summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGustavo Sverzut Barbieri <barbieri@profusion.mobi>2016-08-18 01:53:16 -0300
committerGustavo Sverzut Barbieri <barbieri@profusion.mobi>2016-08-22 18:25:14 -0300
commite7df1a74830082904695f48b29ff41dff30f664a (patch)
tree0ca7be29edc40a6ff8b6ffb518e5a649c60a9f7c
parent5d4688679e9d3f9daa00ec53f0302f89d36028bf (diff)
downloadefl-e7df1a74830082904695f48b29ff41dff30f664a.tar.gz
efl.net: socket, server and dialer for TCP.
Efl.Net.Server defines how to accept new connections, doing the bind(), listen() and accept() for protocols such as TCP. Efl.Net.Dialer defines to to reach a server. Both are based on Efl.Net.Socket as communication interface that is based on Efl.Io.Reader, Efl.Io.Writer and Efl.Io.Closer, thus being usable with code such as Efl.Io.Copier. The Server will emit an event "client,add" with the established Socket, which is a child and can be closed by both the server or the user. The Dialer extends the Socket and allows for creating one given an address, that will be resolved and connected. TCP is the initial implementation so we an validate the interfaces. UDP, Unix-Local and SSL will come later as derivate classes. The examples are documented and should cover the basic principles: - efl_io_copier_example can accept "tcp://IP:PORT" and will work as a "netcat", can send data from socket, file or stdin to a socket, file, stdout or stderr. - efl_net_server_example listens for connections and can either reply "Hello World!" and take some data or work as an echo-server, looping back all received data to the user. More complex interactions that require a "chat" between client and server will be covered with new classes later, such as a queue that empties itself once data is read.
-rw-r--r--configure.ac1
-rw-r--r--src/Makefile_Ecore_Con.am18
-rw-r--r--src/examples/ecore/.gitignore1
-rw-r--r--src/examples/ecore/Makefile.am6
-rw-r--r--src/examples/ecore/efl_io_copier_example.c93
-rw-r--r--src/examples/ecore/efl_net_server_example.c544
-rw-r--r--src/lib/ecore_con/Ecore_Con_Eo.h11
-rw-r--r--src/lib/ecore_con/ecore_con.c52
-rw-r--r--src/lib/ecore_con/ecore_con_private.h2
-rw-r--r--src/lib/ecore_con/efl_net_dialer.c12
-rw-r--r--src/lib/ecore_con/efl_net_dialer.eo93
-rw-r--r--src/lib/ecore_con/efl_net_dialer_tcp.c194
-rw-r--r--src/lib/ecore_con/efl_net_dialer_tcp.eo15
-rw-r--r--src/lib/ecore_con/efl_net_server.c11
-rw-r--r--src/lib/ecore_con/efl_net_server.eo113
-rw-r--r--src/lib/ecore_con/efl_net_server_fd.c368
-rw-r--r--src/lib/ecore_con/efl_net_server_fd.eo103
-rw-r--r--src/lib/ecore_con/efl_net_server_tcp.c203
-rw-r--r--src/lib/ecore_con/efl_net_server_tcp.eo14
-rw-r--r--src/lib/ecore_con/efl_net_socket.c14
-rw-r--r--src/lib/ecore_con/efl_net_socket.eo50
-rw-r--r--src/lib/ecore_con/efl_net_socket_fd.c268
-rw-r--r--src/lib/ecore_con/efl_net_socket_fd.eo50
-rw-r--r--src/lib/ecore_con/efl_net_socket_tcp.c227
-rw-r--r--src/lib/ecore_con/efl_net_socket_tcp.eo48
25 files changed, 2508 insertions, 3 deletions
diff --git a/configure.ac b/configure.ac
index a6414c6b4f..b08054e085 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3098,6 +3098,7 @@ AM_CONDITIONAL([HAVE_IPV6], [test "x${have_ipv6}" = "xyes"])
### Checks for linker characteristics
### Checks for library functions
+AC_CHECK_FUNCS([accept4])
EFL_LIB_END([Ecore_Con])
#### End of Ecore_Con
diff --git a/src/Makefile_Ecore_Con.am b/src/Makefile_Ecore_Con.am
index a478dc2298..ec98fd31dd 100644
--- a/src/Makefile_Ecore_Con.am
+++ b/src/Makefile_Ecore_Con.am
@@ -6,6 +6,14 @@ ecore_con_eolian_files = \
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 \
@@ -52,7 +60,15 @@ lib/ecore_con/ecore_con_url_curl.h \
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
diff --git a/src/examples/ecore/.gitignore b/src/examples/ecore/.gitignore
index bdf185c2f2..31e2bc0613 100644
--- a/src/examples/ecore/.gitignore
+++ b/src/examples/ecore/.gitignore
@@ -47,3 +47,4 @@
/ecore_buffer_consumer_example
/ecore_buffer_provider_example
/efl_io_copier_example
+/efl_net_server_example
diff --git a/src/examples/ecore/Makefile.am b/src/examples/ecore/Makefile.am
index 7f20909c02..17d6d2bd92 100644
--- a/src/examples/ecore/Makefile.am
+++ b/src/examples/ecore/Makefile.am
@@ -77,7 +77,8 @@ ecore_timer_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 \
@@ -281,6 +282,9 @@ $(top_builddir)/src/lib/eet/libeet.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 \
diff --git a/src/examples/ecore/efl_io_copier_example.c b/src/examples/ecore/efl_io_copier_example.c
index 5af644ee64..be5352bff5 100644
--- a/src/examples/ecore/efl_io_copier_example.c
+++ b/src/examples/ecore/efl_io_copier_example.c
@@ -1,6 +1,7 @@
#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>
@@ -58,6 +59,36 @@ EFL_CALLBACKS_ARRAY_DEFINE(output_buffer_cbs,
{ 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"
*/
@@ -240,7 +271,10 @@ static const Ecore_Getopt options = {
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"
@@ -248,6 +282,7 @@ static const Ecore_Getopt options = {
":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
@@ -285,6 +320,7 @@ main(int argc, char **argv)
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)
@@ -323,6 +359,33 @@ main(int argc, char **argv)
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 */
@@ -403,6 +466,33 @@ main(int argc, char **argv)
*/
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,
@@ -470,6 +560,7 @@ main(int argc, char **argv)
input = NULL;
end:
+ ecore_con_shutdown();
ecore_shutdown();
return retval;
diff --git a/src/examples/ecore/efl_net_server_example.c b/src/examples/ecore/efl_net_server_example.c
new file mode 100644
index 0000000000..92e3cd8098
--- /dev/null
+++ b/src/examples/ecore/efl_net_server_example.c
@@ -0,0 +1,544 @@
+#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;
+}
diff --git a/src/lib/ecore_con/Ecore_Con_Eo.h b/src/lib/ecore_con/Ecore_Con_Eo.h
index cb2903b5f1..c185aea437 100644
--- a/src/lib/ecore_con/Ecore_Con_Eo.h
+++ b/src/lib/ecore_con/Ecore_Con_Eo.h
@@ -3,3 +3,14 @@
#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"
diff --git a/src/lib/ecore_con/ecore_con.c b/src/lib/ecore_con/ecore_con.c
index 9d304ac4b9..e040a71a75 100644
--- a/src/lib/ecore_con/ecore_con.c
+++ b/src/lib/ecore_con/ecore_con.c
@@ -2984,3 +2984,55 @@ _ecore_con_lookup_done(void *data,
#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;
+}
diff --git a/src/lib/ecore_con/ecore_con_private.h b/src/lib/ecore_con/ecore_con_private.h
index 331e4926ba..089f864ca5 100644
--- a/src/lib/ecore_con/ecore_con_private.h
+++ b/src/lib/ecore_con/ecore_con_private.h
@@ -373,4 +373,6 @@ void ecore_con_mempool_shutdown(void);
#undef GENERIC_ALLOC_FREE_HEADER
+Eina_Bool efl_net_ip_port_fmt(char *buf, int buflen, const struct sockaddr *addr);
+
#endif
diff --git a/src/lib/ecore_con/efl_net_dialer.c b/src/lib/ecore_con/efl_net_dialer.c
new file mode 100644
index 0000000000..493e8bc0cb
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_dialer.c
@@ -0,0 +1,12 @@
+#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"
diff --git a/src/lib/ecore_con/efl_net_dialer.eo b/src/lib/ecore_con/efl_net_dialer.eo
new file mode 100644
index 0000000000..a2feaa1783
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_dialer.eo
@@ -0,0 +1,93 @@
+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.]]
+ }
+}
diff --git a/src/lib/ecore_con/efl_net_dialer_tcp.c b/src/lib/ecore_con/efl_net_dialer_tcp.c
new file mode 100644
index 0000000000..97504c5e32
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_dialer_tcp.c
@@ -0,0 +1,194 @@
+#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"
diff --git a/src/lib/ecore_con/efl_net_dialer_tcp.eo b/src/lib/ecore_con/efl_net_dialer_tcp.eo
new file mode 100644
index 0000000000..5b02c68c6d
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_dialer_tcp.eo
@@ -0,0 +1,15 @@
+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;
+ }
+}
diff --git a/src/lib/ecore_con/efl_net_server.c b/src/lib/ecore_con/efl_net_server.c
new file mode 100644
index 0000000000..dffb078f77
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_server.c
@@ -0,0 +1,11 @@
+#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"
diff --git a/src/lib/ecore_con/efl_net_server.eo b/src/lib/ecore_con/efl_net_server.eo
new file mode 100644
index 0000000000..da6584126a
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_server.eo
@@ -0,0 +1,113 @@
+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;
+ }
+ }
+ }
+}
diff --git a/src/lib/ecore_con/efl_net_server_fd.c b/src/lib/ecore_con/efl_net_server_fd.c
new file mode 100644
index 0000000000..06af641fb7
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_server_fd.c
@@ -0,0 +1,368 @@
+#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"
diff --git a/src/lib/ecore_con/efl_net_server_fd.eo b/src/lib/ecore_con/efl_net_server_fd.eo
new file mode 100644
index 0000000000..d469e3eba6
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_server_fd.eo
@@ -0,0 +1,103 @@
+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;
+ }
+}
diff --git a/src/lib/ecore_con/efl_net_server_tcp.c b/src/lib/ecore_con/efl_net_server_tcp.c
new file mode 100644
index 0000000000..25bfb01aef
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_server_tcp.c
@@ -0,0 +1,203 @@
+#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"
diff --git a/src/lib/ecore_con/efl_net_server_tcp.eo b/src/lib/ecore_con/efl_net_server_tcp.eo
new file mode 100644
index 0000000000..443e5bc400
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_server_tcp.eo
@@ -0,0 +1,14 @@
+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;
+ }
+}
diff --git a/src/lib/ecore_con/efl_net_socket.c b/src/lib/ecore_con/efl_net_socket.c
new file mode 100644
index 0000000000..1dc2563292
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_socket.c
@@ -0,0 +1,14 @@
+#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"
diff --git a/src/lib/ecore_con/efl_net_socket.eo b/src/lib/ecore_con/efl_net_socket.eo
new file mode 100644
index 0000000000..fdd29edcba
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_socket.eo
@@ -0,0 +1,50 @@
+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;
+ }
+ }
+ }
+}
diff --git a/src/lib/ecore_con/efl_net_socket_fd.c b/src/lib/ecore_con/efl_net_socket_fd.c
new file mode 100644
index 0000000000..e963d08c49
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_socket_fd.c
@@ -0,0 +1,268 @@
+#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"
diff --git a/src/lib/ecore_con/efl_net_socket_fd.eo b/src/lib/ecore_con/efl_net_socket_fd.eo
new file mode 100644
index 0000000000..4ce2bfa54d
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_socket_fd.eo
@@ -0,0 +1,50 @@
+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;
+ }
+}
diff --git a/src/lib/ecore_con/efl_net_socket_tcp.c b/src/lib/ecore_con/efl_net_socket_tcp.c
new file mode 100644
index 0000000000..b620fe1784
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_socket_tcp.c
@@ -0,0 +1,227 @@
+#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"
diff --git a/src/lib/ecore_con/efl_net_socket_tcp.eo b/src/lib/ecore_con/efl_net_socket_tcp.eo
new file mode 100644
index 0000000000..3e6fb2955b
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_socket_tcp.eo
@@ -0,0 +1,48 @@
+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;
+ }
+}