diff options
author | Gustavo Sverzut Barbieri <barbieri@profusion.mobi> | 2016-08-05 00:05:01 -0300 |
---|---|---|
committer | Gustavo Sverzut Barbieri <barbieri@profusion.mobi> | 2016-08-05 00:21:40 -0300 |
commit | 35bab5c64a6c928121202697221ecebf4b606659 (patch) | |
tree | e90001c5e61c8693e5ec6b24a7691153fad39b78 | |
parent | fd8326fc52c8ef45eb8c7a5cdc51f7b1135dcfd2 (diff) | |
download | efl-devs/barbieri/ecore-con-eoify.tar.gz |
WIP: add efl_net eo API.devs/barbieri/ecore-con-eoify
<< THIS IS A WORK IN PROGRESS, DO NOT MERGE >>
The API is composed of the following class structure:
- Efl.Net.Socket: common connection, able to send/receive data
- Efl.Net.Socket.UDP
- Efl.Net.Dialer.UDP
- Efl.Net.Socket.TCP
- Efl.Net.Dialer.TCP
- Efl.Net.Socket.Unix
- Efl.Net.Dialer.Unix
- Efl.Net.Dialer (mixin): creates a connection to a server (client)
- Efl.Net.Server: creates a server to listen for connections
- Efl.Net.Server.UDP
- Efl.Net.Server.TCP
- Efl.Net.Server.Unix
-rw-r--r-- | src/Makefile_Ecore_Con.am | 22 | ||||
-rw-r--r-- | src/examples/ecore/efl_net_server_tcp.c | 277 | ||||
-rw-r--r-- | src/lib/ecore_con/Ecore_Con.h | 30 | ||||
-rw-r--r-- | src/lib/ecore_con/Ecore_Con_Eo.h | 16 | ||||
-rw-r--r-- | src/lib/ecore_con/ecore_con-eoify-analisys.md | 486 | ||||
-rw-r--r-- | src/lib/ecore_con/ecore_con.c | 470 | ||||
-rw-r--r-- | src/lib/ecore_con/ecore_con_private.h | 38 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_dialer.eo | 39 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_dialer_tcp.eo | 3 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_dialer_udp.eo | 3 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_dialer_unix.eo | 5 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_server.eo | 114 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_server_tcp.eo | 3 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_server_udp.eo | 3 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_server_unix.eo | 5 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_socket.eo | 286 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_socket_tcp.eo | 3 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_socket_udp.eo | 11 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_socket_unix.eo | 24 | ||||
-rw-r--r-- | src/lib/ecore_con/efl_net_types.eot | 19 |
20 files changed, 1855 insertions, 2 deletions
diff --git a/src/Makefile_Ecore_Con.am b/src/Makefile_Ecore_Con.am index a478dc2298..3d5f19c391 100644 --- a/src/Makefile_Ecore_Con.am +++ b/src/Makefile_Ecore_Con.am @@ -9,10 +9,27 @@ ecore_con_eolian_files = \ 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 \ - lib/ecore_con/efl_network_url.eo + lib/ecore_con/efl_network_url.eo \ + lib/ecore_con/efl_net_dialer.eo \ + lib/ecore_con/efl_net_dialer_tcp.eo \ + lib/ecore_con/efl_net_dialer_udp.eo \ + lib/ecore_con/efl_net_dialer_unix.eo \ + lib/ecore_con/efl_net_server.eo \ + lib/ecore_con/efl_net_server_tcp.eo \ + lib/ecore_con/efl_net_server_udp.eo \ + lib/ecore_con/efl_net_server_unix.eo \ + lib/ecore_con/efl_net_socket.eo \ + lib/ecore_con/efl_net_socket_tcp.eo \ + lib/ecore_con/efl_net_socket_udp.eo \ + lib/ecore_con/efl_net_socket_unix.eo + +ecore_con_eolian_type_files = \ + lib/ecore_con/efl_net_types.eot + ecore_con_eolian_c = $(ecore_con_eolian_files:%.eo=%.eo.c) ecore_con_eolian_h = $(ecore_con_eolian_files:%.eo=%.eo.h) \ + $(ecore_con_eolian_type_files:%.eot=%.eot.h) \ $(ecore_con_eolian_files:%.eo=%.eo.legacy.h) BUILT_SOURCES += \ @@ -21,7 +38,8 @@ BUILT_SOURCES += \ ecoreconeolianfilesdir = $(datadir)/eolian/include/ecore-@VMAJ@ ecoreconeolianfiles_DATA = \ - $(ecore_con_eolian_files) + $(ecore_con_eolian_files) \ + $(ecore_con_eolian_type_files) EXTRA_DIST2 += \ ${ecoreconeolianfiles_DATA} diff --git a/src/examples/ecore/efl_net_server_tcp.c b/src/examples/ecore/efl_net_server_tcp.c new file mode 100644 index 0000000000..828bd5dfd0 --- /dev/null +++ b/src/examples/ecore/efl_net_server_tcp.c @@ -0,0 +1,277 @@ +#include <Ecore_Getopt.h> +#include <Ecore_Con.h> +#include <Eina.h> +#include <Ecore.h> + +static Eina_List *clients = NULL; + +static void +_client_del(Efl_Net_Socket *client) +{ + clients = eina_list_remove(clients, client); + eo_del(client); +} + +static void +_cb_client_received(void *data EINA_UNUSED, const Eo_Event *event) +{ + Efl_Net_Socket *client = event->object; + Efl_Net_Socket_Receive_Event *ev = event->info; + Eina_Error error; + + printf("received from %p, %s->%s: %zd[%.*s]\n", client, + efl_net_socket_address_local_get(client), + efl_net_socket_address_remote_get(client), + ev->size, (int)ev->size, (const char *)ev->data); + + error = efl_net_socket_send(client, ev->data, ev->size); + if (error) { + fprintf(stderr, "ERROR: could not send data to client %p, %s->%s: %s\n", + client, efl_net_socket_address_local_get(client), + efl_net_socket_address_remote_get(client), + eina_error_msg_get(error)); + _client_del(client); + } +} + +static void +_cb_client_sent(void *data EINA_UNUSED, const Eo_Event *event) +{ + Efl_Net_Socket *client = event->object; + Efl_Net_Socket_Sent_Event *ev = event->info; + + if (ev->error) { + fprintf(stderr, "ERROR: could not send data to client %p, %s->%s: %s, did %zd bytes\n", + client, efl_net_socket_address_local_get(client), + efl_net_socket_address_remote_get(client), + eina_error_msg_get(error), ev->size); + _client_del(client); + return; + } + + printf("sent from %p, %s->%s: %zd\n", client, + efl_net_socket_address_local_get(client), + efl_net_socket_address_remote_get(client), + ev->size); +} + +static void +_cb_client_drained(void *data EINA_UNUSED, const Eo_Event *event) +{ + Efl_Net_Socket *client = event->object; + + printf("drained on %p, %s->%s\n", client, + efl_net_socket_address_local_get(client), + efl_net_socket_address_remote_get(client)); +} + +static void +_cb_client_closed(void *data EINA_UNUSED, const Eo_Event *event) +{ + Efl_Net_Socket *client = event->object; + + printf("closed on %p, %s->%s\n", client, + efl_net_socket_address_local_get(client), + efl_net_socket_address_remote_get(client)); +} + +static void +_cb_client_timedout(void *data EINA_UNUSED, const Eo_Event *event) +{ + Efl_Net_Socket *client = event->object; + + printf("timedout on %p, %s->%s\n", client, + efl_net_socket_address_local_get(client), + efl_net_socket_address_remote_get(client)); +} + +static void +_cb_client_error(void *data EINA_UNUSED, const Eo_Event *event) +{ + Efl_Net_Socket *client = event->object; + Eina_Error error = *(const Eina_Error *)client->info; + + fprintf(stderr, "ERROR: %p, %s->%s: %s\n", client, + efl_net_socket_address_local_get(client), + efl_net_socket_address_remote_get(client), + eina_error_msg_get(error)); + _client_del(client); +} + +static void +_cb_client_add(void *data EINA_UNUSED, const Eo_Event *event) +{ + Efl_Net_Socket *client = event->info; + + printf("client,add: %p, %s->%s\n", client, + efl_net_socket_address_local_get(client), + efl_net_socket_address_remote_get(client)); + + // TODO: change to eo_event_callback_array_add() + eo_event_callback_add(client, EFL_NET_SOCKET_EVENT_RECEIVED, + _cb_client_received, NULL); + eo_event_callback_add(client, EFL_NET_SOCKET_EVENT_SENT, + _cb_client_sent, NULL); + eo_event_callback_add(client, EFL_NET_SOCKET_EVENT_DRAINED, + _cb_client_drained, NULL); + eo_event_callback_add(client, EFL_NET_SOCKET_EVENT_CLOSED, + _cb_client_closed, NULL); + eo_event_callback_add(client, EFL_NET_SOCKET_EVENT_TIMEDOUT, + _cb_client_timedout, NULL); + eo_event_callback_add(client, EFL_NET_SOCKET_EVENT_ERROR, + _cb_client_error, NULL); + + clients = eina_list_append(clients, client); +} + +static void +_cb_client_rejected(void *data EINA_UNUSED, const Eo_Event *event) +{ + const char *address = event->info; + + printf("client,rejected: %s\n", address); +} + +static void +_cb_error(void *data EINA_UNUSED, const Eo_Event *event) +{ + Eina_Error error = *(const Eina_Error *)event->info; + + fprintf(stderr, "ERROR: server error: %s\n", eina_error_msg_get(error)); + ecore_main_loop_quit(); +} + +static Eina_Bool +_cb_stdin(void *data EINA_UNUSED, Ecore_Fd_Handler *fdh) +{ + ssize_t r; + size_t len = 0; + char *buf = NULL; + Eina_List *itr, *itr_next; + Efl_Net_Socket *client; + + r = getline(&buf, &len, stdin); + if (r < 0) { + perror("ERROR: could not read from stdin"); + ecore_main_loop_quit(); + return; + } + + EINA_LIST_FOREACH_SAFE(clients, itr, itr_next, client) { + Eina_Error error = efl_net_socket_send(client, buf, len); + if (error) { + fprintf(stderr, + "ERROR: could not send data to client %p, %s->%s: %s\n", + client, efl_net_socket_address_local_get(client), + efl_net_socket_address_remote_get(client), + eina_error_msg_get(error)); + _client_del(client); + } + } + + free(buf); +} + +static const Ecore_Getopt options = { + "efl_net_server_tcp", + NULL /* default usage line */, + "1" /* version */, + "(C) 2016 Enlightenment Project", + "BSD 2-Clause", + "Example of Efl_Net_Server_TCP usage.\n" + EINA_TRUE /* strict parsing */, + { + ECORE_GETOPT_STORE_UINT('f', "flags", "Efl.Net.Socket.Flags to use"), + ECORE_GETOPT_STORE_DOUBLE('t', "client-timeout", "Timeout in seconds for newly accepted clients."), + ECORE_GETOPT_STORE_UINT('l', "client-limit", "Maximum number of clients."), + ECORE_GETOPT_STORE_BOOL('r', "reject-excess-clients", "Automatically reject clients going after client-limit."), + + /* standard block to provide version, copyright, license and help */ + ECORE_GETOPT_VERSION('V', "version"), + ECORE_GETOPT_COPYRIGHT('C', "copyright"), + ECORE_GETOPT_LICENSE('L', "license"), + ECORE_GETOPT_HELP('h', "help"), + + ECORE_GETOPT_STORE_METAVAR_STR(0, NULL, "IP:PORT to listen.", "ADDRESS"), + + ECORE_GETOPT_SENTINEL + } +}; + +int +main(int argc, char *argv[]) +{ + unsigned int flags = EFL_NET_SOCKET_FLAGS_DEFAULTS; + double timeout = 2.0; + unsigned int client_limit = 2; + Eina_Bool reject_excess_clients = EINA_FALSE; + Eina_Bool quit_option = EINA_FALSE; + const char *addr = "0.0.0.0:8000"; + Ecore_Getopt_Value values[] = { + ECORE_GETOPT_VALUE_UINT(flags), + ECORE_GETOPT_VALUE_DOUBLE(timeout), + ECORE_GETOPT_VALUE_UINT(client_limit), + ECORE_GETOPT_VALUE_BOOL(reject_excess_clients), + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_STR(addr), + ECORE_GETOPT_VALUE_NONE + } + Efl_Net_Server_Tcp *srv; + Ecore_Fd_Handler *fdh; + int args, retval = EXIT_SUCCESS; + + eina_init(); + 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; + } + + srv = eo_add(EFL_NET_SERVER_TCP, NULL, + efl_net_server_address_set(eo_self, addr), + efl_net_server_flags_set(eo_self, flags), + efl_net_server_client_limit_set(eo_self, client_limit, + reject_excess_clients), + efl_net_server_client_timeout_set(eo_self, timeout)); + + // TODO: change to eo_event_callback_array_add() + eo_event_callback_add(srv, EFL_NET_SERVER_EVENT_CLIENT_ADD, + _cb_client_add, NULL); + eo_event_callback_add(srv, EFL_NET_SERVER_EVENT_CLIENT_REJECTED, + _cb_client_rejected, NULL); + eo_event_callback_add(srv, EFL_NET_SERVER_EVENT_ERROR, + _cb_error, NULL); + + fdh = ecore_main_fd_handler_add(STDIN_FILENO, ECORE_FD_READ, + _cb_stdin, NULL, NULL, NULL); + + ecore_main_loop_begin(); + + eo_unref(srv); + ecore_main_fd_handler_del(fdh); + + end: + ecore_con_shutdown(); + ecore_shutdown(); + eina_shutdown(); + + return retval; +} diff --git a/src/lib/ecore_con/Ecore_Con.h b/src/lib/ecore_con/Ecore_Con.h index d298ccf892..b815ba9eb7 100644 --- a/src/lib/ecore_con/Ecore_Con.h +++ b/src/lib/ecore_con/Ecore_Con.h @@ -653,6 +653,36 @@ EAPI extern int ECORE_CON_EVENT_URL_PROGRESS; */ /** + * @addtogroup Ecore_Con_Errors_Group Ecore_Con_Lib_Group + * @ingroup Ecore_Con_Group + * + * @{ + */ +#ifdef EFL_BETA_API_SUPPORT +/** + * No space left on device (errno: ENOSPC). + * @since 1.19 + */ +EAPI extern Eina_Error EFL_NET_ERROR_NO_SPACE; + +/** + * Transport endpoint is not connected (errno: ENOTCONN). + * @since 1.19 + */ +EAPI extern Eina_Error EFL_NET_ERROR_NOT_CONNECTED; + +/** + * Operation Canceled (errno: ECANCELED). + * @since 1.19 + */ +EAPI extern Eina_Error EFL_NET_ERROR_CANCELED; + +#endif +/** + * @} + */ + +/** * @addtogroup Ecore_Con_Events_Group Ecore_Con_Lib_Group * @ingroup Ecore_Con_Group * diff --git a/src/lib/ecore_con/Ecore_Con_Eo.h b/src/lib/ecore_con/Ecore_Con_Eo.h index cb2903b5f1..321a303772 100644 --- a/src/lib/ecore_con/Ecore_Con_Eo.h +++ b/src/lib/ecore_con/Ecore_Con_Eo.h @@ -1,5 +1,21 @@ +/* TODO: remove efl_network_* once efl_net_* are ready */ #include "efl_network.eo.h" #include "efl_network_server.eo.h" #include "efl_network_connector.eo.h" #include "efl_network_client.eo.h" #include "efl_network_url.eo.h" + +#include "efl_net_types.eot.h" + +#include "efl_net_dialer.eo.h" +#include "efl_net_dialer_tcp.eo.h" +#include "efl_net_dialer_udp.eo.h" +#include "efl_net_dialer_unix.eo.h" +#include "efl_net_server.eo.h" +#include "efl_net_server_tcp.eo.h" +#include "efl_net_server_udp.eo.h" +#include "efl_net_server_unix.eo.h" +#include "efl_net_socket.eo.h" +#include "efl_net_socket_tcp.eo.h" +#include "efl_net_socket_udp.eo.h" +#include "efl_net_socket_unix.eo.h" diff --git a/src/lib/ecore_con/ecore_con-eoify-analisys.md b/src/lib/ecore_con/ecore_con-eoify-analisys.md new file mode 100644 index 0000000000..27909646dd --- /dev/null +++ b/src/lib/ecore_con/ecore_con-eoify-analisys.md @@ -0,0 +1,486 @@ +# Ecore_Con + +This module is used via `Ecore_Con_Server`, for both server and +connect-to-server roles, which can be confusing at first sight. + +There is an `Ecore_Con_Client` handle, but users do not create them +manually, rather receive them via an `Ecore_Event` +`ECORE_CON_EVENT_CLIENT_ADD`. This handle is to be used by server-side +to identify connected clients and send them data. + +The **connect-to-server** role (usually called "connection", "socket" or +"client") is done via an `Ecore_Con_Server` created with +`ecore_con_server_connect()`. + +The server role is done via an `Ecore_Con_Server` created with +`ecore_con_server_add()`. + +Current `ecore_con` usage (except `ecore_con_url`, within EFL): +https://gist.github.com/barbieri/ed77c1e829a6e684dd736a771d6732d1 + +## Ecore_Con_Server (as server role) + +Methods (omitted `ecore_con_server_` prefix): + + - `add(type, name, port, data): Ecore_Con_Server` *[[constructor]]* + - `ssl_cert_add(string): bool` *[[Add a PEM certificate file]]* + - `ssl_privkey_add(string): bool` *[[Add a PEM private key file]]* + - `ssl_crl_add(string): bool` *[[add PEM CRL file]]* + - `ssl_cafile_add(string): bool` *[[add PEM CA file]]* + +Properties: + + - `data: void_ptr` *[[user data]]* + - `connected: bool` *[[if connected or not]]* + - `port: int` *[[the port serving the requests]]* + - `uptime: double` *[[time in seconds since it was started]]* + - `client_limit: int` *[[number of concurrent clients to allow]]* + - `reject_excess_clients: bool` *[[if true, automatically disconnects extra clients]]* + +Events (omitted `ECORE_CON_EVENT_` prefix): + + - `CLIENT_ADD: Ecore_Con_Client` + - `CLIENT_DEL: Ecore_Con_Client` + +# Ecore_Con_Client (used only by the server role) + +Methods: + + - `send(buffer): int` *[[server writes data to client]]* + - `flush(): void` *[[block until all pending data is written]]* + - `del(): void` *[[server close connection to client and free client handle]]* + - `upgrade(type): bool` *[[upgrade to SSL using STARTTLS]]* + +Properties: + + - `fd: int` *[[get the internal filedescriptor]]* + - `data [RW]: void_ptr` *[[user data associated with a client]]* + - `ip: string` *[[IP address of this client]]* + - `port: int` *[[port of connected client]]* + - `uptime: double` *[[time in seconds since it was connected]]* + - `connected: bool` *[[if connected]]* + - `timeout [RW]: double` *[[duration of idleness to keep the client alive]]* + +Events (omitted `ECORE_CON_EVENT_CLIENT_` prefix and `Ecore_Con_Client` handle): + + - `DATA: buffer` *[[reports incoming data]]* + - `WRITE: int` *[[reports data sent]]* + - `UPGRADE: void` *[[reports completed handshake]]* + - `ERROR: string` *[[reports error]]* + + +# Ecore_Con_Server (as connect-to-server role) + +Methods: + + - `connect(type, name, port, data):` *[[constructor]]* + - `del():` *[[destructor]]* + - `send(buffer): int` *[[write data to server]]* + - `flush(): void` *[[block until all pending data is written]]* + - `upgrade(type): bool` *[[upgrade to SSL using STARTTLS]]* + +Methods (Did I get all of these right?): + + - `ssl_cert_add(string): bool` *[[Add a PEM certificate file]]* + - `ssl_privkey_add(string): bool` *[[Add a PEM private key file]]* + - `ssl_crl_add(string): bool` *[[add PEM CRL file]]* + - `ssl_cafile_add(string): bool` *[[add PEM CA file]]* + - `ssl_server_upgrade(type): void` *[[start TLS]]* + - `ssl_server_verify(): void` *[[enable (once) the verification of loaded certificates]]* + - `ssl_server_verify_basic(): void` *[[enable hostname-based verification]]* + - `ssl_server_verify_name_set(string): void` *[[change the name to use for the server]]* + - `ssl_server_verify_name_get(): string` *[[get the name to use for the server]]* + +Properties (omitted ecore_con_server_ prefix): + + - `fd: int` *[[get the internal filedescriptor]]* + - `ip: string` *[[IP address of the connected server]]* + - `data: void_ptr` *[[user data]]* + - `uptime: double` *[[time in seconds since it was connected]]* + - `connected: bool` *[[if connected or not]]* + - `timeout [RW]: double` *[[duration of idleness to keep the server alive]]* + +Events (omitted `ECORE_CON_EVENT_SERVER_` prefix and `Ecore_Con_Server` handle): + + - `ADD:` *[[server was connected]]* + - `DEL:` *[[server is disconnected]]* + - `DATA: buffer` *[[server sent some data and it's ready to be read]]* + - `WRITE: int` *[[reports data sent]]* + - `ERROR: string` *[[reports error]]* + - `UPGRADE: void` *[[reports completed handshake]]* + +# Node.js + +Node.js is based on a single thread and main loop with asynchronous +events. + +## net.Server + +This is analogous to `Ecore_Con_Server` as returned by the +`ecore_con_server_add()`. It will listen for connections and each new +accepted is a `net.Socket ` (see below), which is the equivalent of +`Ecore_Con_Client` as passed in `ECORE_CON_EVENT_CLIENT_ADD` event. + +A server is created with `net.createServer([options], [cb])` + +Doc: https://nodejs.org/api/net.html#net_class_net_server + +Methods: + + - `address(): object` *[[bound address with port, family and address]]* + - `close(): void` *[[stop the server servicing new conns, keeps existing]]* + - `getConnections(cb(err, count)):` *[[query count of existing conns]]* + - `listen(options, [cb]):` *[[start listening to conns]]* + +Properties: + + - `listening: bool` *[[whenever server is listening]]* + - `maxConnections: int` *[[maximum concurrent conns to accept]]* + +Events: + + - `close: void` *[[emitted when closed and the last connection is gone]]* + - `connection: client net.Socket` *[[new connection/client]]* + - `error: Error` *[[when an error occurs]]* + - `listening: void` *[[server.listen() was called]]* + +## net.Socket + +This is analogous to either `Ecore_Con_Client` as passed in +`ECORE_CON_EVENT_CLIENT_ADD` or `Ecore_Con_Server` as returned by +`ecore_con_server_connect()`. `Ecore_Con_Client` represents a client +connected to a server, while the `Ecore_Con_Server` represents a +connection to the server. Node.JS, as most APIs out there, simply +handle this as an ongoing connection which offers common properties +such as local and remote addresses, read and write methods and so on. + +A connection is passed to `net.Server::connection` event or created +with net.connect() or net.createConnection(). + +Doc: https://nodejs.org/api/net.html#net_class_net_socket + +Methods: + + - `constructor([{fd, readable, writeable}], [allowHalfOpen])` + - `write(data, encoding): bool` *[[sends data, True means all data was sent]]* + - `address(): object` *[[bound address with port, family and address]]* + - `connect(options, listener):` *[[connect to a server]]* + - `destroy([error]):` *[[ensures no more i/o]]* + - `end([data] [,encoding]):` *[[do write(data, encoding), then send FIN]]* + - `pause():` *[[pauses data reading, does not emit 'data' event]]* + - `resume():` *[[resumes data reading, allows emit 'data' event]]* + - `setEncoding(string):` *[[sets an encoding to convert raw bytes to string]]* + - `setKeepAlive(bool, initialDelay):` *[[change keep alive behavior]]* + - `setNoDelay(bool):` *[[send data immediately on each write()]]* + - `setTimeout(int):` *[[set socket timeout milliseconds, 0 disables]]* + +Properties: + + - `bufferSize: int` *[[write buffer to keep]]* + - `bytesRead: int` *[[received bytes]]* + - `bytesWritten: int` *[[sent bytes]]* + - `connecting: bool` *[[if connect() is still on going]]* + - `destroyed: bool` *[[conn was destroyed]]* + - `localAddress: string` *[[local IP or path]]* + - `localPort: int` *[[local port number]]* + - `remoteAddress: string` *[[remote IP or path]]* + - `remoteFamily: string` [remote family, IPv4 or IPv6]]* + - `remotePort: int` *[[remote port number]]* + +Events: + + - `close: had_error bool` *[[emitted once the socket is fully closed]]* + - `connect: void` *[[emitted once the connection is fully stablished]]* + - `data: buffer` *[[data is received]]* + - `drain: void` *[[emitted when write buffer becomes empty]]* + - `end: void` *[[emitted when the other end sends a FIN packet]]* + - `error: Error` *[[when an error occurs, followed by 'close' event.]]* + - `lookup: err, address, family, host` *[[after name is resolved, before connecting]]* + - `timeout: void` *[[after inactivity timeout]]* + + +# Qt + +Qt is based on single thread and main loop with asynchronous events. + +## QTcpServer + +This is analogous to `Ecore_Con_Server` as returned by +`ecore_con_server_add()`. It will listen for connecting and each new +accepted is a `QTcpSocket` (derivate of `QAbstractSocket`, see below, +which is the equivalent of `Ecore_Con_Client` as passed in +`ECORE_CON_EVENT_CLIENT_ADD` event. + +A server is created with `QTcpServer` object. Clients (`QTcpSocket`) +are child objects of `QTcpServer` and are deleted if the server is +destroyed. + +Doc: http://doc.qt.io/qt-5/qtcpserver.html + +Methods: + + - `close(): void` *[[stop listening for connections]]* + - `listen(address, port): bool` + - `nextPendingConnection(): QTcpSocket` *[[returns the next ready client as QTcpSocket]]* + - `pauseAccepting(): void` + - `resumeAccepting(): void` + - `waitForNewConnection(time)` *[[blocks main thread]]* + +Properties: + + - `isListening(): bool` + - `errorString(): string` *[[string of serverError()]]* + - `serverError(): SocketError` + - `serverAddress(): QHostAddress` + - `serverPort(): int` + - `maxPendingConnections(): int` + - `setMaxPendingConnections(int): void` + - `hasPendingConnections(): bool` *[[if can call nextPendingConnection()]]* + - `proxy(): QNetworkProxy` + - `setProxy(QNetworkProxy): void` + +Events: + + - `acceptError: SocketError` *[[if accept triggers an error]]* + - `newConnection: void` *[[need to call nextPendingConnection()]]* + +## QAbstractSocket + +This is analogous to either `Ecore_Con_Client` as passed in +`ECORE_CON_EVENT_CLIENT_ADD` or `Ecore_Con_Server` as returned by +`ecore_con_server_connect()`. `Ecore_Con_Client` represents a client +connected to a server, while the `Ecore_Con_Server` represents a +connection to the server. Qt, as most APIs out there, simply handle +this as an ongoing connection which offers common properties such as +local and remote addresses, read and write methods and so on. + +A connection is returned by `QTcpServer::nextPendingConnection()` or +created with `QTcpSocket()`, `QSslSocket()`, `QUdpSocket()`, +`QLocalSocket()`... + +Connections are implemented by specific classes such as `QTcpSocket`, +`QSslSocket`, `QUdpSocket` and `QLocalSocket`. + +Doc: http://doc.qt.io/qt-5/qabstractsocket.html + +Methods: + + - `connectToHost(address, port, mode):` *[[connect to server]]* + - `disconnectFromHost()` *[[schedule for close once all data is sent]]* + - `abort(): void` *[[ensures no more i/o]]* + - `flush(): bool` *[[flushes as much data to socket without block]]* + - `close(): bool` *[[emits 'aboutToClose', set mode=NotOpen, closes the socket]]* + - `waitForBytesWritten(time): bool` *[[block caller thread for write()]]* + - `waitForReadyRead(time): bool` *[[block caller thread for read()]]* + - `waitForDisconnect(time): bool`*[[block caller thread for disconnect]]* + - `waitForConnect(time): bool` *[[block caller thread for connect]]* + - `read(char *, int64 max): int64` + - `readAll(): buffer` + - `readLine(): buffer` + - `write(char *, int64 max): int64` + - `getChar(char *): bool` + - `putChar(char): bool` + - `peek(char *, int64 max): int64` + - `reset()` + +Properties: + + - `localAddress(): QHostAddress` + - `localPort(): int` + - `peerAddress(): QHostAddress` + - `peerPort(): int` + - `readBufferSize() int64` + - `setReadBufferSize(int64)` + - `setSocketOption(option, QVariant value)` + - `socketOption(option): QVariant` + - `setProxy(QNetworkProxy)` + - `proxy()` + - `socketType(): SocketType` + - `state(): SocketState` + - `error(): SocketError` + - `isOpen(): bool` + - `isReadable(): bool` + - `isWritable(): bool` + - `isSequential(): bool` *[[ie: TCP, streams]]* + - `isTextModeEnabled(): bool` + - `setTextModeEnabled(bool):` + - `canReadLine(): bool` *[[if there is a full line to be read]]* + - `atEnd(): bool` *[[to be used in loop constructs with read()]]* + - `bytesAvailable(): int64` *[[number of bytes waiting to be read]]* + - `bytesToWrite(): int64` *[[bytes queued for write]]* + - `pos(): int64` + - `canReadLine(): bool` + +Events: + + - `connected: void` + - `disconnected: void` + - `error: SocketError` + - `hostFound: void` *[[after name is resolved, before connecting]]* + - `proxyAuthenticationRequired: proxy, authenticator` + - `stateChanged: SocketState` + - `aboutToClose: void` *[[once close() is called]]* + - `bytesWritten: int64` + - `readChannelFinished` + - `readyRead` + +# Go (language) + +Go is based on *synchronous* and *blocking* primitives, using multiple +co-routines (goroutines) to implement concurrent tasks. + +## net.Listener + +This is the server role and is analogous to `Ecore_Con_Server` as +returned by `ecore_con_server_add()`. + +Listeners are implemented implemented by specific classes such as +`TCPListener`, `UDPListener` and `UnixListener`. + +Doc: https://golang.org/pkg/net/#Listener + +Methods: + + - `Listen(network, address): Listener, error` *[[creates a listener]]* + - `Accept(): Conn, error` *[[waits for and returns a new connection]]* + - `Close(): error` *[[closes the listener]]* + +Properties: + + - `Addr(): Addr` *[[returns the listener local address]]* + + +## net.Dialer + +This is the **client-to-server** role and is analogous to +`Ecore_Con_Server` as returned by `ecore_con_server_connect()`. + +Doc: https://golang.org/pkg/net/#Dialer + +Methods: + + - `Dial(network, address): Conn, error` *[[connect to the remote server]]* + - `Cancel(): chan` *[[close connection if chan is closed]]* + +Properties: + + - `Timeout: time` *[[maximum time to wait before conn times out]]* + - `Deadline: time` *[[absolute point in time to fail]]* + - `LocalAddr: Addr` *[[local address, if nil, assign automatic]]* + - `DualStack: bool` *[[ipv4 and ipv6]]* + - `FallbackDelay: time` *[[if DualStack, time to change to fallback conn]]* + - `KeepAlive: time` *[[duration of idleness to keep the connection alive]]* + +## net.Addr + +Doc: https://golang.org/pkg/net/#Addr + +Properties: + + - `Network(): string` *[[network name]]* + - `String(): string` *[[string representation of the address]]* + + +## net.TCPAddr / net.UDPAddr + +Doc: https://golang.org/pkg/net/#TCPAddr + +Properties: + + - `IP:` (4/16 byte address) + - `Port: int` + - `Zone: string` *[[IPv6 zone]]* + +## net.Conn + +This is analogous to either `Ecore_Con_Client` as passed in +`ECORE_CON_EVENT_CLIENT_ADD` or `Ecore_Con_Server` as returned by +`ecore_con_server_connect()`. `Ecore_Con_Client` represents a client +connected to a server, while the `Ecore_Con_Server` represents a +connection to the server. Go, as most APIs out there, simply handle +this as an ongoing connection which offers common properties such as +local and remote addresses, read and write methods and so on. + +A connection is returned by `net.Listener::Accept()` or created with +`net.Dialer`, such as `net.Dial(network, address)`. + +Unlike EFL, go primitives are all *synchronous* and people use +goroutines to start a co-routine (pseudo thread) to do the processing. + +Connections are implemented by specific classes such as `IPConn`, +`TCPConn`, `UDPConn` and `UnixConn`. + +Doc: https://golang.org/pkg/net/#Conn + +Methods: + + - `Read(buffer): int, error` *[[read to buffer, returns read amount]]* + - `Write(buffer): int, error` *[[write buffer, returns written ammount]]* + - `Close(): error` *[[closes the connection]]* + +Properties: + + - `LocalAddr(): Addr` *[[returns the local address]]* + - `RemoteAddr(): Addr` *[[returns the remote address]]* + - `SetDeadline(time): error` *[[set I/O deadlines]]* + - `SetReadDeadline(time): error` *[[set Input deadline]]* + - `SetWriteDeadline(time): error` *[[set Output deadline]]* + + +## net.IPConn (net.Conn) + +Base class for `TCPConn` and `UDPConn`. + +Doc: https://golang.org/pkg/net/#IPConn + +Extra methods: + + - `ReadFrom(buffer): int, Addr, error` *[[PacketConn variant of Read()]]* + - `ReadFromIP(buffer): int, IPAddr, error` *[[PacketConn variant of Read()]]* + - `ReadMsgIP(buffer, out_of_band_buffer): ...` *[[PacketConn variant]]* + - `WriteTo(buffer, Addr): int, error` *[[PacketConn variant]]* + - `WriteToIP(buffer, IPAddr): int, error` *[[PacketConn variant]]* + - `WriteMsgIP(buffer, out_of_band_buffer, IPAddr): `*[[PacketConn variant]]* + - `SetReadBuffer(int):` *[[bytes to use to receive data]]* + - `SetWriteBuffer(int):` *[[bytes to use to send data]]* + + +## Soletta + +Soletta (https://github.com/solettaproject/soletta) is based on single +thread and main loop with asynchronous events. + +It doesn't expose middleground classes to do socket, just the very +basic POSIX-like API and high level via each module (MQTT, +HTTP...). Nonetheless it focus on small and efficient APIs, thus +instead of multiple setters and getters, most parameters are specified +to the constructor as a structure with optional members (can be zero). + +The API to be listed here is not even a network one, but rather a +streaming API used by its UART module. It's focused on efficiency and +to save on memory it uses blobs (memory + size with parent and +references) to avoid copies in the streaming API. + +Doc: http://solettaproject.github.io/docs/c-api/group__UART.html + +Methods: + + - `open(config): handle` *[[constructor]]* + - `feed(blob): int` *[[]]* + +Events: + + - `data: binbuf, &int` *[[reports data is available in binbuf, user reports how much was consumed]]* + - `feed_done: blob, error` *[[notifies a blob was fully sent or failed]]* + +As one can see, by feeding immutable blobs, the API does not keep an +internal binbuf of pending memory, instead it will keep an array of +pending blobs and the offset of the first pending blob (for partial +sends). This maps well to iovec APIs in Linux. + +To avoid going over-memory on continuous send, `-ENOSPC` is returned +if it goes over pre-defined buffer size in config. + +The constructor specifies maximum read bytes (to be allocated using a +binbuf, 0 is unlimited) and write size (to be allowed in pending blobs +list). diff --git a/src/lib/ecore_con/ecore_con.c b/src/lib/ecore_con/ecore_con.c index 29fe7311bb..1b749edaa2 100644 --- a/src/lib/ecore_con/ecore_con.c +++ b/src/lib/ecore_con/ecore_con.c @@ -175,6 +175,10 @@ EAPI int ECORE_CON_EVENT_CLIENT_ERROR = 0; EAPI int ECORE_CON_EVENT_SERVER_ERROR = 0; EAPI int ECORE_CON_EVENT_PROXY_BIND = 0; +EAPI Eina_Error EFL_NET_ERROR_NO_SPACE = 0; +EAPI Eina_Error EFL_NET_ERROR_NOT_CONNECTED = 0; +EAPI Eina_Error EFL_NET_ERROR_CANCELED = 0; + static Eina_List *servers = NULL; static int _ecore_con_init_count = 0; static int _ecore_con_event_count = 0; @@ -219,6 +223,10 @@ ecore_con_init(void) eina_magic_string_set(ECORE_MAGIC_CON_CLIENT, "Ecore_Con_Client"); eina_magic_string_set(ECORE_MAGIC_CON_URL, "Ecore_Con_Url"); + EFL_NET_ERROR_NO_SPACE = eina_error_msg_static_register("No space left on device"); + EFL_NET_ERROR_NOT_CONNECTED = eina_error_msg_static_register("Transport endpoint is not connected"); + EFL_NET_ERROR_CANCELED = eina_error_msg_static_register("Operation Canceled"); + /* TODO Remember return value, if it fails, use gethostbyname() */ ecore_con_socks_init(); ecore_con_ssl_init(); @@ -2984,3 +2992,465 @@ _ecore_con_lookup_done(void *data, #include "efl_network_client.eo.c" #include "efl_network_server.eo.c" #include "efl_network_connector.eo.c" + +EOLIAN static void +_efl_net_socket_adopt(Eo *obj, Efl_Net_Socket_Data *skt, int fd) +{ + EO_CONSTRUCTOR_CHECK_RETURN(obj); + skt->fd = fd; +} + +EOLIAN static Eina_Error +_efl_net_socket_send(Eo *obj, Efl_Net_Socket_Data *skt, const uint8_t *data, size_t size) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL(skt->fd < 0, EFL_NET_ERROR_NOT_CONNECTED); + EINA_SAFETY_ON_FALSE_RETURN_VAL(eo_finalized_get(obj), EFL_NET_ERROR_NOT_CONNECTED); + + if (skt->buffer.send_size) + { + size_t used = eina_binbuf_length_get(skt->buffer.send); + + if (skt->buffer.send_size < size + used) + { + DBG("using a limited send buffer=%zd, already used=%zd, size=%zd, required=%zd", + skt->buffer.send_size, used, size, + size + used - skt->buffer.send_size); + return EFL_NET_ERROR_NO_SPACE; + } + } + + if (!eina_binbuf_append_length(skt->buffer.send, data, size)) + { + DBG("could not queue data to be sent, buffer usage=%zd, size=%zd", + eina_binbuf_length_get(skt->buffer.send), size); + return EFL_NET_ERROR_NO_SPACE; + } + + // TODO: monitor ECORE_FD_WRITE and send data + + return 0; +} + +EOLIAN static Eina_Bool +_efl_net_socket_flush(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL(skt->fd < 0, EINA_FALSE); + + // TODO: send data + + return eina_binbuf_length_get(skt->buffer.send) == 0; +} + +EOLIAN static void +_efl_net_socket_close(Eo *obj, Efl_Net_Socket_Data *skt) +{ + EINA_SAFETY_ON_TRUE_RETURN(skt->fd < 0); + + close(efl_net_socket_steal_fd(obj)); + + eo_event_callback_call(obj, EFL_NET_SOCKET_EVENT_CLOSED, NULL); +} + +EOLIAN static int +_efl_net_socket_steal_fd(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt) +{ + int fd = skt->fd; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(skt->fd < 0, -1); + skt->fd = -1; + + if (skt->buffer.send) + { + eina_binbuf_free(skt->buffer.send); + skt->buffer.send = NULL; + } + + if (skt->buffer.receive) + { + eina_binbuf_free(skt->buffer.receive); + skt->buffer.receive = NULL; + } + + eina_stringshare_replace(&skt->address.local, NULL); + eina_stringshare_replace(&skt->address.remote, NULL); + + if (skt->timeout.timer) + { + eo_del(skt->timeout.timer); + skt->timeout.timer = NULL; + } + + if (skt->fd_handler) + { + eo_del(skt->fd_handler); + skt->fd_handler = NULL; + } + + return fd; +} + +EOLIAN static Eina_Stringshare * +_efl_net_socket_address_local_get(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt) +{ + return skt->address.local; +} + +EOLIAN static Eina_Stringshare * +_efl_net_socket_address_remote_get(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt) +{ + return skt->address.remote; +} + +EOLIAN static Efl_Net_Socket_Flags +_efl_net_socket_flags_get(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt) +{ + return skt->flags; +} + +EOLIAN static void +_efl_net_socket_flags_set(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt, Efl_Net_Socket_Flags flags) +{ + if (skt->flags == flags) return; + + skt->flags = flags; + if (skt->fd < 0) return; + + /* if we're adopting a socket, its flags were already set */ + if (!eo_finalized_get(obj)) return; + + // TODO: setsockopt() +} + +EOLIAN static Eina_Bool +_efl_net_socket_connected_get(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt) +{ + // TODO: efl_net_dialer to override and check if it's still connecting (resolving names, etc) + return skt->fd >= 0; +} + +EOLIAN static size_t +_efl_net_socket_send_buffer_usage_get(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt) +{ + return eina_binbuf_length_get(skt->buffer.send); +} + +EOLIAN static size_t +_efl_net_socket_send_buffer_size_get(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt) +{ + return skt->buffer.send_size; +} + +static Eina_Bool + _efl_net_socket_common_buffer_resize(Eina_Binbuf **p_buf, size_t bytes) +{ + if (*p_buf && bytes) + { + size_t len = eina_binbuf_length_get(*p_buf); + if (len > bytes) + { + unsigned char *old = eina_binbuf_string_steal(*p_buf); + unsigned char *mem; + + WRN("shrinking buffer to %zd below data size %zd, crop!", + bytes, len); + + mem = realloc(old, bytes + 1); + EINA_SAFETY_ON_NULL_GOTO(mem, realloc_failed); + + mem[bytes] = 0; + old = mem; + + eina_binbuf_free(*p_buf); + *p_buf = eina_binbuf_manage_new(mem, bytes, EINA_FALSE); + EINA_SAFETY_ON_NULL_GOTO(*p_buf, realloc_failed); + + realloc_failed: + free(old); + return EINA_FALSE; + } + } + + if (!*p_buf) + { + if (bytes == 0) + { + *p_buf = eina_binbuf_new(); + EINA_SAFETY_ON_NULL_RETURN_VAL(*p_buf, EINA_FALSE); + return EINA_TRUE; + } + else + { + unsigned char *mem = malloc(bytes + 1); + EINA_SAFETY_ON_NULL_RETURN_VAL(mem, EINA_FALSE); + *p_buf = eina_binbuf_manage_new(mem, bytes, EINA_FALSE); + EINA_SAFETY_ON_NULL_GOTO(*p_buf, binbuf_failed); + eina_binbuf_reset(*p_buf); /* reset len to zero */ + return EINA_TRUE; + + binbuf_failed: + free(mem); + return EINA_FALSE; + } + } + + return EINA_TRUE; +} + +EOLIAN static void +_efl_net_socket_send_buffer_size_set(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt, size_t bytes) +{ + if (skt->buffer.send_size == bytes) return; + + if (!_efl_net_socket_common_buffer_resize(&skt->buffer.send, bytes)) + { + ERR("could not resize send buffer from %zd to %zd", + skt->buffer.send_size, bytes); + return; + } + + skt->buffer.send_size = bytes; +} + +EOLIAN static size_t +_efl_net_socket_receive_buffer_size_get(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt) +{ + return skt->buffer.receive_size; +} + +EOLIAN static void +_efl_net_socket_receive_buffer_size_set(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt, size_t bytes) +{ + if (skt->buffer.receive_size == bytes) return; + + if (!_efl_net_socket_common_buffer_resize(&skt->buffer.receive, bytes)) + { + ERR("could not resize receive buffer from %zd to %zd", + skt->buffer.receive_size, bytes); + return; + } + + skt->buffer.receive_size = bytes; +} + +EOLIAN static double +_efl_net_socket_timeout_get(Eo *obj EINA_UNUSED, Efl_Net_Socket_Data *skt) +{ + return skt->timeout.seconds; +} + +static void + _efl_net_socket_timeout_expired(void *data, const Eo_Event *event EINA_UNUSED) +{ + Efl_Net_Socket *obj = data; + + eo_event_callback_call(obj, EFL_NET_SOCKET_EVENT_TIMEDOUT, NULL); + efl_net_socket_close(obj); +} + +EOLIAN static void +_efl_net_socket_timeout_set(Eo *obj, Efl_Net_Socket_Data *skt, double seconds) +{ + skt->timeout.seconds = seconds; + + if (skt->timeout.timer) + { + if (seconds > 0) + efl_loop_timer_interval_set(skt->timeout.timer, seconds); + else + { + eo_del(skt->timeout.timer); + skt->timeout.timer = NULL; + } + } + else if (seconds > 0) + { + skt->timeout.timer = eo_add(EFL_LOOP_TIMER_CLASS, obj, + efl_loop_timer_interval_set(eo_self, seconds), + eo_event_callback_add(eo_self, EFL_LOOP_TIMER_EVENT_TICK, _efl_net_socket_timeout_expired, obj)); + } +} + +EOLIAN static Eo_Base * +_efl_net_socket_eo_base_constructor(Eo *obj, Efl_Net_Socket_Data *skt) +{ + skt->fd = -1; + skt->flags = EFL_NET_SOCKET_FLAGS_DEFAULTS; + + return eo_constructor(eo_super(obj, EFL_NET_SOCKET_CLASS)); +} + +EOLIAN static void +_efl_net_socket_eo_base_destructor(Eo *obj, Efl_Net_Socket_Data *skt) +{ + if (skt->fd) efl_net_socket_close(obj); + + eo_destructor(eo_super(obj, EFL_NET_SOCKET_CLASS)); +} + +static void +_efl_net_socket_on_error(void *data, const Eo_Event *event) +{ + Efl_Net_Socket *obj = data; + + // TODO: event, find out error to report +} + + +static void +_efl_net_socket_on_read(void *data, const Eo_Event *event) +{ + Efl_Net_Socket *obj = data; + + // TODO: event + + // TODO: it seems _check_fd_event_catcher_del is wrong, ++ instead of --. + // TODO: deleting the event handler won't remove the FD_READ flag! +} + +EOLIAN static Eo * +_efl_net_socket_eo_base_finalize(Eo *obj, Efl_Net_Socket_Data *skt) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL(skt->fd < 0, NULL); + + if (!skt->buffer.send) { + if (!_efl_net_socket_common_buffer_resize(&skt->buffer.send, skt->buffer.send_size)) { + ERR("could not init send buffer"); + return NULL; + } + } + + if (!skt->buffer.receive) { + if (!_efl_net_socket_common_buffer_resize(&skt->buffer.receive, skt->buffer.receive_size)) { + ERR("could not init receive buffer"); + return NULL; + } + } + + // TODO: getsockname() and getpeername() if skt->address.local/remote are unset? + + /* timeout defaults to zero, so if set timer will be created */ + + if (!skt->fd_handler) + { + skt->fd_handler = eo_add(EFL_LOOP_FD_CLASS, obj, + eo_event_callback_add(eo_self, EFL_LOOP_FD_EVENT_ERROR, _efl_net_socket_on_error, obj), + eo_event_callback_add(eo_self, EFL_LOOP_FD_EVENT_READ, _efl_net_socket_on_read, obj)); + } + + return eo_finalize(eo_super(obj, EFL_NET_SOCKET_CLASS)); +} + +EOLIAN static void +_efl_net_server_adopt(Eo *obj, Efl_Net_Server_Data *svr, int fd) +{ + EO_CONSTRUCTOR_CHECK_RETURN(obj); + svr->fd = fd; +} + +EOLIAN static void +_efl_net_server_address_set(Eo *obj, Efl_Net_Server_Data *svr, const char *address) +{ + EO_CONSTRUCTOR_CHECK_RETURN(obj); + eina_stringshare_replace(&svr->address, address); +} + +EOLIAN static Eina_Stringshare * +_efl_net_server_address_get(Eo *obj EINA_UNUSED, Efl_Net_Server_Data *svr) +{ + return svr->address; +} + +EOLIAN static Efl_Net_Socket_Flags +_efl_net_server_flags_get(Eo *obj EINA_UNUSED, Efl_Net_Server_Data *svr) +{ + return svr->flags; +} + +EOLIAN static void +_efl_net_server_flags_set(Eo *obj EINA_UNUSED, Efl_Net_Server_Data *svr, Efl_Net_Socket_Flags flags) +{ + if (svr->flags == flags) return; + + svr->flags = flags; + if (svr->fd < 0) return; + + /* if we're adopting a socket, its flags were already set */ + if (!eo_finalized_get(obj)) return; + + // TODO: setsockopt() +} + +EOLIAN static void +_efl_net_server_clients_limit_set(Eo *obj EINA_UNUSED, Efl_Net_Server_Data *svr, unsigned int limit, Eina_Bool reject_excess) +{ + svr->clients.limit = limit; + svr->clients.reject_excess = reject_excess; +} + +EOLIAN static void +_efl_net_server_clients_limit_get(Eo *obj EINA_UNUSED, Efl_Net_Server_Data *svr, unsigned int *limit, Eina_Bool *reject_excess) +{ + if (limit) *limit = svr->clients.limit; + if (reject_excess) *reject_excess = svr->clients.reject_excess; +} + +EOLIAN static double +_efl_net_server_clients_timeout_get(Eo *obj EINA_UNUSED, Efl_Net_Server_Data *svr) +{ + return svr->clients.timeout; +} + +EOLIAN static void +_efl_net_server_clients_timeout_set(Eo *obj EINA_UNUSED, Efl_Net_Server_Data *svr, double seconds) +{ + svr->clients.timeout = seconds; +} + +EOLIAN static Eo_Base * +_efl_net_server_eo_base_constructor(Eo *obj, Efl_Net_Server_Data *svr) +{ + svr->fd = -1; + svr->flags = EFL_NET_SOCKET_FLAGS_DEFAULTS; + + return eo_constructor(eo_super(obj, EFL_NET_SERVER_CLASS)); +} + +EOLIAN static void +_efl_net_server_eo_base_destructor(Eo *obj, Efl_Net_Server_Data *svr) +{ + close(svr->fd); + svr->fd = -1; + + eina_stringshare_replace(&svr->address, NULL); + + if (svr->fd_handler) + { + eo_del(svr->fd_handler); + svr->fd_handler = NULL; + } + + eo_destructor(eo_super(obj, EFL_NET_SERVER_CLASS)); +} + +EOLIAN static Eo * +_efl_net_server_eo_base_finalize(Eo *obj, Efl_Net_Server_Data *svr) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL(svr->fd < 0, NULL); + + // TODO: listen/bind, register fd handler + + return eo_finalize(eo_super(obj, EFL_NET_SERVER_CLASS)); +} + +#include "efl_net_dialer.eo.c" +#include "efl_net_dialer_tcp.eo.c" +#include "efl_net_dialer_udp.eo.c" +#include "efl_net_dialer_unix.eo.c" +#include "efl_net_server.eo.c" +#include "efl_net_server_tcp.eo.c" +#include "efl_net_server_udp.eo.c" +#include "efl_net_server_unix.eo.c" +#include "efl_net_socket.eo.c" +#include "efl_net_socket_tcp.eo.c" +#include "efl_net_socket_udp.eo.c" +#include "efl_net_socket_unix.eo.c" diff --git a/src/lib/ecore_con/ecore_con_private.h b/src/lib/ecore_con/ecore_con_private.h index 331e4926ba..a6718da832 100644 --- a/src/lib/ecore_con/ecore_con_private.h +++ b/src/lib/ecore_con/ecore_con_private.h @@ -207,6 +207,44 @@ struct _Efl_Network_Server_Data typedef struct _Efl_Network_Server_Data Efl_Network_Server_Data; +typedef struct _Efl_Net_Socket_Data { + struct { + Eina_Binbuf *send; // TODO: if Eina_Blob, change to list + offset within the first element + send_usage accounting + Eina_Binbuf *receive; + size_t receive_size; + size_t send_size; + } buffer; + struct { + Eina_Stringshare *local; + Eina_Stringshare *remote; + } address; + struct { + Efl_Loop_Timer *timer; + double seconds; + } timeout; + Efl_Loop_Fd *fd_handler; + int fd; + Efl_Net_Socket_Flags flags; +} Efl_Net_Socket_Data; + +typedef struct _Efl_Net_Server_Data { + Eina_Stringshare *address; + Efl_Loop_Fd *fd_handler; + int fd; + Efl_Net_Socket_Flags flags; + struct { + double timeout; + unsigned int limit; + unsigned int count; + Eina_Bool reject_excess; + } clients; +} Efl_Net_Server_Data; + +typedef struct _Efl_Net_Dialer_Data { + Eina_Stringshare *address_connected; +} Efl_Net_Dialer_Data; + + struct _Ecore_Con_Info { unsigned int size; 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..5d789d9d47 --- /dev/null +++ b/src/lib/ecore_con/efl_net_dialer.eo @@ -0,0 +1,39 @@ +mixin Efl.Net.Dialer (Eo.Interface) { + [[The Dialer creates a client socket that establishes a network + connection to a remote server. + + Use specific dialer for each connection type, such as + @Efl.Net.Dialer.UDP, @Efl.Net.Dialer.TCP or + @Efl.Net.Dialer.Unix. + + @since 1.19 + ]] + + events { + resolved: string; [[The connecting address was resolved. + + This event is dispatched if a name + resolution was needed, after the name was + resolved and before the connection is + established. + + It is the one to be reported in + @Efl.Net.Socket.address_remote. The + original address is reported in + @.address_connected. + ]] + } + + methods { + @property address_connected { + [[The address used to create and connect this socket. It + is the unresolved address, see the property address_remote + for the actual resolved address]] + get @virtual_pure { + } + values { + address: string; + } + } + } +} 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..344f108f7e --- /dev/null +++ b/src/lib/ecore_con/efl_net_dialer_tcp.eo @@ -0,0 +1,3 @@ +class Efl.Net.Dialer.TCP (Efl.Net.Socket.TCP, Efl.Net.Dialer) { + data: null; +} diff --git a/src/lib/ecore_con/efl_net_dialer_udp.eo b/src/lib/ecore_con/efl_net_dialer_udp.eo new file mode 100644 index 0000000000..18036f995f --- /dev/null +++ b/src/lib/ecore_con/efl_net_dialer_udp.eo @@ -0,0 +1,3 @@ +class Efl.Net.Dialer.UDP (Efl.Net.Socket.UDP, Efl.Net.Dialer) { + data: null; +} diff --git a/src/lib/ecore_con/efl_net_dialer_unix.eo b/src/lib/ecore_con/efl_net_dialer_unix.eo new file mode 100644 index 0000000000..9b0cb57155 --- /dev/null +++ b/src/lib/ecore_con/efl_net_dialer_unix.eo @@ -0,0 +1,5 @@ +class Efl.Net.Dialer.Unix (Efl.Net.Socket.Unix, Efl.Net.Dialer) { + data: null; + + /* TODO: abstract, user or system socket */ +} 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..ca932f8707 --- /dev/null +++ b/src/lib/ecore_con/efl_net_server.eo @@ -0,0 +1,114 @@ +import efl_net_types; + +class Efl.Net.Server (Efl.Loop_User) { + [[The Server will wait for incoming clients and establish their + connections, to be exposed as @Efl.Net.Socket reported by + \@ref EFL_NET_SERVER_CLIENT_ADD event. + + To be notified on client disconnection, listen to Eo event "del" + on each connection. + + @since 1.19 + ]] + + events { + client,add @hot: Efl.Net.Socket; [[New client was accepted]] + client,rejected: string; [[Notifies a client that was rejected. + + See @.clients_limit property. + ]] + error: Eina.Error; [[Some error happened and the server needs to be stopped. + + The error may be in either listen(2), + bind(2), accept(2) or socket becoming + invalid. + + TODO: auto del the object? + ]] + } + + methods { + adopt { + [[Constructor-only method that is used to initialize the new object owning a pre-existent file descriptor. + + Usually servers are created from specific classes such + as @Efl.Net.Server.TCP, @Efl.Net.Server.UDP or + @Efl.Net.Server.Unix. + + This method is useful if you receive the filedescriptor + from elsewhere, such as systemd or inetd socket + activation. + ]] + params { + @in fd: int; [[The Filedescriptor to adopt]] + } + } + + @property address { + [[The local IP or unix-local address used to create and listen. + + This will be used with bind(2) syscall. If an IP + connection, should specify the port after ":", such as + "0.0.0.0:80". + ]] + values { + address: string; + } + } + + @property flags { + [[Bitwise OR of flags to used on sockets. + + These flags are set on the server internal socket as + well as each newly created connection. + ]] + values { + flags: Efl.Net.Socket_Flags; + } + } + + @property clients_limit { + [[Number of concurrent clients accepted 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 clients_timeout { + [[Timeout (in seconds) to be set on each new client. + + This property affects only new clients and is not + applicable to the server's internal file descriptor used + to bind(2) and accept(2) new connections. + ]] + values { + timeout: double; + } + } + } + + implements { + Eo.Base.constructor; + Eo.Base.destructor; + Eo.Base.finalize; + // TODO: needed for Loop_User? Eo.Base.parent.set; + } + + constructors { + .adopt; + } +} 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..3efb72f19f --- /dev/null +++ b/src/lib/ecore_con/efl_net_server_tcp.eo @@ -0,0 +1,3 @@ +class Efl.Net.Server.TCP (Efl.Net.Server) { + data: null; +} diff --git a/src/lib/ecore_con/efl_net_server_udp.eo b/src/lib/ecore_con/efl_net_server_udp.eo new file mode 100644 index 0000000000..7bf0ee4245 --- /dev/null +++ b/src/lib/ecore_con/efl_net_server_udp.eo @@ -0,0 +1,3 @@ +class Efl.Net.Server.UDP (Efl.Net.Server) { + data: null; +} diff --git a/src/lib/ecore_con/efl_net_server_unix.eo b/src/lib/ecore_con/efl_net_server_unix.eo new file mode 100644 index 0000000000..bd15be5a40 --- /dev/null +++ b/src/lib/ecore_con/efl_net_server_unix.eo @@ -0,0 +1,5 @@ +class Efl.Net.Server.Unix (Efl.Net.Server) { + data: null; + + /* TODO: abstract, user or system socket */ +} 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..c1643cb46c --- /dev/null +++ b/src/lib/ecore_con/efl_net_socket.eo @@ -0,0 +1,286 @@ +import efl_net_types; + +// TODO: was using Eina.Binbuf, but asked to change to simpler types +struct Efl.Net.Socket.Receive_Event { + data: const(uint8) *; + size: size; +} + +struct Efl.Net.Socket.Sent_Event { + //blob: Eina.Blob; [[blob with data given to @.send]] + size: size; [[size that was sent, if no error, matches blob's size, otherwise states partial operation.]] + error: Eina.Error; [[if operation failed, notifies the error that happened]] +} + +class Efl.Net.Socket (Efl.Loop_User) { + [[Abstract class representing a network connection socket. + + A base socket is an already established connection and can send + data (see @.send method), as well as notify about incoming data + (see \@ref EFL_NET_SOCKET_EVENT_RECEIVED event). + + When the connection is closed, the socket will be automatically + deleted and the Eo event "del" is to be used. + + @since 1.19 + ]] + + events { + //received @hot: Eina.Binbuf; [[Data is available to read, + received @hot: Efl.Net.Socket.Receive_Event; [[ + + TODO: Only when using Eina.Binbuf: + + Data is available to read, + if consumed use eina_binbuf_remove(). + + To implement input flow control + used a fixed size receive buffer + (@.receive_buffer_size). When + the buffer size is fully filled, + the socket will stop reading + more data. + ]] + sent: Efl.Net.Socket.Sent_Event; [[Data specified by blob was sent.]] + + drained; [[All pending data was sent. + + This event is to implement flow control. To + avoid saturating the socket, listen this event + and only send more data once it's emitted. + + Alternatively one can specify + @.send_buffer_size and check for @.send return + code, if it's "no space left", queue locally + the data, stop producing more and wait for + \@ref EFL_NET_SOCKET_EVENT_SENT + event in order to resume it. + ]] + + closed; [[Socket was closed]] + timedout; [[Socket was inactive and timed out]] /* maybe just use error event for that? */ + + error: Eina.Error; [[An error occurred and the socket will be closed. + + The error may be in either read, send or + socket becoming invalid (remote peer + closed the connection). If it's on send, + then it will be informed also on + \@ref EFL_NET_SOCKET_EVENT_SENT + event. + + TODO: auto del the object? + ]] + } + + methods { + adopt { + [[Constructor-only method that is used to initialize the new object owning a pre-existent file descriptor. + + Usually sockets are created from a specific dialer, such + as @Efl.Net.Dialer.TCP, @Efl.Net.Dialer.UDP or + @Efl.Net.Dialer.Unix, or internally from a server such + as @Efl.Net.Server.TCP, @Efl.Net.Server.UDP or + @Efl.Net.Server.Unix. + + This method is useful if you receive the filedescriptor + from elsewhere. + ]] + params { + @in fd: int; [[The Filedescriptor to adopt]] + } + } + + send { + [[Queue data to be sent to remote. + + The API is asynchronous, thus data won't be immediately + sent to the kernel, instead it will be queued locally + and when the kernel can do a non-blocking operation, + then it will be dispatched. + + The maximum amount of bytes to be sent is defined with + @.send_buffer_size, with current usage reported as + @.send_buffer_usage. + + If @.send_buffer_usage and the new blob size exceeds + @.send_buffer_size, then an error (ENOSPC XXX TODO EINA_ERROR...) + is returned immediately, no references are taken to the + blob and no \@ref EFL_NET_SOCKET_EVENT_SENT + event will be dispatched for this blob. + + If no error is returned, then a reference is kept to the + blob and once it's fully sent, the + \@ref EFL_NET_SOCKET_EVENT_SENT + event will be dispatched with size parameter matching + the blob's size and error will be zero. If some error + occurs during the send syscall operation, the + \@ref EFL_NET_SOCKET_EVENT_SENT + event will be dispatched with size parameter less then + blob's size and error will be non-zero. In all cases, + after the \@ref EFL_NET_SOCKET_EVENT_SENT event is + dispatched, the reference to the blob will be released. + + If the object is deleted with pending blobs, the + \@ref EFL_NET_SOCKET_EVENT_SENT + event will be dispatched with an error notifying + cancellation (ECANCELED XXX TODO EINA_ERROR...) + + When all pending blobs were fully sent, then + \@ref EFL_NET_SOCKET_EVENT_DRAINED + event is dispatched. + ]] + params { + //@in data: Eina.Blob; [[data to queue for sending]] + @in data: const(uint8) * @nonull; + @in size: size; + } + + return: Eina.Error (0); [[0 on success, + ENOSPC XXX TODO EINA_ERROR if no space left]] + } + + flush { + [[Try to send as much as data without blocking. + + If all data was sent, then + \@ref EFL_NET_SOCKET_EVENT_DRAINED + event will be dispatched and true is returned. + + If the kernel can't sent more data and the operation + would block waiting, then false is returned. + + If really all data must be sent, the user should busy + wait based on @.send_buffer_usage and call @.flush in a + loop. + ]] + return: bool (false); [[true if all data was sent]] + } + + close { + [[Closes the socket, discarding all read data and pending data to be sent. + + If pending data was discarded, then + \@ref EFL_NET_SOCKET_EVENT_SENT + will be called with \@ref EFL_NET_ERROR_CANCELED. + + If all data must be delivered prior to close, do it from + \@ref EFL_NET_SOCKET_EVENT_DRAINED + event callback. + ]] + } + + steal_fd { + [[Steal the file descriptor and make this connection shallow. + + A shallow socket can't do any real operation and must be + deleted. + + XXX TODO: auto delete? + ]] + return: int (-1); [[the internal file descriptor]] + } + + @property address_local { + [[The local IP or unix-local address. + + This is analogous to getsockname(), it must return the + local address and port for IP connetions, the path for + unix socket. + ]] + get { + } + values { + address: string; + } + } + + @property address_remote { + [[The remote IP or unix-local address. + + This is Analogous to getpeername(), it must return the + remote (peer) address and port for IP connections, the + path for unix socket. + + For IP addresses, the returned value is the final IP + address, so it's already resolved. + ]] + get { + } + values { + address: string; + } + } + + @property flags { + [[Bitwise OR of flags to used on this socket.]] + values { + flags: Efl.Net.Socket_Flags; + } + } + + @property connected { + [[If the socket is still connected.]] + get { + } + values { + connected: bool; + } + } + + @property send_buffer_usage { + [[How many bytes are queued to be sent. + + If @.send_buffer_size >= 0, then this must be <= + @.send_buffer_size. + ]] + get { + } + values { + bytes: size; + } + } + + @property send_buffer_size { + [[Amount of bytes to use when sending data. + 0 is unlimited, + > 0 is an upper limit of queued bytes before + @.send returns ENOSPC]] + values { + bytes: size; + } + } + + @property receive_buffer_size { + [[Amount of bytes to use when receiving data. + 0 is unlimited, > 0 is fixed size.]] + values { + bytes: size; + } + } + + @property timeout { + [[Timeout (in seconds) to close the connection. + + If nothing is sent or received until this amount of + seconds is elapsed, then the connection will be + automatically closed and the socket object will be + deleted. + ]] + values { + timeout: double; + } + } + } + + implements { + Eo.Base.constructor; + Eo.Base.destructor; + Eo.Base.finalize; + // TODO: needed for Loop_User? Eo.Base.parent.set; + } + + constructors { + .adopt; + } +} 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..6c86600b8f --- /dev/null +++ b/src/lib/ecore_con/efl_net_socket_tcp.eo @@ -0,0 +1,3 @@ +class Efl.Net.Socket.TCP (Efl.Net.Socket) { + data: null; +} diff --git a/src/lib/ecore_con/efl_net_socket_udp.eo b/src/lib/ecore_con/efl_net_socket_udp.eo new file mode 100644 index 0000000000..810881dc98 --- /dev/null +++ b/src/lib/ecore_con/efl_net_socket_udp.eo @@ -0,0 +1,11 @@ +class Efl.Net.Socket.UDP (Efl.Net.Socket) { + data: null; + + events { + /* TODO: received,packet received,msg */ + } + + methods { + /* TODO: sendto() and sendmsg() */ + } +} diff --git a/src/lib/ecore_con/efl_net_socket_unix.eo b/src/lib/ecore_con/efl_net_socket_unix.eo new file mode 100644 index 0000000000..ac3ae54da6 --- /dev/null +++ b/src/lib/ecore_con/efl_net_socket_unix.eo @@ -0,0 +1,24 @@ +class Efl.Net.Socket.Unix (Efl.Net.Socket) { + data: null; + + events { + /* TODO: received,fd: int fd */ + } + + methods { + /* TODO: pass_fd(int fd) */ + + /* TODO: + @property credentials { + get { + } + + values { + pid: uint64; [[The process identifier of remote peer]] + uid: uint64; [[The user identifier of remote peer]] + gid: uint64; [[The group identifier of the remote peer]] + } + } + */ + } +} diff --git a/src/lib/ecore_con/efl_net_types.eot b/src/lib/ecore_con/efl_net_types.eot new file mode 100644 index 0000000000..f8b4c9d1f5 --- /dev/null +++ b/src/lib/ecore_con/efl_net_types.eot @@ -0,0 +1,19 @@ +import eina_types; + +enum Efl.Net.Socket_Flags { + none = 0, + defaults = (Efl.Net.Socket_Flags.close_on_exec | Efl.Net.Socket_Flags.non_block), + close_on_exec = (1 << 0), [[set SOCK_CLOEXEC to mark socket to be closed when the process (or a child) call exec().]] + non_block = (1 << 1), [[set SOCK_NONBLOCK, send/receive may return EAGAIN.]] + keep_alive = (1 << 2), [[set SO_KEEPALIVE on connection oriented sockets.]] + reuse_address = (1 << 3), [[set SO_REUSEADDR ]] + reuse_port = (1 << 4), [[set SO_REUSEPORT (since Linux 3.9)]] + broadcast = (1 << 5), [[set SO_BROADCAST]] + multicast = (1 << 6), [[configure multicast. Uses IPPROTO_IP with IP_ADD_MEMBERSHIP]] + tcp_nodelay = (1 << 7), [[configure TCP connections with TCP_NODELAY]] + tcp_cork = (1 << 8), [[configure TCP connections with TCP_CORK to avoid sending partial frames.]] +} + +var ERROR_NOT_CONNECTED: Eina.Error; // TODO: seems to not generate anything +var ERROR_NO_SPACE: Eina.Error; // TODO: seems to not generate anything +var ERROR_CANCELED: Eina.Error; // TODO: seems to not generate anything |