### Checks for linker characteristics
### Checks for library functions
+AC_CHECK_FUNCS([accept4])
EFL_LIB_END([Ecore_Con])
#### End of Ecore_Con
lib/ecore_con/efl_network_client.eo \
lib/ecore_con/efl_network_server.eo \
lib/ecore_con/efl_network_connector.eo \
+ lib/ecore_con/efl_net_socket.eo \
+ lib/ecore_con/efl_net_socket_fd.eo \
+ lib/ecore_con/efl_net_socket_tcp.eo \
+ lib/ecore_con/efl_net_dialer.eo \
+ lib/ecore_con/efl_net_dialer_tcp.eo \
+ lib/ecore_con/efl_net_server.eo \
+ lib/ecore_con/efl_net_server_fd.eo \
+ lib/ecore_con/efl_net_server_tcp.eo \
lib/ecore_con/ecore_con_eet_base.eo \
lib/ecore_con/ecore_con_eet_server_obj.eo \
lib/ecore_con/ecore_con_eet_client_obj.eo \
static_libs/http-parser/http_parser.c \
static_libs/http-parser/http_parser.h \
lib/ecore_con/ecore_con_private.h \
-lib/ecore_con/ecore_con_info.c
+lib/ecore_con/ecore_con_info.c \
+lib/ecore_con/efl_net_socket.c \
+lib/ecore_con/efl_net_socket_fd.c \
+lib/ecore_con/efl_net_socket_tcp.c \
+lib/ecore_con/efl_net_dialer.c \
+lib/ecore_con/efl_net_dialer_tcp.c \
+lib/ecore_con/efl_net_server.c \
+lib/ecore_con/efl_net_server_fd.c \
+lib/ecore_con/efl_net_server_tcp.c
EXTRA_DIST2 += lib/ecore_con/ecore_con_legacy.c
/ecore_buffer_consumer_example
/ecore_buffer_provider_example
/efl_io_copier_example
+/efl_net_server_example
ecore_getopt_example \
ecore_con_eet_client_example \
ecore_con_eet_server_example \
-efl_io_copier_example
+efl_io_copier_example \
+efl_net_server_example
ECORE_COMMON_LDADD = \
$(top_builddir)/src/lib/ecore/libecore.la \
efl_io_copier_example_SOURCES = efl_io_copier_example.c
efl_io_copier_example_LDADD = $(ECORE_CON_COMMON_LDADD)
+efl_net_server_example_SOURCES = efl_net_server_example.c
+efl_net_server_example_LDADD = $(ECORE_CON_COMMON_LDADD)
+
SRCS = \
ecore_animator_example.c \
ecore_buffer_example.c \
#define EFL_BETA_API_SUPPORT 1
#define EFL_EO_API_SUPPORT 1
#include <Ecore.h>
+#include <Ecore_Con.h>
#include <Ecore_Getopt.h>
#include <fcntl.h>
{ EFL_IO_BUFFER_EVENT_REALLOCATED, _output_buffer_reallocated });
+static void
+_dialer_resolved(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ fprintf(stderr, "INFO: dialer resolved '%s' to '%s'\n",
+ efl_net_dialer_address_dial_get(event->object),
+ efl_net_socket_address_remote_get(event->object));
+}
+
+static void
+_dialer_error(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ const Eina_Error *perr = event->info;
+ fprintf(stderr, "INFO: error: %d\n", *perr);
+ retval = EXIT_FAILURE;
+ /* no need to quit as copier will get a "eos" event and emit "done" */
+}
+
+static void
+_dialer_connected(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ fprintf(stderr, "INFO: dialer connected to '%s' (%s)\n",
+ efl_net_dialer_address_dial_get(event->object),
+ efl_net_socket_address_remote_get(event->object));
+}
+
+EFL_CALLBACKS_ARRAY_DEFINE(dialer_cbs,
+ { EFL_NET_DIALER_EVENT_RESOLVED, _dialer_resolved },
+ { EFL_NET_DIALER_EVENT_ERROR, _dialer_error },
+ { EFL_NET_DIALER_EVENT_CONNECTED, _dialer_connected });
+
/* copier events are of interest, you should hook to at least "done"
* and "error"
*/
ECORE_GETOPT_HELP('h', "help"),
ECORE_GETOPT_STORE_METAVAR_STR(0, NULL,
- "The input file name or ':stdin:' to read from stdin.",
+ "The input file name or:\n"
+ ":stdin: to read from stdin.\n"
+ "tcp://IP:PORT to connect using TCP and an IPv4 (A.B.C.D:PORT) or IPv6 ([A:B:C:D::E]:PORT).\n"
+ "",
"input-file"),
ECORE_GETOPT_STORE_METAVAR_STR(0, NULL,
"The output file name or:\n"
":stderr: to write to stderr.\n"
":memory: to write to a memory buffer.\n"
":none: to not use a destination object.\n"
+ "tcp://IP:PORT to connect using TCP and an IPv4 (A.B.C.D:PORT) or IPv6 ([A:B:C:D::E]:PORT).\n"
"",
"output-file"),
ECORE_GETOPT_SENTINEL
Eina_Slice line_delm_slice = EINA_SLICE_STR_LITERAL("");
ecore_init();
+ ecore_con_init();
args = ecore_getopt_parse(&options, values, argc, argv);
if (args < 0)
goto end;
}
}
+ else if (strncmp(input_fname, "tcp://", strlen("tcp://")) == 0)
+ {
+ /*
+ * Since Efl.Net.Socket implements the required interfaces,
+ * they can be used here as well.
+ */
+ const char *address = input_fname + strlen("tcp://");
+ Eina_Error err;
+ input = efl_add(EFL_NET_DIALER_TCP_CLASS, NULL,
+ efl_event_callback_array_add(efl_self, input_cbs(), NULL), /* optional */
+ efl_event_callback_array_add(efl_self, dialer_cbs(), NULL) /* optional */
+ );
+ if (!input)
+ {
+ fprintf(stderr, "ERROR: could not create TCP Dialer.\n");
+ retval = EXIT_FAILURE;
+ goto end;
+ }
+
+ err = efl_net_dialer_dial(input, address);
+ if (err)
+ {
+ fprintf(stderr, "ERROR: could not TCP dial %s: %s\n",
+ address, eina_error_msg_get(err));
+ goto end_input;
+ }
+ }
else
{
/* regular file, open with flags: read-only and close-on-exec */
*/
output = NULL;
}
+ else if (strncmp(output_fname, "tcp://", strlen("tcp://")) == 0)
+ {
+ /*
+ * Since Efl.Net.Socket implements the required interfaces,
+ * they can be used here as well.
+ */
+ const char *address = output_fname + strlen("tcp://");
+ Eina_Error err;
+ output = efl_add(EFL_NET_DIALER_TCP_CLASS, NULL,
+ efl_event_callback_array_add(efl_self, output_cbs(), NULL), /* optional */
+ efl_event_callback_array_add(efl_self, dialer_cbs(), NULL) /* optional */
+ );
+ if (!output)
+ {
+ fprintf(stderr, "ERROR: could not create TCP Dialer.\n");
+ retval = EXIT_FAILURE;
+ goto end_input;
+ }
+
+ err = efl_net_dialer_dial(output, address);
+ if (err)
+ {
+ fprintf(stderr, "ERROR: could not TCP dial %s: %s\n",
+ address, eina_error_msg_get(err));
+ goto end_output;
+ }
+ }
else
{
/* regular file, open with flags: write-only, close-on-exec,
input = NULL;
end:
+ ecore_con_shutdown();
ecore_shutdown();
return retval;
--- /dev/null
+#define EFL_BETA_API_SUPPORT 1
+#define EFL_EO_API_SUPPORT 1
+#include <Ecore.h>
+#include <Ecore_Con.h>
+#include <Ecore_Getopt.h>
+#include <fcntl.h>
+
+static int retval = EXIT_SUCCESS;
+static Eina_Bool echo = EINA_FALSE;
+
+/* NOTE: client i/o events are only used as debug, you can omit these */
+
+static void
+_client_can_read_changed(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ fprintf(stderr, "INFO: client %s can_read=%d\n",
+ efl_net_socket_address_remote_get(event->object),
+ efl_io_reader_can_read_get(event->object));
+}
+
+static void
+_client_can_write_changed(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ fprintf(stderr, "INFO: client %s can_write=%d\n",
+ efl_net_socket_address_remote_get(event->object),
+ efl_io_writer_can_write_get(event->object));
+}
+
+static void
+_client_eos(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ fprintf(stderr, "INFO: client %s eos.\n",
+ efl_net_socket_address_remote_get(event->object));
+}
+
+static void
+_client_closed(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ fprintf(stderr, "INFO: client %s closed.\n",
+ efl_net_socket_address_remote_get(event->object));
+}
+
+EFL_CALLBACKS_ARRAY_DEFINE(client_cbs,
+ { EFL_IO_READER_EVENT_CAN_READ_CHANGED, _client_can_read_changed },
+ { EFL_IO_READER_EVENT_EOS, _client_eos },
+ { EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, _client_can_write_changed },
+ { EFL_IO_CLOSER_EVENT_CLOSED, _client_closed });
+
+
+/* copier events are of interest, you should hook to at least "done"
+ * and "error"
+ */
+
+/* echo copier is about the same socket, you can close it right away */
+
+static void
+_echo_copier_done(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ Eo *copier = event->object;
+ fprintf(stderr, "INFO: echo copier done, close and del %p\n", copier);
+ efl_io_closer_close(copier);
+ efl_del(copier);
+}
+
+static void
+_echo_copier_error(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ Eo *copier = event->object;
+ const Eina_Error *perr = event->info;
+
+ retval = EXIT_FAILURE;
+
+ fprintf(stderr, "ERROR: echo copier %p failed %d, close and del.\n",
+ copier, *perr);
+
+ efl_io_closer_close(copier);
+ efl_del(copier);
+}
+
+EFL_CALLBACKS_ARRAY_DEFINE(echo_copier_cbs,
+ { EFL_IO_COPIER_EVENT_DONE, _echo_copier_done },
+ { EFL_IO_COPIER_EVENT_ERROR, _echo_copier_error});
+
+/* When sender and receiver peers are about different entities, you
+ * can only close when both are done, otherwise the socket will be
+ * prematurely closed.
+ *
+ * Here we use a struct with both copiers and NULL them once they are
+ * done, when both are done we close the socket and free the struct.
+ */
+typedef struct {
+ Eo *send_copier;
+ Eo *recv_copier;
+ Eo *client;
+} Send_Recv_Data;
+
+static Send_Recv_Data *
+_send_recv_new(Eo *client)
+{
+ Send_Recv_Data *d = calloc(1, sizeof(Send_Recv_Data));
+ if (!d) return NULL;
+
+ /* take a reference since copiers will only hold their reference
+ * while they are alive. As we're deleting them before calling
+ * efl_io_closer_close(), then we need it for bit longer.
+ */
+ d->client = efl_ref(client);
+ return d;
+}
+
+static void
+_send_recv_free(Send_Recv_Data *d)
+{
+ efl_unref(d->client);
+ free(d);
+}
+
+static void
+_send_recv_done(Send_Recv_Data *d, Eo *copier)
+{
+ if (d->send_copier == copier) d->send_copier = NULL;
+ else d->recv_copier = NULL;
+
+ efl_del(copier);
+ if (d->send_copier || d->recv_copier) return;
+ efl_io_closer_close(d->client);
+ _send_recv_free(d);
+}
+
+static void
+_send_copier_done(void *data, const Eo_Event *event)
+{
+ Eo *copier = event->object;
+ Eo *buffer = efl_io_copier_source_get(copier);
+ Eo *client = efl_io_copier_destination_get(copier);
+ Send_Recv_Data *d = data;
+ Eina_Slice slice;
+
+ /* show what we sent, just for debug */
+ if (!efl_io_buffer_slice_get(buffer, &slice))
+ fprintf(stderr, "ERROR: could not get buffer slice\n");
+ else
+ fprintf(stderr,
+ "INFO: sent to %s %zd bytes:"
+ "\n--BEGIN SENT DATA--\n"
+ EINA_SLICE_STR_FMT
+ "\n--END SENT DATA--\n",
+ efl_net_socket_address_remote_get(client),
+ slice.len, EINA_SLICE_STR_PRINT(slice));
+
+ fprintf(stderr, "INFO: send copier done, check if should close %p\n", copier);
+ _send_recv_done(d, copier);
+}
+
+static void
+_send_copier_error(void *data, const Eo_Event *event)
+{
+ Eo *copier = event->object;
+ Eo *buffer = efl_io_copier_source_get(copier);
+ Eo *client = efl_io_copier_destination_get(copier);
+ const Eina_Error *perr = event->info;
+ Send_Recv_Data *d = data;
+ uint64_t offset;
+ Eina_Slice slice;
+
+ retval = EXIT_FAILURE;
+
+ offset = efl_io_buffer_position_read_get(buffer);
+ if (!efl_io_buffer_slice_get(buffer, &slice))
+ fprintf(stderr, "ERROR: could not get buffer slice\n");
+ else
+ {
+ Eina_Slice remaining = slice;
+
+ remaining.bytes += offset;
+ remaining.len -= offset;
+
+ slice.len = offset;
+
+ fprintf(stderr,
+ "ERROR: sent to %s only %zd bytes:"
+ "\n--BEGIN SENT DATA--\n"
+ EINA_SLICE_STR_FMT
+ "\n--END SENT DATA--\n"
+ "Remaining %zd bytes:"
+ "\n--BEGIN REMAINING DATA--\n"
+ EINA_SLICE_STR_FMT
+ "\n--END REMAINING DATA--\n",
+ efl_net_socket_address_remote_get(client),
+ slice.len, EINA_SLICE_STR_PRINT(slice),
+ remaining.len, EINA_SLICE_STR_PRINT(remaining));
+ }
+
+ fprintf(stderr, "ERROR: send copier %p failed %d, check if should close..\n",
+ copier, *perr);
+ _send_recv_done(d, copier);
+}
+
+EFL_CALLBACKS_ARRAY_DEFINE(send_copier_cbs,
+ { EFL_IO_COPIER_EVENT_DONE, _send_copier_done },
+ { EFL_IO_COPIER_EVENT_ERROR, _send_copier_error});
+
+static void
+_recv_copier_done(void *data, const Eo_Event *event)
+{
+ Eo *copier = event->object;
+ Eo *client = efl_io_copier_source_get(copier);
+ Eo *buffer = efl_io_copier_destination_get(copier);
+ Send_Recv_Data *d = data;
+ Eina_Slice slice;
+
+ /* show case, you could use a copier to Efl_Io_Stdout, a
+ * file... and get progressive processing.
+ *
+ * Here we're using a memory buffer and printing everything at
+ * once.
+ *
+ * You could also steal the binbuf with
+ * efl_io_buffer_binbuf_steal()
+ */
+ if (!efl_io_buffer_slice_get(buffer, &slice))
+ fprintf(stderr, "ERROR: could not get buffer slice\n");
+ else
+ fprintf(stderr,
+ "INFO: recv from %s %zd bytes:"
+ "\n--BEGIN RECV DATA--\n"
+ EINA_SLICE_STR_FMT "\n"
+ "\n--END RECV DATA--\n",
+ efl_net_socket_address_remote_get(client),
+ slice.len, EINA_SLICE_STR_PRINT(slice));
+
+ fprintf(stderr, "INFO: receive copier done, check if should close %p\n", copier);
+ _send_recv_done(d, copier);
+}
+
+static void
+_recv_copier_error(void *data, const Eo_Event *event)
+{
+ Eo *copier = event->object;
+ Eo *buffer = efl_io_copier_source_get(copier);
+ Eo *client = efl_io_copier_destination_get(copier);
+ const Eina_Error *perr = event->info;
+ Send_Recv_Data *d = data;
+ Eina_Slice slice;
+
+ retval = EXIT_FAILURE;
+
+ if (!efl_io_buffer_slice_get(buffer, &slice))
+ fprintf(stderr, "ERROR: could not get buffer slice\n");
+ else
+ fprintf(stderr,
+ "ERROR: recv to %s only %zd bytes:"
+ "\n--BEGIN RECV DATA--\n"
+ EINA_SLICE_STR_FMT "\n"
+ "\n--END RECV DATA--\n",
+ efl_net_socket_address_remote_get(client),
+ slice.len, EINA_SLICE_STR_PRINT(slice));
+
+ fprintf(stderr, "ERROR: receive copier %p failed %d, check if should close..\n",
+ copier, *perr);
+ _send_recv_done(d, copier);
+}
+
+EFL_CALLBACKS_ARRAY_DEFINE(recv_copier_cbs,
+ { EFL_IO_COPIER_EVENT_DONE, _recv_copier_done },
+ { EFL_IO_COPIER_EVENT_ERROR, _recv_copier_error});
+
+
+/* server events are mandatory, afterall you need to define what's
+ * going to happen after a client socket is connected. This is the
+ * "client,add" event.
+ *
+ * if clients_limit and clients_reject_excess are set, then
+ * "client,rejected" is dispatched for rejected sockets, they contain
+ * the string with socket identification.
+ */
+static void
+_server_client_add(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ Efl_Net_Socket *client = event->info;
+
+ fprintf(stderr, "INFO: accepted client %s\n",
+ efl_net_socket_address_remote_get(client));
+
+ /* to use a client, you must efl_ref() it. Here we're not doing it
+ * explicitly because copiers do take a reference.
+ */
+
+ /*
+ * monitor the client socket for debug purposes (optional)
+ */
+ efl_event_callback_array_add(client, client_cbs(), NULL);
+
+ /*
+ * Since sockets are reader/writer/closer objects, we can use the
+ * Efl_Io_Copier utility.
+ */
+
+ if (echo)
+ {
+ /*
+ * An echo copier is pretty simple, use the socket as both
+ * source and destination.
+ */
+ Eo *echo_copier = efl_add(EFL_IO_COPIER_CLASS, efl_parent_get(client),
+ efl_io_copier_source_set(efl_self, client),
+ efl_io_copier_destination_set(efl_self, client),
+ efl_event_callback_array_add(efl_self, echo_copier_cbs(), client)
+ );
+
+ fprintf(stderr, "INFO: using an echo copier=%p for client %s\n",
+ echo_copier, efl_net_socket_address_remote_get(client));
+ return;
+ }
+ else
+ {
+ /*
+ * Here we create a fixed buffer with a string to send:
+ * - "Hello World!"
+ * and another one to store the received buffer so we can print as
+ * a single blob at the end.
+ *
+ * One can change these to Efl_Io_File or event pipe to something
+ * else like Efl_Io_Stdin, Efl_Io_Stdout and it would just work.
+ */
+ Eina_Slice slice;
+ Send_Recv_Data *d;
+ Eo *send_buffer, *recv_buffer;
+
+ d = _send_recv_new(client);
+ if (!d)
+ {
+ fprintf(stderr, "ERROR: could not allocate memory\n");
+ return;
+ }
+
+ // TODO buffer constructor taking RO string
+ send_buffer = efl_add(EFL_IO_BUFFER_CLASS, NULL);
+ slice = (Eina_Slice)EINA_SLICE_STR("Hello World!");
+ efl_io_writer_write(send_buffer, &slice, NULL);
+
+ /* Unlimited buffer to store the received data. */
+ recv_buffer = efl_add(EFL_IO_BUFFER_CLASS, NULL);
+
+ /* an input copier that takes data from send_buffer and pushes to client */
+ d->send_copier = efl_add(EFL_IO_COPIER_CLASS, efl_parent_get(client),
+ efl_io_copier_source_set(efl_self, send_buffer),
+ efl_io_copier_destination_set(efl_self, client),
+ efl_event_callback_array_add(efl_self, send_copier_cbs(), d)
+ );
+
+ fprintf(stderr, "INFO: using sender buffer %p with copier %p for client %s\n",
+ send_buffer, d->send_copier, efl_net_socket_address_remote_get(client));
+
+ efl_unref(send_buffer); /* d->send_copier adds a reference */
+ if (!d->send_copier)
+ fprintf(stderr, "ERROR: failed to create sender copier\n");
+
+
+ /* an output copier that takes data from socket and pushes to recv_buffer. */
+ d->recv_copier = efl_add(EFL_IO_COPIER_CLASS, efl_parent_get(client),
+ efl_io_copier_source_set(efl_self, client),
+ efl_io_copier_destination_set(efl_self, recv_buffer),
+ efl_event_callback_array_add(efl_self, recv_copier_cbs(), d)
+ );
+
+ fprintf(stderr, "INFO: using receiver buffer %p with copier %p for client %s\n",
+ recv_buffer, d->recv_copier, efl_net_socket_address_remote_get(client));
+
+ efl_unref(recv_buffer); /* d->recv_copier adds a reference */
+ if (!d->recv_copier)
+ fprintf(stderr, "ERROR: failed to create receiver copier\n");
+
+ if (!d->recv_copier && !d->send_copier)
+ _send_recv_free(d);
+ }
+}
+
+static void
+_server_client_rejected(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ const char *client_address = event->info;
+ fprintf(stderr, "INFO: rejected client %s\n", client_address);
+}
+
+static void
+_server_error(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ const Eina_Error *perr = event->info;
+ fprintf(stderr, "INFO: error: %d\n", *perr);
+ retval = EXIT_FAILURE;
+ ecore_main_loop_quit();
+}
+
+static void
+_server_serving(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ fprintf(stderr, "INFO: serving at %s\n",
+ efl_net_server_address_get(event->object));
+}
+
+EFL_CALLBACKS_ARRAY_DEFINE(server_cbs,
+ { EFL_NET_SERVER_EVENT_CLIENT_ADD, _server_client_add },
+ { EFL_NET_SERVER_EVENT_CLIENT_REJECTED, _server_client_rejected },
+ { EFL_NET_SERVER_EVENT_ERROR, _server_error },
+ { EFL_NET_SERVER_EVENT_SERVING, _server_serving });
+
+static const char * protocols[] = {
+ "tcp",
+ NULL
+};
+
+
+static const Ecore_Getopt options = {
+ "efl_net_server_example", /* program name */
+ NULL, /* usage line */
+ "1", /* version */
+ "(C) 2016 Enlightenment Project", /* copyright */
+ "BSD 2-Clause", /* license */
+ /* long description, may be multiline and contain \n */
+ "Example of Efl_Net_Server objects usage.\n"
+ "\n"
+ "This example spawns a server of the given protocol at the given address.",
+ EINA_FALSE,
+ {
+ ECORE_GETOPT_STORE_BOOL('e', "echo",
+ "If true, will send back to client all the data receive (echo server)"),
+ ECORE_GETOPT_STORE_UINT('l', "clients-limit",
+ "If set will limit number of clients to accept"),
+ ECORE_GETOPT_STORE_BOOL('r', "clients-reject-excess",
+ "If true, excess clients will be immediately rejected."),
+ ECORE_GETOPT_VERSION('V', "version"),
+ ECORE_GETOPT_COPYRIGHT('C', "copyright"),
+ ECORE_GETOPT_LICENSE('L', "license"),
+ ECORE_GETOPT_HELP('h', "help"),
+
+ ECORE_GETOPT_CHOICE_METAVAR(0, NULL, "The server protocol.", "protocol",
+ protocols),
+ ECORE_GETOPT_STORE_METAVAR_STR(0, NULL,
+ "The server address to listen, such as "
+ "IPv4:PORT, [IPv6]:PORT, Unix socket path...",
+ "address"),
+ ECORE_GETOPT_SENTINEL
+ }
+};
+
+int
+main(int argc, char **argv)
+{
+ const Efl_Class *cls;
+ char *protocol = NULL;
+ char *address = NULL;
+ unsigned int clients_limit = 0;
+ Eina_Bool clients_reject_excess = EINA_FALSE;
+ Eina_Bool quit_option = EINA_FALSE;
+ Ecore_Getopt_Value values[] = {
+ ECORE_GETOPT_VALUE_BOOL(echo),
+ ECORE_GETOPT_VALUE_UINT(clients_limit),
+ ECORE_GETOPT_VALUE_BOOL(clients_reject_excess),
+
+ /* standard block to provide version, copyright, license and help */
+ ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */
+ ECORE_GETOPT_VALUE_BOOL(quit_option), /* -C/--copyright quits */
+ ECORE_GETOPT_VALUE_BOOL(quit_option), /* -L/--license quits */
+ ECORE_GETOPT_VALUE_BOOL(quit_option), /* -h/--help quits */
+
+ /* positional argument */
+ ECORE_GETOPT_VALUE_STR(protocol),
+ ECORE_GETOPT_VALUE_STR(address),
+
+ ECORE_GETOPT_VALUE_NONE /* sentinel */
+ };
+ int args;
+ Eo *server;
+ Eina_Error err;
+
+ ecore_init();
+ ecore_con_init();
+
+ args = ecore_getopt_parse(&options, values, argc, argv);
+ if (args < 0)
+ {
+ fputs("ERROR: Could not parse command line options.\n", stderr);
+ retval = EXIT_FAILURE;
+ goto end;
+ }
+
+ if (quit_option) goto end;
+
+ args = ecore_getopt_parse_positional(&options, values, argc, argv, args);
+ if (args < 0)
+ {
+ fputs("ERROR: Could not parse positional arguments.\n", stderr);
+ retval = EXIT_FAILURE;
+ goto end;
+ }
+
+ if (strcmp(protocol, "tcp") == 0) cls = EFL_NET_SERVER_TCP_CLASS;
+ else
+ {
+ fprintf(stderr, "ERROR: unsupported protocol: %s\n", protocol);
+ goto end;
+ }
+
+ server = efl_add(cls, ecore_main_loop_get(), /* it's mandatory to use a main loop provider as the server parent */
+ efl_net_server_fd_close_on_exec_set(efl_self, EINA_TRUE), /* recommended */
+ efl_net_server_fd_reuse_address_set(efl_self, EINA_TRUE), /* optional, but nice for testing */
+ efl_net_server_fd_reuse_port_set(efl_self, EINA_TRUE), /* optional, but nice for testing... not secure unless you know what you're doing */
+ efl_net_server_clients_limit_set(efl_self,
+ clients_limit,
+ clients_reject_excess), /* optional */
+ efl_event_callback_array_add(efl_self, server_cbs(), NULL)); /* mandatory to have "client,add" in order to be useful */
+ if (!server)
+ {
+ fprintf(stderr, "ERROR: could not create class %p (%s)\n",
+ cls, efl_class_name_get(cls));
+ goto end;
+ }
+
+ /* an explicit call to efl_net_server_serve() after the object is
+ * constructed allows for more complex setup, such as interacting
+ * with the object to add more properties that couldn't be done
+ * during efl_add().
+ */
+ err = efl_net_server_serve(server, address);
+ if (err)
+ {
+ fprintf(stderr, "ERROR: could not serve(%s): %s\n",
+ address, eina_error_msg_get(err));
+ goto end_server;
+ }
+
+ ecore_main_loop_begin();
+
+ end_server:
+ efl_del(server);
+ server = NULL;
+
+ end:
+ ecore_con_shutdown();
+ ecore_shutdown();
+
+ return retval;
+}
#include "efl_network_connector.eo.h"
#include "efl_network_client.eo.h"
#include "efl_network_url.eo.h"
+
+#include "efl_net_socket.eo.h"
+#include "efl_net_dialer.eo.h"
+#include "efl_net_server.eo.h"
+
+#include "efl_net_socket_fd.eo.h"
+#include "efl_net_server_fd.eo.h"
+
+#include "efl_net_socket_tcp.eo.h"
+#include "efl_net_dialer_tcp.eo.h"
+#include "efl_net_server_tcp.eo.h"
#include "efl_network_client.eo.c"
#include "efl_network_server.eo.c"
#include "efl_network_connector.eo.c"
+
+Eina_Bool
+efl_net_ip_port_fmt(char *buf, int buflen, const struct sockaddr *addr)
+{
+ char p[INET6_ADDRSTRLEN];
+ const void *mem;
+ unsigned short port;
+ int r;
+
+ if (addr->sa_family == AF_INET)
+ {
+ const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
+ mem = &a->sin_addr;
+ port = ntohs(a->sin_port);
+ }
+ else if (addr->sa_family == AF_INET6)
+ {
+ const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)addr;
+ mem = &a->sin6_addr;
+ port = ntohs(a->sin6_port);
+ }
+ else
+ {
+ ERR("unsupported address family: %d", addr->sa_family);
+ return EINA_FALSE;
+ }
+
+ if (!inet_ntop(addr->sa_family, mem, p, sizeof(p)))
+ {
+ ERR("inet_ntop(%d, %p, %p, %zd): %s",
+ addr->sa_family, mem, p, sizeof(p), strerror(errno));
+ return EINA_FALSE;
+ }
+
+ if (addr->sa_family == AF_INET)
+ r = snprintf(buf, buflen, "%s:%hu", p, port);
+ else
+ r = snprintf(buf, buflen, "[%s]:%hu", p, port);
+
+ if (r < 0)
+ {
+ ERR("could not snprintf(): %s", strerror(errno));
+ return EINA_FALSE;
+ }
+ else if (r > buflen)
+ {
+ ERR("buffer is too small: %d, required %d", buflen, r);
+ return EINA_FALSE;
+ }
+
+ return EINA_TRUE;
+}
#undef GENERIC_ALLOC_FREE_HEADER
+Eina_Bool efl_net_ip_port_fmt(char *buf, int buflen, const struct sockaddr *addr);
+
#endif
--- /dev/null
+#define EFL_NET_DIALER_PROTECTED 1
+#define EFL_NET_SOCKET_PROTECTED 1
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "Ecore.h"
+#include "Ecore_Con.h"
+#include "ecore_con_private.h"
+
+#include "efl_net_dialer.eo.c"
--- /dev/null
+interface Efl.Net.Dialer (Efl.Net.Socket) {
+ [[Creates a client socket to reach a remote peer.
+
+ The connection process starts when @.dial is executed, this
+ allows implementations to request more complex setup that would
+ require a live object handle that is not possible during
+ construction.
+
+ The socket should be considered connected and ready to use only
+ when "connected" event is dispatched, by then
+ @Efl.Io.Reader.can_read and @Efl.Io.Writer.can_write should
+ change accordingly.
+
+ Once the socket is closed, @Efl.Io.Closer.closed will be called
+ and the "closed" event is dispatched.
+
+ @since 1.19
+ ]]
+ methods {
+ dial {
+ [[Dials to the remote peer.
+
+ This method starts the connection process, resolving
+ address and then proceeding to the actual connection.
+
+ Once the connection is fully setup, "connected" event is
+ dispatched.
+ ]]
+ params {
+ address: string @nonull;
+ }
+ return: Eina.Error;
+ }
+
+ @property address_dial {
+ [[Return the address given to @.dial.
+
+ If the resolved address is desired, then listen to
+ "resolved" event and use the
+ @Efl.Net.Socket.address_remote property.
+ ]]
+ get { }
+ set @protected { }
+ values {
+ address: string;
+ }
+ }
+
+ @property connected {
+ [[Returns whenever the socket is connected or not.
+
+ Whenever this property becomes true, "connected" event
+ should be dispatched.
+ ]]
+ get { }
+ set @protected { }
+ values {
+ connected: bool;
+ }
+ }
+
+ @property timeout_dial {
+ [[The timeout in seconds to use for dialing/connecting.
+
+ This should be set before dialing.
+ ]]
+ get { }
+ set { }
+ values {
+ seconds: double;
+ }
+ }
+ }
+
+ events {
+ resolved: string; [[Notifies @.address_dial was resolved to
+ @Efl.Net.Socket.address_remote.
+
+ This is emitted before "connected" and may
+ be emitted from @Efl.Object.finalize, thus
+ be sure to connect the callbacks during
+ the object construction.
+
+ Some protocols allows redirects and this
+ may be emitted multiple times, such as
+ HTTP.
+ ]]
+ error: Eina.Error; [[Some error happened and the socket
+ stopped working.
+ ]]
+ connected; [[Notifies the socket is connected to the remote peer.]]
+ }
+}
--- /dev/null
+#define EFL_NET_DIALER_TCP_PROTECTED 1
+#define EFL_NET_DIALER_PROTECTED 1
+#define EFL_NET_SOCKET_FD_PROTECTED 1
+#define EFL_NET_SOCKET_PROTECTED 1
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "Ecore.h"
+#include "Ecore_Con.h"
+#include "ecore_con_private.h"
+
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_TCP_H
+# include <netinet/tcp.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+#ifdef HAVE_EVIL
+# include <Evil.h>
+#endif
+
+#define MY_CLASS EFL_NET_DIALER_TCP_CLASS
+
+typedef struct _Efl_Net_Dialer_Tcp_Data
+{
+ Eina_Stringshare *address_dial;
+ Eina_Bool connected;
+ double timeout_dial;
+} Efl_Net_Dialer_Tcp_Data;
+
+EOLIAN static void
+_efl_net_dialer_tcp_efl_object_destructor(Eo *o, Efl_Net_Dialer_Tcp_Data *pd)
+{
+ efl_destructor(efl_super(o, MY_CLASS));
+
+ eina_stringshare_replace(&pd->address_dial, NULL);
+}
+
+EOLIAN static Eina_Error
+_efl_net_dialer_tcp_efl_net_dialer_dial(Eo *o, Efl_Net_Dialer_Tcp_Data *pd EINA_UNUSED, const char *address)
+{
+ struct sockaddr_storage addr = {};
+ char *str, *host, *port;
+ int r, fd, extra_flags = 0;
+ socklen_t addrlen;
+ char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
+
+ EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL);
+ EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_net_dialer_connected_get(o), EISCONN);
+ EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EBADF);
+ EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_loop_fd_get(o) >= 0, EALREADY);
+
+ // TODO: change to getaddrinfo() and move to a thread...
+ str = host = strdup(address);
+ EINA_SAFETY_ON_NULL_RETURN_VAL(str, ENOMEM);
+
+ if (host[0] == '[')
+ {
+ struct sockaddr_in6 *a = (struct sockaddr_in6 *)&addr;
+ /* IPv6 is: [IP]:port */
+ host++;
+ port = strchr(host, ']');
+ if (!port)
+ {
+ ERR("missing ']' in IPv6 address: %s", address);
+ goto invalid_address;
+ }
+ *port = '\0';
+ port++;
+
+ if (port[0] == ':')
+ port++;
+ else
+ port = NULL;
+ a->sin6_family = AF_INET6;
+ a->sin6_port = htons(port ? atoi(port) : 0);
+ r = inet_pton(AF_INET6, host, &(a->sin6_addr));
+ addrlen = sizeof(*a);
+ }
+ else
+ {
+ struct sockaddr_in *a = (struct sockaddr_in *)&addr;
+ port = strchr(host, ':');
+ if (port)
+ {
+ *port = '\0';
+ port++;
+ }
+ a->sin_family = AF_INET;
+ a->sin_port = htons(port ? atoi(port) : 0);
+ r = inet_pton(AF_INET, host, &(a->sin_addr));
+ addrlen = sizeof(*a);
+ }
+
+ if (r != 1)
+ {
+ ERR("could not parse IP '%s' (%s)", host, address);
+ goto invalid_address;
+ }
+ free(str);
+
+ efl_net_socket_fd_family_set(o, addr.ss_family);
+ efl_net_dialer_address_dial_set(o, address);
+
+ if (efl_net_ip_port_fmt(buf, sizeof(buf), (struct sockaddr *)&addr))
+ {
+ efl_net_socket_address_remote_set(o, buf);
+ efl_event_callback_call(o, EFL_NET_DIALER_EVENT_RESOLVED, NULL);
+ }
+
+ if (efl_net_socket_fd_close_on_exec_get(o))
+ extra_flags |= SOCK_CLOEXEC;
+
+ fd = socket(addr.ss_family, SOCK_STREAM | extra_flags, IPPROTO_TCP);
+ if (fd < 0)
+ {
+ ERR("socket(%d, SOCK_STREAM | %#x, IPPROTO_TCP): %s",
+ addr.ss_family, extra_flags, strerror(errno));
+ return errno;
+ }
+
+ r = connect(fd, (struct sockaddr *)&addr, addrlen);
+ if (r < 0)
+ {
+ int errno_bkp = errno;
+ ERR("connect(%d, %s): %s", fd, address, strerror(errno));
+ close(fd);
+ return errno_bkp;
+ }
+
+ efl_loop_fd_set(o, fd);
+ efl_net_dialer_connected_set(o, EINA_TRUE);
+ return 0;
+
+ invalid_address:
+ free(str);
+ return EINVAL;
+}
+
+EOLIAN static void
+_efl_net_dialer_tcp_efl_net_dialer_address_dial_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Tcp_Data *pd, const char *address)
+{
+ eina_stringshare_replace(&pd->address_dial, address);
+}
+
+EOLIAN static const char *
+_efl_net_dialer_tcp_efl_net_dialer_address_dial_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Tcp_Data *pd)
+{
+ return pd->address_dial;
+}
+
+EOLIAN static void
+_efl_net_dialer_tcp_efl_net_dialer_timeout_dial_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Tcp_Data *pd, double seconds)
+{
+ ERR("TODO: when using ecore_con_info/threads, set timeout");
+ pd->timeout_dial = seconds;
+}
+
+EOLIAN static double
+_efl_net_dialer_tcp_efl_net_dialer_timeout_dial_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Tcp_Data *pd)
+{
+ return pd->timeout_dial;
+}
+
+EOLIAN static void
+_efl_net_dialer_tcp_efl_net_dialer_connected_set(Eo *o, Efl_Net_Dialer_Tcp_Data *pd, Eina_Bool connected)
+{
+ if (pd->connected == connected) return;
+ pd->connected = connected;
+ if (connected) efl_event_callback_call(o, EFL_NET_DIALER_EVENT_CONNECTED, NULL);
+}
+
+EOLIAN static Eina_Bool
+_efl_net_dialer_tcp_efl_net_dialer_connected_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Tcp_Data *pd)
+{
+ return pd->connected;
+}
+
+EOLIAN static Eina_Error
+_efl_net_dialer_tcp_efl_io_closer_close(Eo *o, Efl_Net_Dialer_Tcp_Data *pd EINA_UNUSED)
+{
+ efl_net_dialer_connected_set(o, EINA_FALSE);
+ return efl_io_closer_close(efl_super(o, MY_CLASS));
+}
+
+#include "efl_net_dialer_tcp.eo.c"
--- /dev/null
+class Efl.Net.Dialer.Tcp (Efl.Net.Socket.Tcp, Efl.Net.Dialer) {
+ [[Connects to a remote TCP server.
+
+ @since 1.19
+ ]]
+
+ implements {
+ Efl.Object.destructor;
+ Efl.Net.Dialer.dial;
+ Efl.Net.Dialer.address_dial;
+ Efl.Net.Dialer.connected;
+ Efl.Net.Dialer.timeout_dial;
+ Efl.Io.Closer.close;
+ }
+}
--- /dev/null
+#define EFL_NET_SERVER_PROTECTED 1
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "Ecore.h"
+#include "Ecore_Con.h"
+#include "ecore_con_private.h"
+
+#include "efl_net_server.eo.c"
--- /dev/null
+interface Efl.Net.Server {
+ [[The basic server interface.
+
+ It will start serving and accepting clients once @.serve is
+ called and the "serving" event is dispatched.
+
+ When new clients are accepted, then "client,add" event is
+ dispatched with a child object implementing @Efl.Net.Socket
+ interface. These implement the standard @Efl.Io.Reader,
+ @Efl.Io.Writer and @Efl.Io.Closer interfaces, thus can be used
+ with utilities such as @Efl.Io.Copier.
+
+ @since 1.19
+ ]]
+
+ events {
+ client,add @hot: Efl.Net.Socket; [[A new client socket was created.
+
+ The socket will have the
+ server as parent and can be
+ closed by both the server
+ or the user using
+ @Efl.Io.Closer.
+ ]]
+ client,rejected: string; [[Notifies a client was rejected due
+ excess, see @.clients_limit.
+ ]]
+
+ error: Eina.Error; [[Some error happened and the server needs
+ to be stopped.
+ ]]
+ serving; [[Notifies the server is ready to accept clients.
+ See property @.serving]]
+ }
+
+ methods {
+ serve {
+ [[Starts serving requests.
+
+ This method starts the server, resolving address and
+ then proceeding to the actual listen(2)/bind(2)
+ equivalent..
+
+ Once the connection is fully setup, "serving" event is
+ dispatched.
+ ]]
+ params {
+ address: string @nonull;
+ }
+ return: Eina.Error;
+ }
+
+ @property address {
+ [[The address the server is bound to.
+
+ The actual value depends on the type of server, like an
+ IPv4 (ip:port) or IPv6 ([ip]:port) formatted for a
+ TCP/UDP server, the path if an Unix Local...
+
+ It's always resolved, then if operations are working
+ with domain names or URL, this is the values the kernel
+ reports. It's similar to getsockname() in behavior.
+ ]]
+ get { }
+ set @protected { }
+ values {
+ address: string;
+ }
+ }
+
+ @property clients_count {
+ [[Number of concurrent clients accepted by this server.]]
+ get { }
+ set @protected { }
+ values {
+ count: uint;
+ }
+ }
+
+ @property clients_limit {
+ [[Number of maximum concurrent clients allowed by this server.
+
+ If reject_excess is set to true, then the connection
+ will be accepted and immediately closed.
+
+ If reject_excess is set to false (default), then
+ accept(2) won't be called and clients will be queued at
+ the kernel side, usually up to 4096 pending clients.
+
+ Whenever changed, this property will only apply to new
+ connections, that is, if the current connection count
+ alredy exceeds the limit, no connections will be closed.
+ ]]
+ values {
+ limit: uint;
+ reject_excess: bool @optional;
+ }
+ }
+
+ @property serving {
+ [[Returns whenever the server is ready to accept clients or not.
+
+ Whenever this property becomes true, "serving" event
+ should be dispatched.
+ ]]
+ get { }
+ set @protected { }
+ values {
+ serving: bool;
+ }
+ }
+ }
+}
--- /dev/null
+#define EFL_NET_SERVER_FD_PROTECTED 1
+#define EFL_NET_SERVER_PROTECTED 1
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "Ecore.h"
+#include "Ecore_Con.h"
+#include "ecore_con_private.h"
+
+#include <fcntl.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#ifdef HAVE_EVIL
+# include <Evil.h>
+#endif
+
+#define MY_CLASS EFL_NET_SERVER_FD_CLASS
+
+typedef struct _Efl_Net_Server_Fd_Data
+{
+ Eina_Stringshare *address;
+ int family;
+ unsigned int clients_count;
+ unsigned int clients_limit;
+ Eina_Bool clients_reject_excess;
+ Eina_Bool serving;
+ Eina_Bool close_on_exec;
+ Eina_Bool reuse_address;
+ Eina_Bool reuse_port;
+} Efl_Net_Server_Fd_Data;
+
+static void
+_efl_net_server_fd_event_read(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ Eo *o = event->object;
+ unsigned int count, limit;
+ Eina_Bool reject_excess, do_reject = EINA_FALSE;
+ struct sockaddr_storage addr;
+ int client, fd, flags = 0;
+ socklen_t addrlen;
+
+ count = efl_net_server_clients_count_get(o);
+ efl_net_server_clients_limit_get(o, &limit, &reject_excess);
+
+ if ((limit > 0) && (count >= limit))
+ {
+ if (!reject_excess)
+ {
+ // TODO: disconnect 'read' so stops calling?
+ return;
+ }
+ do_reject = EINA_TRUE;
+ }
+
+ fd = efl_loop_fd_get(o);
+
+ if (efl_net_server_fd_close_on_exec_get(o))
+ flags |= SOCK_CLOEXEC;
+
+ addrlen = sizeof(addr);
+#ifdef HAVE_ACCEPT4
+ client = accept4(fd, (struct sockaddr *)&addr, &addrlen, flags);
+#else
+ client = accept(fd, (struct sockaddr *)&addr, &addrlen);
+#endif
+ if (client < 0)
+ {
+ Eina_Error err = errno;
+ ERR("accept(%d): %s", fd, strerror(errno));
+ efl_event_callback_call(o, EFL_NET_SERVER_EVENT_ERROR, &err);
+ return;
+ }
+
+#ifndef HAVE_ACCEPT4
+ if (fcntl(fd, F_SETFD, flags) < 0)
+ ERR("fcntl(%d, F_SETFD, %#x): %s", fd, flags, strerror(errno));
+#endif
+
+ if (do_reject)
+ efl_net_server_fd_client_reject(o, client);
+ else
+ efl_net_server_fd_client_add(o, client);
+}
+
+static void
+_efl_net_server_fd_event_error(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ Eo *o = event->object;
+ Eina_Error err = EBADF;
+
+ efl_net_server_serving_set(o, EINA_FALSE);
+ efl_event_callback_call(o, EFL_NET_SERVER_EVENT_ERROR, &err);
+}
+
+EOLIAN static Efl_Object *
+_efl_net_server_fd_efl_object_finalize(Eo *o, Efl_Net_Server_Fd_Data *pd EINA_UNUSED)
+{
+ o = efl_finalize(efl_super(o, MY_CLASS));
+ if (!o) return NULL;
+
+ // TODO: only register "read" if "can_read" is being monitored?
+ efl_event_callback_add(o, EFL_LOOP_FD_EVENT_READ, _efl_net_server_fd_event_read, NULL);
+ efl_event_callback_add(o, EFL_LOOP_FD_EVENT_ERROR, _efl_net_server_fd_event_error, NULL);
+ return o;
+}
+
+EOLIAN static Efl_Object *
+_efl_net_server_fd_efl_object_constructor(Eo *o, Efl_Net_Server_Fd_Data *pd EINA_UNUSED)
+{
+ pd->family = AF_UNSPEC;
+ return efl_constructor(efl_super(o, MY_CLASS));
+}
+
+EOLIAN static void
+_efl_net_server_fd_efl_object_destructor(Eo *o, Efl_Net_Server_Fd_Data *pd)
+{
+ efl_destructor(efl_super(o, MY_CLASS));
+
+ eina_stringshare_replace(&pd->address, NULL);
+}
+
+EOLIAN static void
+_efl_net_server_fd_efl_loop_fd_fd_set(Eo *o, Efl_Net_Server_Fd_Data *pd, int fd)
+{
+ efl_loop_fd_set(efl_super(o, MY_CLASS), fd);
+
+ if (fd >= 0)
+ {
+ /* apply postponed values */
+ efl_net_server_fd_close_on_exec_set(o, pd->close_on_exec);
+ efl_net_server_fd_reuse_address_set(o, pd->reuse_address);
+ efl_net_server_fd_reuse_port_set(o, pd->reuse_port);
+
+ if (pd->family == AF_UNSPEC)
+ {
+ ERR("efl_loop_fd_set() must be called after efl_net_server_fd_family_set()");
+ return;
+ }
+ }
+ else
+ {
+ efl_net_server_address_set(o, NULL);
+ }
+}
+
+EOLIAN static void
+_efl_net_server_fd_efl_net_server_address_set(Eo *o EINA_UNUSED, Efl_Net_Server_Fd_Data *pd, const char *address)
+{
+ eina_stringshare_replace(&pd->address, address);
+}
+
+EOLIAN static const char *
+_efl_net_server_fd_efl_net_server_address_get(Eo *o EINA_UNUSED, Efl_Net_Server_Fd_Data *pd)
+{
+ return pd->address;
+}
+
+EOLIAN static unsigned int
+_efl_net_server_fd_efl_net_server_clients_count_get(Eo *o EINA_UNUSED, Efl_Net_Server_Fd_Data *pd)
+{
+ return pd->clients_count;
+}
+
+EOLIAN static void
+_efl_net_server_fd_efl_net_server_clients_count_set(Eo *o EINA_UNUSED, Efl_Net_Server_Fd_Data *pd, unsigned int count)
+{
+ pd->clients_count = count;
+}
+
+EOLIAN static void
+_efl_net_server_fd_efl_net_server_clients_limit_set(Eo *o EINA_UNUSED, Efl_Net_Server_Fd_Data *pd, unsigned int limit, Eina_Bool reject_excess)
+{
+ pd->clients_limit = limit;
+ pd->clients_reject_excess = reject_excess;
+}
+
+EOLIAN static void
+_efl_net_server_fd_efl_net_server_clients_limit_get(Eo *o EINA_UNUSED, Efl_Net_Server_Fd_Data *pd, unsigned int *limit, Eina_Bool *reject_excess)
+{
+ if (limit) *limit = pd->clients_limit;
+ if (reject_excess) *reject_excess = pd->clients_reject_excess;
+}
+
+EOLIAN static void
+_efl_net_server_fd_efl_net_server_serving_set(Eo *o EINA_UNUSED, Efl_Net_Server_Fd_Data *pd, Eina_Bool serving)
+{
+ if (pd->serving == serving) return;
+ pd->serving = serving;
+ if (serving)
+ efl_event_callback_call(o, EFL_NET_SERVER_EVENT_SERVING, NULL);
+}
+
+EOLIAN static Eina_Error
+_efl_net_server_fd_efl_net_server_serve(Eo *o EINA_UNUSED, Efl_Net_Server_Fd_Data *pd EINA_UNUSED, const char *address)
+{
+ DBG("address=%s", address);
+ return 0;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_server_fd_efl_net_server_serving_get(Eo *o EINA_UNUSED, Efl_Net_Server_Fd_Data *pd)
+{
+ return pd->serving;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_server_fd_close_on_exec_set(Eo *o, Efl_Net_Server_Fd_Data *pd, Eina_Bool close_on_exec)
+{
+ int flags, fd;
+ Eina_Bool old = pd->close_on_exec;
+
+ pd->close_on_exec = close_on_exec;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return EINA_TRUE; /* postpone until fd_set() */
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0)
+ {
+ ERR("fcntl(%d, F_GETFD): %s", fd, strerror(errno));
+ return EINA_FALSE;
+ }
+ if (close_on_exec)
+ flags |= FD_CLOEXEC;
+ else
+ flags &= (~FD_CLOEXEC);
+ if (fcntl(fd, F_SETFD, flags) < 0)
+ {
+ ERR("fcntl(%d, F_SETFD, %#x): %s", fd, flags, strerror(errno));
+ pd->close_on_exec = old;
+ return EINA_FALSE;
+ }
+
+ return EINA_TRUE;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_server_fd_close_on_exec_get(Eo *o, Efl_Net_Server_Fd_Data *pd)
+{
+ int flags, fd;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return pd->close_on_exec;
+
+ /* if there is a fd, always query it directly as it may be modified
+ * elsewhere by nasty users.
+ */
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0)
+ {
+ ERR("fcntl(%d, F_GETFD): %s", fd, strerror(errno));
+ return EINA_FALSE;
+ }
+
+ pd->close_on_exec = !!(flags & FD_CLOEXEC); /* sync */
+ return pd->close_on_exec;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_server_fd_reuse_address_set(Eo *o, Efl_Net_Server_Fd_Data *pd, Eina_Bool reuse_address)
+{
+ int value, fd;
+ Eina_Bool old = pd->reuse_address;
+
+ pd->reuse_address = reuse_address;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return EINA_TRUE; /* postpone until fd_set() */
+
+ value = reuse_address;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) < 0)
+ {
+ ERR("setsockopt(%d, SOL_SOCKET, SO_REUSEADDR, %d): %s",
+ fd, value, strerror(errno));
+ pd->reuse_address = old;
+ return EINA_FALSE;
+ }
+
+ return EINA_TRUE;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_server_fd_reuse_address_get(Eo *o, Efl_Net_Server_Fd_Data *pd)
+{
+ int value = 0, fd;
+ socklen_t valuelen;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return pd->reuse_address;
+
+ /* if there is a fd, always query it directly as it may be modified
+ * elsewhere by nasty users.
+ */
+ valuelen = sizeof(value);
+ if (getsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &value, &valuelen) < 0)
+ {
+ ERR("getsockopt(%d, SOL_SOCKET, SO_REUSEADDR): %s",
+ fd, strerror(errno));
+ return EINA_FALSE;
+ }
+
+ pd->reuse_address = !!value; /* sync */
+ return pd->reuse_address;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_server_fd_reuse_port_set(Eo *o, Efl_Net_Server_Fd_Data *pd, Eina_Bool reuse_port)
+{
+ int value, fd;
+ Eina_Bool old = pd->reuse_port;
+
+ pd->reuse_port = reuse_port;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return EINA_TRUE; /* postpone until fd_set() */
+
+ value = reuse_port;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value)) < 0)
+ {
+ ERR("setsockopt(%d, SOL_SOCKET, SO_REUSEPORT, %d): %s",
+ fd, value, strerror(errno));
+ pd->reuse_port = old;
+ return EINA_FALSE;
+ }
+
+ return EINA_TRUE;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_server_fd_reuse_port_get(Eo *o, Efl_Net_Server_Fd_Data *pd)
+{
+ int value = 0, fd;
+ socklen_t valuelen;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return pd->reuse_port;
+
+ /* if there is a fd, always query it directly as it may be modified
+ * elsewhere by nasty users.
+ */
+ valuelen = sizeof(value);
+ if (getsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &value, &valuelen) < 0)
+ {
+ ERR("getsockopt(%d, SOL_SOCKET, SO_REUSEPORT): %s",
+ fd, strerror(errno));
+ return EINA_FALSE;
+ }
+
+ pd->reuse_port = !!value; /* sync */
+ return pd->reuse_port;
+}
+
+EOLIAN static void
+_efl_net_server_fd_family_set(Eo *o EINA_UNUSED, Efl_Net_Server_Fd_Data *pd, int family)
+{
+ pd->family = family;
+}
+
+EOLIAN static int
+_efl_net_server_fd_family_get(Eo *o EINA_UNUSED, Efl_Net_Server_Fd_Data *pd)
+{
+ return pd->family;
+}
+
+#include "efl_net_server_fd.eo.c"
--- /dev/null
+class Efl.Net.Server.Fd (Efl.Loop.Fd, Efl.Net.Server) {
+ [[A generic server based on file descriptors.
+
+ @since 1.19
+ ]]
+
+ methods {
+ @property family {
+ [[The address family (AF_*) family of this socket.
+
+ It will be one of AF_INET (IPv4), AF_INET6 (IPv6),
+ AF_UNIX...
+
+ It must be set before the @Efl.Loop.Fd.fd.set is called
+ with a valid file descriptor.
+ ]]
+ get { }
+ set @protected { }
+ values {
+ family: int;
+ }
+ }
+
+ @property close_on_exec {
+ [[Controls Close-on-Exec() using FD_CLOEXEC.
+
+ Children socket will inherit the server's setting by
+ default. One can change the behavior using each instance
+ @Efl.Net.Socket.Fd.close_on_exec.set.
+ ]]
+ get { }
+ set {
+ return: bool (false); [[$true on success]]
+ }
+ values {
+ close_on_exec: bool;
+ }
+ }
+
+ @property reuse_address {
+ [[Controls address reuse() using SO_REUSEADDR]]
+ get { }
+ set {
+ return: bool (false); [[$true on success]]
+ }
+ values {
+ reuse_address: bool;
+ }
+ }
+
+ @property reuse_port {
+ [[Controls port reuse() using SO_REUSEPORT (since linux 3.9)]]
+ get { }
+ set {
+ return: bool (false); [[$true on success]]
+ }
+ values {
+ reuse_port: bool;
+ }
+ }
+
+ client_add @protected @virtual_pure {
+ [[Accept a new client, should emit "client,add".
+
+ Remember to create the client object with a callback to
+ EFL_IO_CLOSER_EVENT_CLOSED during the construction and
+ decrease @Efl.Net.Server.clients_count as well as unref
+ the client and remove yourself as parent.
+
+ The new clients should have the server as parent and
+ increase the @Efl.Net.Server.clients_count.
+
+ Whenever this function fails, it must close the given
+ client file descriptor.
+ ]]
+ params {
+ client_fd: int; [[The file descriptor of the client socket. It comes preconfigured with close_on_exec. On failure, remember to close this socket]]
+ }
+ }
+
+ client_reject @protected @virtual_pure {
+ [[Reject a new client, should emit "client,rejected".
+
+ Must always close the client socket when it's done.
+ ]]
+ params {
+ client_fd: int; [[The file descriptor of the client socket. It comes preconfigured with close_on_exec and should be closed once it's not needed anymore]]
+ }
+ }
+ }
+
+ implements {
+ Efl.Object.finalize;
+ Efl.Object.constructor;
+ Efl.Object.destructor;
+ Efl.Loop.Fd.fd.set;
+ Efl.Net.Server.address;
+ Efl.Net.Server.clients_count;
+ Efl.Net.Server.clients_limit;
+ Efl.Net.Server.serving;
+ Efl.Net.Server.serve;
+ }
+}
--- /dev/null
+#define EFL_NET_SERVER_TCP_PROTECTED 1
+#define EFL_NET_SERVER_FD_PROTECTED 1
+#define EFL_NET_SERVER_PROTECTED 1
+#define EFL_LOOP_FD_PROTECTED 1
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "Ecore.h"
+#include "Ecore_Con.h"
+#include "ecore_con_private.h"
+
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_TCP_H
+# include <netinet/tcp.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+#ifdef HAVE_EVIL
+# include <Evil.h>
+#endif
+
+#define MY_CLASS EFL_NET_SERVER_TCP_CLASS
+
+EOLIAN static Eina_Error
+_efl_net_server_tcp_efl_net_server_serve(Eo *o, void *pd EINA_UNUSED, const char *address)
+{
+ struct sockaddr_storage addr = {};
+ char *str, *host, *port;
+ int r, fd, extra_flags = 0;
+ socklen_t addrlen;
+ char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
+ Eina_Error err = 0;
+
+ EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL);
+
+ // TODO: change to getaddrinfo() and move to a thread...
+ str = host = strdup(address);
+ EINA_SAFETY_ON_NULL_RETURN_VAL(str, ENOMEM);
+
+ if (host[0] == '[')
+ {
+ struct sockaddr_in6 *a = (struct sockaddr_in6 *)&addr;
+ /* IPv6 is: [IP]:port */
+ host++;
+ port = strchr(host, ']');
+ if (!port)
+ {
+ ERR("missing ']' in IPv6 address: %s", address);
+ err = EINVAL;
+ goto invalid_address;
+ }
+ *port = '\0';
+ port++;
+
+ if (port[0] == ':')
+ port++;
+ else
+ port = NULL;
+ a->sin6_family = AF_INET6;
+ a->sin6_port = htons(port ? atoi(port) : 0);
+ r = inet_pton(AF_INET6, host, &(a->sin6_addr));
+ addrlen = sizeof(*a);
+ }
+ else
+ {
+ struct sockaddr_in *a = (struct sockaddr_in *)&addr;
+ port = strchr(host, ':');
+ if (port)
+ {
+ *port = '\0';
+ port++;
+ }
+ a->sin_family = AF_INET;
+ a->sin_port = htons(port ? atoi(port) : 0);
+ r = inet_pton(AF_INET, host, &(a->sin_addr));
+ addrlen = sizeof(*a);
+ }
+
+ if (r != 1)
+ {
+ ERR("could not parse IP '%s' (%s)", host, address);
+ err = EINVAL;
+ goto invalid_address;
+ }
+ free(str);
+
+ efl_net_server_fd_family_set(o, addr.ss_family);
+
+ if (efl_net_ip_port_fmt(buf, sizeof(buf), (struct sockaddr *)&addr))
+ efl_net_server_address_set(o, buf);
+
+ if (efl_net_server_fd_close_on_exec_get(o))
+ extra_flags |= SOCK_CLOEXEC;
+
+ fd = socket(addr.ss_family, SOCK_STREAM | extra_flags, IPPROTO_TCP);
+ if (fd < 0)
+ {
+ err = errno;
+ ERR("socket(%d, SOCK_STREAM | %#x, IPPROTO_TCP): %s",
+ addr.ss_family, extra_flags, strerror(errno));
+ goto error_socket;
+ }
+
+ efl_loop_fd_set(o, fd);
+
+ r = bind(fd, (struct sockaddr *)&addr, addrlen);
+ if (r < 0)
+ {
+ err = errno;
+ ERR("bind(%d, %s): %s", fd, address, strerror(errno));
+ goto error_listen;
+ }
+
+ r = listen(fd, 0);
+ if (r < 0)
+ {
+ err = errno;
+ ERR("listen(%d): %s", fd, strerror(errno));
+ goto error_listen;
+ }
+
+ efl_net_server_serving_set(o, EINA_TRUE);
+ return 0;
+
+ invalid_address:
+ free(str);
+ goto error_socket;
+
+ error_listen:
+ close(fd);
+ error_socket:
+ efl_event_callback_call(o, EFL_NET_SERVER_EVENT_ERROR, &err);
+ return err;
+}
+
+static Efl_Callback_Array_Item *_efl_net_server_tcp_client_cbs(void);
+
+static void
+_efl_net_server_tcp_client_event_closed(void *data, const Eo_Event *event)
+{
+ Eo *server = data;
+ Eo *client = event->object;
+
+ efl_event_callback_array_del(client, _efl_net_server_tcp_client_cbs(), server);
+ if (efl_parent_get(client) == server)
+ efl_parent_set(client, NULL);
+
+ efl_net_server_clients_count_set(server, efl_net_server_clients_count_get(server) - 1);
+}
+
+EFL_CALLBACKS_ARRAY_DEFINE(_efl_net_server_tcp_client_cbs,
+ { EFL_IO_CLOSER_EVENT_CLOSED, _efl_net_server_tcp_client_event_closed });
+
+static void
+_efl_net_server_tcp_efl_net_server_fd_client_add(Eo *o, void *pd EINA_UNUSED, int client_fd)
+{
+ Eo *client = efl_add(EFL_NET_SOCKET_TCP_CLASS, o,
+ efl_event_callback_array_add(efl_self, _efl_net_server_tcp_client_cbs(), o),
+ efl_loop_fd_set(efl_self, client_fd));
+ if (!client)
+ {
+ ERR("could not create client object fd=%d", client_fd);
+ close(client_fd);
+ return;
+ }
+
+ efl_net_server_clients_count_set(o, efl_net_server_clients_count_get(o) + 1);
+ efl_event_callback_call(o, EFL_NET_SERVER_EVENT_CLIENT_ADD, client);
+
+ if (efl_ref_get(client) == 1) /* users must take a reference themselves */
+ {
+ DBG("client %s was not handled, closing it...",
+ efl_net_socket_address_remote_get(client));
+ efl_io_closer_close(client);
+ }
+}
+
+static void
+_efl_net_server_tcp_efl_net_server_fd_client_reject(Eo *o, void *pd EINA_UNUSED, int client_fd)
+{
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ char str[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
+
+ addrlen = sizeof(addr);
+ if (getpeername(client_fd, (struct sockaddr *)&addr, &addrlen) < 0)
+ ERR("getpeername(%d): %s", client_fd, strerror(errno));
+ else
+ efl_net_ip_port_fmt(str, sizeof(str), (struct sockaddr *)&addr);
+
+ close(client_fd);
+ efl_event_callback_call(o, EFL_NET_SERVER_EVENT_CLIENT_REJECTED, str);
+}
+
+#include "efl_net_server_tcp.eo.c"
--- /dev/null
+class Efl.Net.Server.Tcp (Efl.Net.Server.Fd) {
+ [[A TCP server.
+
+ @since 1.19
+ ]]
+
+ data: null;
+
+ implements {
+ Efl.Net.Server.serve;
+ Efl.Net.Server.Fd.client_add;
+ Efl.Net.Server.Fd.client_reject;
+ }
+}
--- /dev/null
+#define EFL_IO_READER_PROTECTED 1
+#define EFL_IO_WRITER_PROTECTED 1
+#define EFL_IO_CLOSER_PROTECTED 1
+#define EFL_NET_SOCKET_PROTECTED 1
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "Ecore.h"
+#include "Ecore_Con.h"
+#include "ecore_con_private.h"
+
+#include "efl_net_socket.eo.c"
--- /dev/null
+interface Efl.Net.Socket (Efl.Io.Reader, Efl.Io.Writer, Efl.Io.Closer) {
+ [[The basic socket interface.
+
+ It is built upon the three core Input/Output interfaces:
+ - @Efl.Io.Reader: to receive data.
+ - @Efl.Io.Writer: to send data.
+ - @Efl.Io.Closer: to close the socket for further operations.
+
+ Thus it can be used with utilities like @Efl.Io.Copier.
+
+ @since 1.19
+ ]]
+ methods {
+ @property address_local {
+ [[The local address, similar to getsockname().
+
+ The actual value depends on the type of socket, like an
+ IPv4 (ip:port) or IPv6 ([ip]:port) formatted for a
+ TCP/UDP socket, the path if an Unix Local...
+
+ It's always resolved, then if operations are working
+ with domain names or URL, this is the values the kernel
+ reports. It's similar to getsockname() in behavior.
+ ]]
+ get { }
+ set @protected { }
+ values {
+ address: string;
+ }
+ }
+
+ @property address_remote {
+ [[The remote address, similar to getpeername().
+
+ The actual value depends on the type of socket, like an
+ IPv4 (ip:port) or IPv6 ([ip]:port) formatted for a
+ TCP/UDP socket, the path if an Unix Local...
+
+ It's always resolved, then if operations are working
+ with domain names or URL, this is the values the kernel
+ reports. It's similar to getpeername() in behavior.
+ ]]
+ get { }
+ set @protected { }
+ values {
+ address: string;
+ }
+ }
+ }
+}
--- /dev/null
+#define EFL_NET_SOCKET_FD_PROTECTED 1
+#define EFL_LOOP_FD_PROTECTED 1
+#define EFL_IO_READER_FD_PROTECTED 1
+#define EFL_IO_WRITER_FD_PROTECTED 1
+#define EFL_IO_CLOSER_FD_PROTECTED 1
+#define EFL_IO_READER_PROTECTED 1
+#define EFL_IO_WRITER_PROTECTED 1
+#define EFL_IO_CLOSER_PROTECTED 1
+#define EFL_NET_SOCKET_PROTECTED 1
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "Ecore.h"
+#include "Ecore_Con.h"
+#include "ecore_con_private.h"
+
+#include <fcntl.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#ifdef HAVE_EVIL
+# include <Evil.h>
+#endif
+
+#define MY_CLASS EFL_NET_SOCKET_FD_CLASS
+
+typedef struct _Efl_Net_Socket_Fd_Data
+{
+ Eina_Stringshare *address_local;
+ Eina_Stringshare *address_remote;
+ int family;
+ Eina_Bool close_on_exec;
+} Efl_Net_Socket_Fd_Data;
+
+static void
+_efl_net_socket_fd_event_read(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ efl_io_reader_can_read_set(event->object, EINA_TRUE);
+ efl_io_reader_eos_set(event->object, EINA_FALSE);
+}
+
+static void
+_efl_net_socket_fd_event_write(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ efl_io_writer_can_write_set(event->object, EINA_TRUE);
+}
+
+static void
+_efl_net_socket_fd_event_error(void *data EINA_UNUSED, const Eo_Event *event)
+{
+ efl_io_writer_can_write_set(event->object, EINA_FALSE);
+ efl_io_reader_can_read_set(event->object, EINA_FALSE);
+ efl_io_reader_eos_set(event->object, EINA_TRUE);
+}
+
+EOLIAN static Efl_Object *
+_efl_net_socket_fd_efl_object_finalize(Eo *o, Efl_Net_Socket_Fd_Data *pd EINA_UNUSED)
+{
+ o = efl_finalize(efl_super(o, MY_CLASS));
+ if (!o) return NULL;
+
+ // TODO: only register "read" if "can_read" is being monitored?
+ // TODO: only register "write" if "can_write" is being monitored?
+ efl_event_callback_add(o, EFL_LOOP_FD_EVENT_WRITE, _efl_net_socket_fd_event_write, NULL);
+ efl_event_callback_add(o, EFL_LOOP_FD_EVENT_READ, _efl_net_socket_fd_event_read, NULL);
+ efl_event_callback_add(o, EFL_LOOP_FD_EVENT_ERROR, _efl_net_socket_fd_event_error, NULL);
+ return o;
+}
+
+EOLIAN static Efl_Object *
+_efl_net_socket_fd_efl_object_constructor(Eo *o, Efl_Net_Socket_Fd_Data *pd EINA_UNUSED)
+{
+ pd->family = AF_UNSPEC;
+ return efl_constructor(efl_super(o, MY_CLASS));
+}
+
+EOLIAN static void
+_efl_net_socket_fd_efl_object_destructor(Eo *o, Efl_Net_Socket_Fd_Data *pd)
+{
+ efl_destructor(efl_super(o, MY_CLASS));
+
+ eina_stringshare_replace(&pd->address_local, NULL);
+ eina_stringshare_replace(&pd->address_remote, NULL);
+}
+
+static void
+_efl_net_socket_fd_set(Eo *o, Efl_Net_Socket_Fd_Data *pd, int fd)
+{
+ efl_io_reader_fd_reader_fd_set(o, fd);
+ efl_io_writer_fd_writer_fd_set(o, fd);
+ efl_io_closer_fd_closer_fd_set(o, fd);
+
+ /* apply postponed values */
+ efl_net_socket_fd_close_on_exec_set(o, pd->close_on_exec);
+ if (pd->family == AF_UNSPEC)
+ {
+ ERR("efl_loop_fd_set() must be called after efl_net_server_fd_family_set()");
+ return;
+ }
+}
+
+static void
+_efl_net_socket_fd_unset(Eo *o)
+{
+ efl_io_reader_fd_reader_fd_set(o, -1);
+ efl_io_writer_fd_writer_fd_set(o, -1);
+ efl_io_closer_fd_closer_fd_set(o, -1);
+
+ efl_net_socket_address_local_set(o, NULL);
+ efl_net_socket_address_remote_set(o, NULL);
+}
+
+EOLIAN static void
+_efl_net_socket_fd_efl_loop_fd_fd_set(Eo *o, Efl_Net_Socket_Fd_Data *pd, int fd)
+{
+ if ((pd->family == AF_UNSPEC) && (fd >= 0))
+ {
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) < 0)
+ ERR("getsockname(%d): %s", fd, strerror(errno));
+ else
+ efl_net_socket_fd_family_set(o, addr.ss_family);
+ }
+
+ efl_loop_fd_set(efl_super(o, MY_CLASS), fd);
+
+ if (fd >= 0) _efl_net_socket_fd_set(o, pd, fd);
+ else _efl_net_socket_fd_unset(o);
+}
+
+EOLIAN static Eina_Error
+_efl_net_socket_fd_efl_io_closer_close(Eo *o, Efl_Net_Socket_Fd_Data *pd EINA_UNUSED)
+{
+ Eina_Error ret;
+
+ efl_io_writer_can_write_set(o, EINA_FALSE);
+ efl_io_reader_can_read_set(o, EINA_FALSE);
+ efl_io_reader_eos_set(o, EINA_TRUE);
+
+ /* skip _efl_net_socket_fd_efl_loop_fd_fd_set() since we want to
+ * retain efl_io_closer_fd_closer_fd_get() so close(super()) works
+ * and we emit the events with proper addresses.
+ */
+ efl_loop_fd_set(efl_super(o, MY_CLASS), -1);
+
+ ret = efl_io_closer_close(efl_super(o, MY_CLASS));
+
+ /* do the cleanup our _efl_net_socket_fd_efl_loop_fd_fd_set() would do */
+ _efl_net_socket_fd_unset(o);
+
+ return ret;
+}
+
+EOLIAN static Eina_Error
+_efl_net_socket_fd_efl_io_reader_read(Eo *o, Efl_Net_Socket_Fd_Data *pd EINA_UNUSED, Eina_Rw_Slice *rw_slice)
+{
+ Eina_Error ret;
+
+ ret = efl_io_reader_read(efl_super(o, MY_CLASS), rw_slice);
+ if (rw_slice && rw_slice->len > 0)
+ efl_io_reader_can_read_set(o, EINA_FALSE); /* wait Efl.Loop.Fd "read" */
+
+ return ret;
+}
+
+EOLIAN static Eina_Error
+_efl_net_socket_fd_efl_io_writer_write(Eo *o, Efl_Net_Socket_Fd_Data *pd EINA_UNUSED, Eina_Slice *ro_slice, Eina_Slice *remaining)
+{
+ Eina_Error ret;
+
+ ret = efl_io_writer_write(efl_super(o, MY_CLASS), ro_slice, remaining);
+ if (ro_slice && ro_slice->len > 0)
+ efl_io_writer_can_write_set(o, EINA_FALSE); /* wait Efl.Loop.Fd "write" */
+
+ return ret;
+}
+
+EOLIAN static void
+_efl_net_socket_fd_efl_net_socket_address_local_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Fd_Data *pd, const char *address)
+{
+ eina_stringshare_replace(&pd->address_local, address);
+}
+
+EOLIAN static const char *
+_efl_net_socket_fd_efl_net_socket_address_local_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Fd_Data *pd)
+{
+ return pd->address_local;
+}
+
+EOLIAN static void
+_efl_net_socket_fd_efl_net_socket_address_remote_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Fd_Data *pd, const char *address)
+{
+ eina_stringshare_replace(&pd->address_remote, address);
+}
+
+EOLIAN static const char *
+_efl_net_socket_fd_efl_net_socket_address_remote_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Fd_Data *pd)
+{
+ return pd->address_remote;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_socket_fd_close_on_exec_set(Eo *o, Efl_Net_Socket_Fd_Data *pd, Eina_Bool close_on_exec)
+{
+ int flags, fd;
+
+ pd->close_on_exec = close_on_exec;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return EINA_TRUE; /* postpone until fd_set() */
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0)
+ {
+ ERR("fcntl(%d, F_GETFD): %s", fd, strerror(errno));
+ return EINA_FALSE;
+ }
+ if (close_on_exec)
+ flags |= FD_CLOEXEC;
+ else
+ flags &= (~FD_CLOEXEC);
+ if (fcntl(fd, F_SETFD, flags) < 0)
+ {
+ ERR("fcntl(%d, F_SETFD, %#x): %s", fd, flags, strerror(errno));
+ return EINA_FALSE;
+ }
+
+ return EINA_TRUE;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_socket_fd_close_on_exec_get(Eo *o, Efl_Net_Socket_Fd_Data *pd)
+{
+ int flags, fd;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return pd->close_on_exec;
+
+ /* if there is a fd, always query it directly as it may be modified
+ * elsewhere by nasty users.
+ */
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0)
+ {
+ ERR("fcntl(%d, F_GETFD): %s", fd, strerror(errno));
+ return EINA_FALSE;
+ }
+
+ pd->close_on_exec = !!(flags & FD_CLOEXEC); /* sync */
+ return pd->close_on_exec;
+}
+
+EOLIAN static void
+_efl_net_socket_fd_family_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Fd_Data *pd, int family)
+{
+ pd->family = family;
+}
+
+EOLIAN static int
+_efl_net_socket_fd_family_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Fd_Data *pd)
+{
+ return pd->family;
+}
+
+#include "efl_net_socket_fd.eo.c"
--- /dev/null
+class Efl.Net.Socket.Fd (Efl.Loop.Fd, Efl.Io.Reader.Fd, Efl.Io.Writer.Fd, Efl.Io.Closer.Fd, Efl.Net.Socket) {
+ [[A base implementation for sockets over filedescriptors (fd)
+
+ This is the common class and takes an existing FD, usually
+ created by an dialer or server.
+
+ @since 1.19
+ ]]
+
+ methods {
+ @property family {
+ [[The address family (AF_*) family of this socket.
+
+ It will be one of AF_INET (IPv4), AF_INET6 (IPv6),
+ AF_UNIX...
+
+ It must be set before the @Efl.Loop.Fd.fd.set is called
+ with a valid file descriptor.
+ ]]
+ get { }
+ set @protected { }
+ values {
+ family: int;
+ }
+ }
+
+ @property close_on_exec {
+ [[Controls Close-on-Exec() using FD_CLOEXEC]]
+ get { }
+ set {
+ return: bool (false); [[$true on success]]
+ }
+ values {
+ close_on_exec: bool;
+ }
+ }
+ }
+
+ implements {
+ Efl.Object.finalize;
+ Efl.Object.constructor;
+ Efl.Object.destructor;
+ Efl.Loop.Fd.fd.set;
+ Efl.Io.Closer.close;
+ Efl.Io.Reader.read;
+ Efl.Io.Writer.write;
+ Efl.Net.Socket.address_local;
+ Efl.Net.Socket.address_remote;
+ }
+}
--- /dev/null
+#define EFL_NET_SOCKET_TCP_PROTECTED 1
+#define EFL_NET_SOCKET_FD_PROTECTED 1
+#define EFL_LOOP_FD_PROTECTED 1
+#define EFL_IO_READER_FD_PROTECTED 1
+#define EFL_IO_WRITER_FD_PROTECTED 1
+#define EFL_IO_CLOSER_FD_PROTECTED 1
+#define EFL_IO_READER_PROTECTED 1
+#define EFL_IO_WRITER_PROTECTED 1
+#define EFL_IO_CLOSER_PROTECTED 1
+#define EFL_NET_SOCKET_PROTECTED 1
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "Ecore.h"
+#include "Ecore_Con.h"
+#include "ecore_con_private.h"
+
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_TCP_H
+# include <netinet/tcp.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+#ifdef HAVE_EVIL
+# include <Evil.h>
+#endif
+
+#define MY_CLASS EFL_NET_SOCKET_TCP_CLASS
+
+typedef struct _Efl_Net_Socket_Tcp_Data
+{
+ Eina_Bool keep_alive;
+ Eina_Bool no_delay;
+ Eina_Bool cork;
+} Efl_Net_Socket_Tcp_Data;
+
+EOLIAN static void
+_efl_net_socket_tcp_efl_loop_fd_fd_set(Eo *o, Efl_Net_Socket_Tcp_Data *pd EINA_UNUSED, int fd)
+{
+ efl_loop_fd_set(efl_super(o, MY_CLASS), fd);
+
+ if (fd >= 0)
+ {
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ int family;
+
+ /* apply postponed values */
+ efl_net_socket_tcp_keep_alive_set(o, pd->keep_alive);
+ efl_net_socket_tcp_no_delay_set(o, pd->no_delay);
+ efl_net_socket_tcp_cork_set(o, pd->cork);
+
+ family = efl_net_socket_fd_family_get(o);
+ if (family == AF_UNSPEC) return;
+
+ addrlen = sizeof(addr);
+ if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) < 0)
+ ERR("getsockname(%d): %s", fd, strerror(errno));
+ else
+ {
+ char str[INET6_ADDRSTRLEN + sizeof("[]:65536")];
+ if (efl_net_ip_port_fmt(str, sizeof(str), (struct sockaddr *)&addr))
+ efl_net_socket_address_local_set(o, str);
+ }
+
+ addrlen = sizeof(addr);
+ if (getpeername(fd, (struct sockaddr *)&addr, &addrlen) < 0)
+ ERR("getpeername(%d): %s", fd, strerror(errno));
+ else
+ {
+ char str[INET6_ADDRSTRLEN + sizeof("[]:65536")];
+ if (efl_net_ip_port_fmt(str, sizeof(str), (struct sockaddr *)&addr))
+ efl_net_socket_address_remote_set(o, str);
+ }
+ }
+}
+
+EOLIAN static Eina_Bool
+_efl_net_socket_tcp_keep_alive_set(Eo *o, Efl_Net_Socket_Tcp_Data *pd, Eina_Bool keep_alive)
+{
+ int value, fd;
+ Eina_Bool old = pd->keep_alive;
+
+ pd->keep_alive = keep_alive;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return EINA_TRUE; /* postpone until fd_set() */
+
+ value = keep_alive;
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &value, sizeof(value)) < 0)
+ {
+ ERR("setsockopt(%d, SOL_SOCKET, SO_KEEPALIVE, %d): %s",
+ fd, value, strerror(errno));
+ pd->keep_alive = old;
+ return EINA_FALSE;
+ }
+
+ return EINA_TRUE;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_socket_tcp_keep_alive_get(Eo *o, Efl_Net_Socket_Tcp_Data *pd)
+{
+ int value = 0, fd;
+ socklen_t valuelen;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return pd->keep_alive;
+
+ /* if there is a fd, always query it directly as it may be modified
+ * elsewhere by nasty users.
+ */
+ valuelen = sizeof(value);
+ if (getsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &value, &valuelen) < 0)
+ {
+ ERR("getsockopt(%d, SOL_SOCKET, SO_KEEPALIVE): %s",
+ fd, strerror(errno));
+ return EINA_FALSE;
+ }
+
+ pd->keep_alive = !!value; /* sync */
+ return pd->keep_alive;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_socket_tcp_no_delay_set(Eo *o, Efl_Net_Socket_Tcp_Data *pd, Eina_Bool no_delay)
+{
+ int value, fd;
+ Eina_Bool old = pd->no_delay;
+
+ pd->no_delay = no_delay;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return EINA_TRUE; /* postpone until fd_set() */
+
+ value = no_delay;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value)) < 0)
+ {
+ ERR("setsockopt(%d, IPPROTO_TCP, TCP_NODELAY, %d): %s",
+ fd, value, strerror(errno));
+ pd->no_delay = old;
+ return EINA_FALSE;
+ }
+
+ return EINA_TRUE;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_socket_tcp_no_delay_get(Eo *o, Efl_Net_Socket_Tcp_Data *pd)
+{
+ int value = 0, fd;
+ socklen_t valuelen;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return pd->no_delay;
+
+ /* if there is a fd, always query it directly as it may be modified
+ * elsewhere by nasty users.
+ */
+ valuelen = sizeof(value);
+ if (getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &value, &valuelen) < 0)
+ {
+ ERR("getsockopt(%d, IPPROTO_TCP, TCP_NODELAY): %s",
+ fd, strerror(errno));
+ return EINA_FALSE;
+ }
+
+ pd->no_delay = !!value; /* sync */
+ return pd->no_delay;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_socket_tcp_cork_set(Eo *o, Efl_Net_Socket_Tcp_Data *pd, Eina_Bool cork)
+{
+ int value, fd;
+ Eina_Bool old = pd->cork;
+
+ pd->cork = cork;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return EINA_TRUE; /* postpone until fd_set() */
+
+ value = cork;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_CORK, &value, sizeof(value)) < 0)
+ {
+ ERR("setsockopt(%d, IPPROTO_TCP, TCP_CORK, %d): %s",
+ fd, value, strerror(errno));
+ pd->cork = old;
+ return EINA_FALSE;
+ }
+
+ return EINA_TRUE;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_socket_tcp_cork_get(Eo *o, Efl_Net_Socket_Tcp_Data *pd)
+{
+ int value = 0, fd;
+ socklen_t valuelen;
+
+ fd = efl_loop_fd_get(o);
+ if (fd < 0) return pd->cork;
+
+ /* if there is a fd, always query it directly as it may be modified
+ * elsewhere by nasty users.
+ */
+ valuelen = sizeof(value);
+ if (getsockopt(fd, IPPROTO_TCP, TCP_CORK, &value, &valuelen) < 0)
+ {
+ ERR("getsockopt(%d, IPPROTO_TCP, TCP_CORK): %s",
+ fd, strerror(errno));
+ return EINA_FALSE;
+ }
+
+ pd->cork = !!value; /* sync */
+ return pd->cork;
+}
+
+#include "efl_net_socket_tcp.eo.c"
--- /dev/null
+class Efl.Net.Socket.Tcp (Efl.Net.Socket.Fd) {
+ [[A base TCP socket.
+
+ This is the common class and takes an existing FD, usually
+ created by an dialer or server.
+
+ @since 1.19
+ ]]
+
+ methods {
+ @property keep_alive {
+ [[Controls keep-alive using SO_KEEPALIVE]]
+ get { }
+ set {
+ return: bool (false); [[$true on success]]
+ }
+ values {
+ keep_alive: bool;
+ }
+ }
+
+ @property no_delay {
+ [[Controls TCP's no-delay using TCP_NODELAY]]
+ get { }
+ set {
+ return: bool (false); [[$true on success]]
+ }
+ values {
+ no_delay: bool;
+ }
+ }
+
+ @property cork {
+ [[Controls TCP's cork using TCP_CORK]]
+ get { }
+ set {
+ return: bool (false); [[$true on success]]
+ }
+ values {
+ cork: bool;
+ }
+ }
+ }
+
+ implements {
+ Efl.Loop.Fd.fd.set;
+ }
+}