From e552e7ea14e28b1085058667177883a42fbd0004 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 22 Mar 2017 04:29:16 -0300 Subject: WIP: efl_net_{socket,dialer,server}_windows This is the local socket for windows, analogous to AF_UNIX. WIP: This is being worked out by vtorri and myself. WIP: still untested --- src/Makefile_Ecore_Con.am | 9 +- src/examples/ecore/Makefile.am | 6 +- src/examples/ecore/efl_io_copier_example.c | 72 ++- .../ecore/efl_net_dialer_windows_example.c | 212 ++++++++ src/examples/ecore/efl_net_server_example.c | 12 +- src/examples/ecore/efl_net_server_simple_example.c | 12 +- src/lib/ecore_con/Ecore_Con_Eo.h | 3 + src/lib/ecore_con/ecore_con_private.h | 22 +- src/lib/ecore_con/efl_net_dialer_windows.c | 130 +++++ src/lib/ecore_con/efl_net_dialer_windows.eo | 20 + src/lib/ecore_con/efl_net_server_windows.c | 454 ++++++++++++++++ src/lib/ecore_con/efl_net_server_windows.eo | 36 ++ src/lib/ecore_con/efl_net_socket_windows.c | 599 +++++++++++++++++++++ src/lib/ecore_con/efl_net_socket_windows.eo | 24 + 14 files changed, 1598 insertions(+), 13 deletions(-) create mode 100644 src/examples/ecore/efl_net_dialer_windows_example.c create mode 100644 src/lib/ecore_con/efl_net_dialer_windows.c create mode 100644 src/lib/ecore_con/efl_net_dialer_windows.eo create mode 100644 src/lib/ecore_con/efl_net_server_windows.c create mode 100644 src/lib/ecore_con/efl_net_server_windows.eo create mode 100644 src/lib/ecore_con/efl_net_socket_windows.c create mode 100644 src/lib/ecore_con/efl_net_socket_windows.eo diff --git a/src/Makefile_Ecore_Con.am b/src/Makefile_Ecore_Con.am index b22c3f6c0d..cfae980ed4 100644 --- a/src/Makefile_Ecore_Con.am +++ b/src/Makefile_Ecore_Con.am @@ -33,6 +33,10 @@ ecore_con_eolian_files = \ lib/ecore_con/ecore_con_eet_client_obj.eo if HAVE_WINDOWS +ecore_con_eolian_files += \ + lib/ecore_con/efl_net_socket_windows.eo \ + lib/ecore_con/efl_net_dialer_windows.eo \ + lib/ecore_con/efl_net_server_windows.eo else ecore_con_eolian_files += \ lib/ecore_con/efl_net_socket_unix.eo \ @@ -145,7 +149,10 @@ lib/ecore_con/efl_net_ssl_ctx-gnutls.c \ lib/ecore_con/efl_net_ssl_ctx-none.c if HAVE_WINDOWS -#lib_ecore_con_libecore_con_la_SOURCES += lib/ecore_con/ecore_con_local_win32.c +lib_ecore_con_libecore_con_la_SOURCES += \ +lib/ecore_con/efl_net_socket_windows.c \ +lib/ecore_con/efl_net_dialer_windows.c \ +lib/ecore_con/efl_net_server_windows.c else lib_ecore_con_libecore_con_la_SOURCES += \ lib/ecore_con/efl_net_socket_unix.c \ diff --git a/src/examples/ecore/Makefile.am b/src/examples/ecore/Makefile.am index c90e0c6f2b..e10dfc7827 100644 --- a/src/examples/ecore/Makefile.am +++ b/src/examples/ecore/Makefile.am @@ -353,7 +353,11 @@ efl_net_dialer_udp_example_LDADD = $(ECORE_CON_COMMON_LDADD) efl_net_dialer_simple_example_SOURCES = efl_net_dialer_simple_example.c efl_net_dialer_simple_example_LDADD = $(ECORE_CON_COMMON_LDADD) -if ! HAVE_WINDOWS +if HAVE_WINDOWS +EXTRA_PROGRAMS += efl_net_dialer_windows_example +efl_net_dialer_windows_example_SOURCES = efl_net_dialer_windows_example.c +efl_net_dialer_windows_example_LDADD = $(ECORE_CON_COMMON_LDADD) +else EXTRA_PROGRAMS += efl_net_dialer_unix_example efl_net_dialer_unix_example_SOURCES = efl_net_dialer_unix_example.c efl_net_dialer_unix_example_LDADD = $(ECORE_CON_COMMON_LDADD) diff --git a/src/examples/ecore/efl_io_copier_example.c b/src/examples/ecore/efl_io_copier_example.c index 5e5cbebc66..1abb486985 100644 --- a/src/examples/ecore/efl_io_copier_example.c +++ b/src/examples/ecore/efl_io_copier_example.c @@ -330,7 +330,10 @@ static const Ecore_Getopt options = { "http://address to do a GET request\n" "ws://address or wss:// to do WebSocket request (must send some data once connected)\n" "udp://IP:PORT to bind using UDP and an IPv4 (A.B.C.D:PORT) or IPv6 ([A:B:C:D::E]:PORT).\n" -#ifndef _WIN32 +#ifdef EFL_NET_DIALER_WINDOWS_CLASS + "windows://path to connect to an Windows NamedPipe server. It will have '\\\\.pipe\\' prepended.\n" +#endif +#ifdef EFL_NET_DIALER_UNIX_CLASS "unix://path to connect to an AF_UNIX server. For Linux one can create abstract sockets with unix://abstract:name.\n" #endif "ssl://IP:PORT to connect using TCP+SSL and an IPv4 (A.B.C.D:PORT) or IPv6 ([A:B:C:D::E]:PORT).\n" @@ -346,7 +349,10 @@ static const Ecore_Getopt options = { "http://address to do a PUT request\n" "ws://address or wss:// to do WebSocket request\n" "udp://IP:PORT to connect using UDP and an IPv4 (A.B.C.D:PORT) or IPv6 ([A:B:C:D::E]:PORT).\n" -#ifndef _WIN32 +#ifdef EFL_NET_DIALER_WINDOWS_CLASS + "windows://path to connect to an Windows NamedPipe server. It will have '\\\\.pipe\\' prepended.\n" +#endif +#ifdef EFL_NET_SERVER_UNIX_CLASS "unix://path to connect to an AF_UNIX server. For Linux one can create abstract sockets with unix://abstract:name.\n" #endif "ssl://IP:PORT to connect using TCP+SSL and an IPv4 (A.B.C.D:PORT) or IPv6 ([A:B:C:D::E]:PORT).\n" @@ -542,7 +548,7 @@ main(int argc, char **argv) goto end_input; } } -#ifndef _WIN32 +#ifdef EFL_NET_DIALER_UNIX_CLASS else if (strncmp(input_fname, "unix://", strlen("unix://")) == 0) { /* @@ -570,6 +576,35 @@ main(int argc, char **argv) goto end_input; } } +#endif +#ifdef EFL_NET_DIALER_WINDOWS_CLASS + else if (strncmp(input_fname, "windows://", strlen("windows://")) == 0) + { + /* + * Since Efl.Net.Socket implements the required interfaces, + * they can be used here as well. + */ + const char *address = input_fname + strlen("windows://"); + Eina_Error err; + input = efl_add(EFL_NET_DIALER_WINDOWS_CLASS, ecore_main_loop_get(), + efl_event_callback_array_add(efl_added, input_cbs(), NULL), /* optional */ + efl_event_callback_array_add(efl_added, dialer_cbs(), NULL) /* optional */ + ); + if (!input) + { + fprintf(stderr, "ERROR: could not create Windows NamedPipe Dialer.\n"); + retval = EXIT_FAILURE; + goto end; + } + + err = efl_net_dialer_dial(input, address); + if (err) + { + fprintf(stderr, "ERROR: could not Windows NamedPipe dial %s: %s\n", + address, eina_error_msg_get(err)); + goto end_input; + } + } #endif else if (strncmp(input_fname, "ssl://", strlen("ssl://")) == 0) { @@ -787,7 +822,7 @@ main(int argc, char **argv) goto end_output; } } -#ifndef _WIN32 +#ifdef EFL_NET_DIALER_UNIX_CLASS else if (strncmp(output_fname, "unix://", strlen("unix://")) == 0) { /* @@ -815,6 +850,35 @@ main(int argc, char **argv) goto end_output; } } +#endif +#ifdef EFL_NET_DIALER_WINDOWS_CLASS + else if (strncmp(output_fname, "windows://", strlen("windows://")) == 0) + { + /* + * Since Efl.Net.Socket implements the required interfaces, + * they can be used here as well. + */ + const char *address = output_fname + strlen("windows://"); + Eina_Error err; + output = efl_add(EFL_NET_DIALER_WINDOWS_CLASS, ecore_main_loop_get(), + efl_event_callback_array_add(efl_added, output_cbs(), NULL), /* optional */ + efl_event_callback_array_add(efl_added, dialer_cbs(), NULL) /* optional */ + ); + if (!output) + { + fprintf(stderr, "ERROR: could not create Windows NamedPipe Dialer.\n"); + retval = EXIT_FAILURE; + goto end_input; + } + + err = efl_net_dialer_dial(output, address); + if (err) + { + fprintf(stderr, "ERROR: could not Windows NamedPipe dial %s: %s\n", + address, eina_error_msg_get(err)); + goto end_output; + } + } #endif else if (strncmp(output_fname, "ssl://", strlen("ssl://")) == 0) { diff --git a/src/examples/ecore/efl_net_dialer_windows_example.c b/src/examples/ecore/efl_net_dialer_windows_example.c new file mode 100644 index 0000000000..fd24dc852d --- /dev/null +++ b/src/examples/ecore/efl_net_dialer_windows_example.c @@ -0,0 +1,212 @@ +#define EFL_BETA_API_SUPPORT 1 +#define EFL_EO_API_SUPPORT 1 +#include +#include +#include +#include +#include + +static int retval = EXIT_SUCCESS; +static Eina_Bool do_read = EINA_FALSE; + +static void +_connected(void *data EINA_UNUSED, const Efl_Event *event) +{ + fprintf(stderr, + "INFO: connected to '%s' (%s)\n" + "INFO: - local address=%s\n" + "INFO: - read-after-write=%u\n", + efl_net_dialer_address_dial_get(event->object), + efl_net_socket_address_remote_get(event->object), + efl_net_socket_address_local_get(event->object), + do_read); +} + +static void +_eos(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED) +{ + fprintf(stderr, "INFO: end of stream. \n"); + ecore_main_loop_quit(); +} + +static void +_can_read(void *data EINA_UNUSED, const Efl_Event *event) +{ + char buf[4]; + Eina_Rw_Slice rw_slice = EINA_SLICE_ARRAY(buf); + Eina_Error err; + Eina_Bool can_read = efl_io_reader_can_read_get(event->object); + + /* NOTE: this message may appear with can read=0 BEFORE + * "read '...'" because efl_io_reader_read() will change the status + * of can_read to FALSE prior to return so we can print it! + */ + fprintf(stderr, "INFO: can read=%d\n", can_read); + if (!can_read) return; + if (!do_read) return; + + err = efl_io_reader_read(event->object, &rw_slice); + if (err) + { + fprintf(stderr, "ERROR: could not read: %s\n", eina_error_msg_get(err)); + retval = EXIT_FAILURE; + ecore_main_loop_quit(); + return; + } + + fprintf(stderr, "INFO: read '" EINA_SLICE_STR_FMT "'\n", EINA_SLICE_STR_PRINT(rw_slice)); +} + +static void +_can_write(void *data EINA_UNUSED, const Efl_Event *event) +{ + static Eina_Slice slice = EINA_SLICE_STR_LITERAL("Hello World!"); + Eina_Slice to_write; + Eina_Error err; + Eina_Bool can_write = efl_io_writer_can_write_get(event->object); + + /* NOTE: this message may appear with can write=0 BEFORE + * "wrote '...'" because efl_io_writer_write() will change the status + * of can_write to FALSE prior to return so we can print it! + */ + fprintf(stderr, "INFO: can write=%d (wanted bytes=%zd)\n", can_write, slice.len); + if (!can_write) return; + if (slice.len == 0) return; + + to_write = slice; + err = efl_io_writer_write(event->object, &to_write, &slice); + if (err) + { + fprintf(stderr, "ERROR: could not write: %s\n", eina_error_msg_get(err)); + retval = EXIT_FAILURE; + ecore_main_loop_quit(); + return; + } + + fprintf(stderr, "INFO: wrote '" EINA_SLICE_STR_FMT "', still pending=%zd bytes\n", EINA_SLICE_STR_PRINT(to_write), slice.len); + + if ((!do_read) && (slice.len == 0)) + { + retval = EXIT_SUCCESS; + ecore_main_loop_quit(); + return; + } +} + +static void +_resolved(void *data EINA_UNUSED, const Efl_Event *event) +{ + fprintf(stderr, "INFO: resolved %s => %s\n", + efl_net_dialer_address_dial_get(event->object), + efl_net_socket_address_remote_get(event->object)); +} + +static void +_error(void *data EINA_UNUSED, const Efl_Event *event) +{ + const Eina_Error *perr = event->info; + fprintf(stderr, "INFO: error: %d '%s'\n", *perr, eina_error_msg_get(*perr)); + retval = EXIT_FAILURE; +} + +EFL_CALLBACKS_ARRAY_DEFINE(dialer_cbs, + { EFL_NET_DIALER_EVENT_CONNECTED, _connected }, + { EFL_NET_DIALER_EVENT_RESOLVED, _resolved }, + { EFL_NET_DIALER_EVENT_ERROR, _error }, + { EFL_IO_READER_EVENT_EOS, _eos }, + { EFL_IO_READER_EVENT_CAN_READ_CHANGED, _can_read }, + { EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, _can_write } + ); + +static const Ecore_Getopt options = { + "efl_net_dialer_windows_example", /* program name */ + NULL, /* usage line */ + "1", /* version */ + "(C) 2017 Enlightenment Project", /* copyright */ + "BSD 2-Clause", /* license */ + /* long description, may be multiline and contain \n */ + "Example of Efl_Net_Dialer_Windows usage, sending a message and receiving a reply\n", + EINA_FALSE, + { + ECORE_GETOPT_STORE_TRUE('r', "read-after-write", "Do a read after writes are done."), + 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, + "The address (URL) to dial", "address"), + ECORE_GETOPT_SENTINEL + } +}; + +int +main(int argc, char **argv) +{ + char *address = NULL; + Eina_Bool quit_option = EINA_FALSE; + Ecore_Getopt_Value values[] = { + ECORE_GETOPT_VALUE_BOOL(do_read), + + /* 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(address), + + ECORE_GETOPT_VALUE_NONE /* sentinel */ + }; + int args; + Eo *dialer, *loop; + 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; + + loop = ecore_main_loop_get(); + + 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; + } + + dialer = efl_add(EFL_NET_DIALER_WINDOWS_CLASS, loop, + efl_name_set(efl_added, "dialer"), + efl_event_callback_array_add(efl_added, dialer_cbs(), NULL)); + + err = efl_net_dialer_dial(dialer, address); + if (err != 0) + { + fprintf(stderr, "ERROR: could not dial '%s': %s", + address, eina_error_msg_get(err)); + goto no_mainloop; + } + + ecore_main_loop_begin(); + + fprintf(stderr, "INFO: main loop finished.\n"); + + no_mainloop: + efl_del(dialer); + + 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 index 3a9598e2cb..c3b19ec3fc 100644 --- a/src/examples/ecore/efl_net_server_example.c +++ b/src/examples/ecore/efl_net_server_example.c @@ -461,7 +461,10 @@ static const char * protocols[] = { "tcp", "udp", "ssl", -#ifndef _WIN32 +#ifdef EFL_NET_SERVER_WINDOWS_CLASS + "windows", +#endif +#ifdef EFL_NET_SERVER_UNIX_CLASS "unix", #endif NULL @@ -622,7 +625,10 @@ main(int argc, char **argv) if (strcmp(protocol, "tcp") == 0) cls = EFL_NET_SERVER_TCP_CLASS; else if (strcmp(protocol, "udp") == 0) cls = EFL_NET_SERVER_UDP_CLASS; else if (strcmp(protocol, "ssl") == 0) cls = EFL_NET_SERVER_SSL_CLASS; -#ifndef _WIN32 +#ifdef EFL_NET_SERVER_WINDOWS_CLASS + else if (strcmp(protocol, "windows") == 0) cls = EFL_NET_SERVER_WINDOWS_CLASS; +#endif +#ifdef EFL_NET_SERVER_UNIX_CLASS else if (strcmp(protocol, "unix") == 0) cls = EFL_NET_SERVER_UNIX_CLASS; #endif else @@ -703,7 +709,7 @@ main(int argc, char **argv) efl_net_server_ssl_reuse_port_set(server, EINA_TRUE); /* optional, but nice for testing... not secure unless you know what you're doing */ if (socket_activated) efl_net_server_ssl_socket_activate(server, address); } -#ifndef _WIN32 +#ifdef EFL_NET_SERVER_UNIX_CLASS else if (cls == EFL_NET_SERVER_UNIX_CLASS) { efl_net_server_unix_unlink_before_bind_set(server, EINA_TRUE); /* makes testing easier */ diff --git a/src/examples/ecore/efl_net_server_simple_example.c b/src/examples/ecore/efl_net_server_simple_example.c index 548dd92ccc..ddce4c86cd 100644 --- a/src/examples/ecore/efl_net_server_simple_example.c +++ b/src/examples/ecore/efl_net_server_simple_example.c @@ -265,7 +265,10 @@ static const char * protocols[] = { "tcp", "udp", "ssl", -#ifndef _WIN32 +#ifdef EFL_NET_SERVER_WINDOWS_CLASS + "windows", +#endif +#ifdef EFL_NET_SERVER_UNIX_CLASS "unix", #endif NULL @@ -426,7 +429,10 @@ main(int argc, char **argv) if (strcmp(protocol, "tcp") == 0) cls = EFL_NET_SERVER_TCP_CLASS; else if (strcmp(protocol, "udp") == 0) cls = EFL_NET_SERVER_UDP_CLASS; else if (strcmp(protocol, "ssl") == 0) cls = EFL_NET_SERVER_SSL_CLASS; -#ifndef _WIN32 +#ifdef EFL_NET_SERVER_WINDOWS_CLASS + else if (strcmp(protocol, "windows") == 0) cls = EFL_NET_SERVER_WINDOWS_CLASS; +#endif +#ifdef EFL_NET_SERVER_UNIX_CLASS else if (strcmp(protocol, "unix") == 0) cls = EFL_NET_SERVER_UNIX_CLASS; #endif else @@ -511,7 +517,7 @@ main(int argc, char **argv) efl_net_server_ssl_reuse_port_set(server, EINA_TRUE); /* optional, but nice for testing... not secure unless you know what you're doing */ if (socket_activated) efl_net_server_ssl_socket_activate(server, address); } -#ifndef _WIN32 +#ifdef EFL_NET_SERVER_UNIX_CLASS else if (cls == EFL_NET_SERVER_UNIX_CLASS) { efl_net_server_unix_unlink_before_bind_set(server, EINA_TRUE); /* makes testing easier */ diff --git a/src/lib/ecore_con/Ecore_Con_Eo.h b/src/lib/ecore_con/Ecore_Con_Eo.h index 03ade071de..9825a2b2a6 100644 --- a/src/lib/ecore_con/Ecore_Con_Eo.h +++ b/src/lib/ecore_con/Ecore_Con_Eo.h @@ -18,6 +18,9 @@ #include "efl_net_server_tcp.eo.h" #ifdef _WIN32 +#include "efl_net_socket_windows.eo.h" +#include "efl_net_dialer_windows.eo.h" +#include "efl_net_server_windows.eo.h" #else #include "efl_net_socket_unix.eo.h" #include "efl_net_dialer_unix.eo.h" diff --git a/src/lib/ecore_con/ecore_con_private.h b/src/lib/ecore_con/ecore_con_private.h index 42f6dedef7..20c76fa000 100644 --- a/src/lib/ecore_con/ecore_con_private.h +++ b/src/lib/ecore_con/ecore_con_private.h @@ -170,7 +170,27 @@ void _ecore_con_local_mkpath(const char *path, mode_t mode); void _efl_net_server_udp_client_init(Eo *client, SOCKET fd, const struct sockaddr *addr, socklen_t addrlen, const char *str); void _efl_net_server_udp_client_feed(Eo *client, Eina_Rw_Slice slice); -#ifndef _WIN32 +#ifdef EFL_NET_SOCKET_WINDOWS_CLASS +Eina_Error _efl_net_socket_windows_init(Eo *o, HANDLE h); +Eina_Error _efl_net_socket_windows_io_start(Eo *o); +HANDLE _efl_net_socket_windows_handle_get(const Eo *o); + +typedef struct _Efl_Net_Socket_Windows_Operation Efl_Net_Socket_Windows_Operation; +typedef void (*Efl_Net_Socket_Windows_Operation_Success_Cb)(void *data, Eo *sock, Eina_Rw_Slice slice); +typedef void (*Efl_Net_Socket_Windows_Operation_Failure_Cb)(void *data, Eo *sock, Eina_Error err); + +Efl_Net_Socket_Windows_Operation *_efl_net_socket_windows_operation_new(Eo *sock, Efl_Net_Socket_Windows_Operation_Success_Cb success_cb, Efl_Net_Socket_Windows_Operation_Failure_Cb failure_cb, const void *data); +void _efl_net_socket_windows_operation_failed(Efl_Net_Socket_Windows_Operation *op, Eina_Error err); +void _efl_net_socket_windows_operation_succeeded(Efl_Net_Socket_Windows_Operation *op, Eina_Rw_Slice slice); + +static inline OVERLAPPED * +_efl_net_socket_windows_operation_overlapped_get(Efl_Net_Socket_Windows_Operation *op) +{ + return (OVERLAPPED *)op; +} +#endif + +#ifdef EFL_NET_SOCKET_UNIX_CLASS Eina_Bool efl_net_unix_fmt(char *buf, size_t buflen, SOCKET fd, const struct sockaddr_un *addr, socklen_t addrlen); #endif Eina_Bool efl_net_ip_port_parse(const char *address, struct sockaddr_storage *storage); diff --git a/src/lib/ecore_con/efl_net_dialer_windows.c b/src/lib/ecore_con/efl_net_dialer_windows.c new file mode 100644 index 0000000000..e3ce95f3ea --- /dev/null +++ b/src/lib/ecore_con/efl_net_dialer_windows.c @@ -0,0 +1,130 @@ +#define EFL_NET_SOCKET_WINDOWS_PROTECTED 1 +#define EFL_IO_READER_PROTECTED 1 +#define EFL_IO_WRITER_PROTECTED 1 +#define EFL_IO_CLOSER_PROTECTED 1 +#define EFL_NET_DIALER_PROTECTED 1 +#define EFL_NET_SOCKET_PROTECTED 1 + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 +#elif _WIN32_WINNT < 0x0600 +#error "This version of Windows is too old" +#endif + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "Ecore.h" +#include "Ecore_Con.h" +#include "ecore_con_private.h" + +#define MY_CLASS EFL_NET_DIALER_WINDOWS_CLASS + +typedef struct _Efl_Net_Dialer_Windows_Data +{ + Eina_Stringshare *address_dial; + double timeout_dial; + Eina_Bool connected; +} Efl_Net_Dialer_Windows_Data; + +EOLIAN static void +_efl_net_dialer_windows_efl_object_destructor(Eo *o, Efl_Net_Dialer_Windows_Data *pd) +{ + efl_destructor(efl_super(o, MY_CLASS)); + + eina_stringshare_replace(&pd->address_dial, NULL); +} + +EOLIAN static Eina_Error +_efl_net_dialer_windows_efl_net_dialer_dial(Eo *o, Efl_Net_Dialer_Windows_Data *pd, const char *address) +{ + Eina_Error err; + HANDLE h; + + EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(strchr(address, '/') != NULL, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(strchr(address, '\\') != NULL, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(strlen("\\\\.pipe\\") + strlen(address) >= 256, 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); + + efl_net_dialer_address_dial_set(o, address); + + h = CreateFile(pd->address_dial, + FILE_READ_ATTRIBUTES | FILE_READ_DATA | + FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + if (h == INVALID_HANDLE_VALUE) + return GetLastError(); + + // TODO vtorri: will this CreateFile() take a while if the server + // waits to accept? If so, we may need to move this to an + // Ecore_Thread and call _efl_net_socket_windows_init() and the + // rest of this function from "end_cb" + + err = _efl_net_socket_windows_init(o, h); + if (err) + { + CloseHandle(h); + return err; + } + + efl_net_socket_address_remote_set(o, efl_net_dialer_address_dial_get(o)); + efl_net_socket_address_local_set(o, "TODO"); // TODO vtorri: can we get the local peer address, like getsockname()? + efl_event_callback_call(o, EFL_NET_DIALER_EVENT_RESOLVED, NULL); + efl_net_dialer_connected_set(o, EINA_TRUE); + + return _efl_net_socket_windows_io_start(o); +} + +EOLIAN static void +_efl_net_dialer_windows_efl_net_dialer_address_dial_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Windows_Data *pd, const char *address) +{ + const char *tmp = eina_stringshare_printf("\\\\.pipe\\%s", address); + eina_stringshare_del(pd->address_dial); + pd->address_dial = tmp; +} + +EOLIAN static const char * +_efl_net_dialer_windows_efl_net_dialer_address_dial_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Windows_Data *pd) +{ + return pd->address_dial + strlen("\\\\.pipe\\");; +} + +EOLIAN static void +_efl_net_dialer_windows_efl_net_dialer_connected_set(Eo *o, Efl_Net_Dialer_Windows_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_windows_efl_net_dialer_connected_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Windows_Data *pd) +{ + return pd->connected; +} + +EOLIAN static void +_efl_net_dialer_windows_efl_net_dialer_timeout_dial_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Windows_Data *pd, double seconds) +{ + pd->timeout_dial = seconds; +} + +EOLIAN static double +_efl_net_dialer_windows_efl_net_dialer_timeout_dial_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Windows_Data *pd) +{ + return pd->timeout_dial; +} + +EOLIAN static Eina_Error +_efl_net_dialer_windows_efl_io_closer_close(Eo *o, Efl_Net_Dialer_Windows_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_windows.eo.c" diff --git a/src/lib/ecore_con/efl_net_dialer_windows.eo b/src/lib/ecore_con/efl_net_dialer_windows.eo new file mode 100644 index 0000000000..5432bcb766 --- /dev/null +++ b/src/lib/ecore_con/efl_net_dialer_windows.eo @@ -0,0 +1,20 @@ +class Efl.Net.Dialer.Windows (Efl.Net.Socket.Windows, Efl.Net.Dialer) { + [[Connects to a Windows NamedPipe server. + + The dial address will have "\\.pipe\" prepended as required by + Windows CreateNamedPipe(). + + \@note Proxies are meaningless, thus are not implemented. + + @since 1.19 + ]] + + implements { + Efl.Object.destructor; + Efl.Net.Dialer.dial; [[address parameter will have "\\.pipe\" prepended]] + Efl.Net.Dialer.address_dial { get; set; } + Efl.Net.Dialer.connected { get; set; } + Efl.Net.Dialer.timeout_dial { get; set; } + Efl.Io.Closer.close; + } +} diff --git a/src/lib/ecore_con/efl_net_server_windows.c b/src/lib/ecore_con/efl_net_server_windows.c new file mode 100644 index 0000000000..2c7021e961 --- /dev/null +++ b/src/lib/ecore_con/efl_net_server_windows.c @@ -0,0 +1,454 @@ +#define EFL_NET_SERVER_WINDOWS_PROTECTED 1 +#define EFL_NET_SERVER_PROTECTED 1 +#define EFL_NET_SOCKET_PROTECTED 1 + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 +#elif _WIN32_WINNT < 0x0600 +#error "This version of Windows is too old" +#endif + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "Ecore.h" +#include "Ecore_Con.h" +#include "ecore_con_private.h" + +/* + * See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365601(v=vs.85).aspx + * Named Pipe Server Using Completion Routines + * + * See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365603(v=vs.85).aspx + * Named Pipe Server Using Overlapped I/O + * + * Each instance (PIPEINST) is an Efl_Net_Socket_Windows. Instead of + * pre-creating all possible instances and having all of them to + * accept connections (ConnectNamedPipe()), we create a single one + * (Efl_Net_Server_Windows_Data::next_client), once it's connected we + * announce the client and if it's used, a new "next_client" is + * started, otherwise if the announced client is not used, then it's + * disconnected and reused with a new ConnectNamedPipe(). + */ + +#define MY_CLASS EFL_NET_SERVER_WINDOWS_CLASS + +typedef struct _Efl_Net_Server_Windows_Data +{ + Eo *next_client; + Eina_List *pending_clients; + + Eina_Stringshare *address; /* includes prefix: \\.pipe\, returned without it */ + Efl_Future *pending_announcer_job; + unsigned int clients_count; + unsigned int clients_limit; + Eina_Bool clients_reject_excess; + Eina_Bool serving; + Eina_Bool first; + Eina_Bool allow_remote; +} Efl_Net_Server_Windows_Data; + +static Eina_Error _efl_net_server_windows_client_listen(Eo *o, Efl_Net_Server_Windows_Data *pd); +static Eina_Error _efl_net_server_windows_client_new(Eo *o, Efl_Net_Server_Windows_Data *pd); + +static void +_efl_net_server_windows_client_listen_success(void *data, Eo *client, Eina_Rw_Slice slice EINA_UNUSED) +{ + Eo *o = data; + Efl_Net_Server_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); + char str[256]; + + EINA_SAFETY_ON_NULL_RETURN(pd); + + snprintf(str, sizeof(str), "XXXTODO"); // TODO vtorri: do we have a way to identify the client? + + DBG("server=%p received incoming connection at %s (%s)", o, efl_net_server_address_get(o), str); + + efl_ref(o); /* will trigger events, which call user which may delete us */ + + if ((pd->clients_limit > 0) && (pd->clients_count >= pd->clients_limit)) + { + if (!pd->clients_reject_excess) + { + /* keep queueing, but do not call user */ + + pd->pending_clients = eina_list_append(pd->pending_clients, client); + if (pd->next_client == client) + pd->next_client = NULL; + + efl_net_socket_address_local_set(client, efl_net_server_address_get(o)); + efl_net_socket_address_remote_set(client, str); + + DBG("server=%p queued client %p", o, client); + } + else + { + DBG("server=%p rejecting client %p", o, client); + efl_event_callback_call(o, EFL_NET_SERVER_EVENT_CLIENT_REJECTED, str); + + if (pd->next_client != client) + efl_del(client); + else + { + HANDLE h = _efl_net_socket_windows_handle_get(client); + DisconnectNamedPipe(h); + + /* reuse existing pipe for a new connection */ + _efl_net_server_windows_client_listen(o, pd); + } + } + } + else + { + DBG("server=%p announcing client %p", o, client); + if (pd->next_client == client) + pd->next_client = NULL; + efl_net_server_client_announce(o, client); + } + + if (!pd->next_client) + _efl_net_server_windows_client_new(o, pd); + + efl_unref(o); +} + +static void +_efl_net_server_windows_client_listen_failure(void *data, Eo *client EINA_UNUSED, Eina_Error err) +{ + Eo *o = data; + + WRN("server=%p failed to accept connection at %s: #%d %s", + o, efl_net_server_address_get(o), err, eina_error_msg_get(err)); + + efl_event_callback_call(o, EFL_NET_SERVER_EVENT_ERROR, &err); + + // TODO: create a new one on failure? +} + +static Eina_Error +_efl_net_server_windows_client_listen(Eo *o, Efl_Net_Server_Windows_Data *pd) +{ + Efl_Net_Socket_Windows_Operation *op; + HANDLE h; + OVERLAPPED *ovl; + Eina_Error err = 0; + + op = _efl_net_socket_windows_operation_new(pd->next_client, + _efl_net_server_windows_client_listen_success, + _efl_net_server_windows_client_listen_failure, + o); + + EINA_SAFETY_ON_NULL_RETURN_VAL(op, EINVAL); + + h = _efl_net_socket_windows_handle_get(pd->next_client); + ovl = _efl_net_socket_windows_operation_overlapped_get(op); + + DBG("server=%p connecting to %s...", o, efl_net_server_address_get(o)); + + if (!ConnectNamedPipe(h, ovl)) + { + err = GetLastError(); + if (err == ERROR_IO_PENDING) + return 0; + else if (err == ERROR_PIPE_CONNECTED) + { + _efl_net_socket_windows_operation_succeeded(op, (Eina_Rw_Slice){}); + return 0; + } + else + { + _efl_net_socket_windows_operation_failed(op, err); + return err; + } + } + + _efl_net_socket_windows_operation_failed(op, EINVAL); + return EINVAL; +} + +static Eina_Error +_efl_net_server_windows_client_new(Eo *o, Efl_Net_Server_Windows_Data *pd) +{ + Eina_Error err; + HANDLE h; + + h = CreateNamedPipe(pd->address, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | + pd->first ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | + pd->allow_remote ? PIPE_ACCEPT_REMOTE_CLIENTS : PIPE_REJECT_REMOTE_CLIENTS, + pd->clients_limit > 0 ? pd->clients_limit : PIPE_UNLIMITED_INSTANCES, + 4096, 4096, INFINITE, NULL); + if (h == INVALID_HANDLE_VALUE) + return GetLastError(); + + pd->next_client = efl_add(EFL_NET_SOCKET_WINDOWS_CLASS, o, + efl_io_closer_close_on_destructor_set(efl_added, EINA_TRUE)); + if (!pd->next_client) + { + err = ENOMEM; + goto error_socket; + } + + err = _efl_net_socket_windows_init(pd->next_client, h); + if (err) goto error_init; + + pd->first = EINA_FALSE; + + err = _efl_net_server_windows_client_listen(o, pd); + if (err) return err; + + efl_net_server_serving_set(o, EINA_TRUE); + return 0; + + error_init: + efl_del(pd->next_client); + error_socket: + CloseHandle(h); + return err; +} + +EOLIAN static void +_efl_net_server_windows_allow_remote_set(Eo *o EINA_UNUSED, Efl_Net_Server_Windows_Data *pd, Eina_Bool allow_remote) +{ + pd->allow_remote = allow_remote; +} + +EOLIAN static Eina_Bool +_efl_net_server_windows_allow_remote_get(Eo *o EINA_UNUSED, Efl_Net_Server_Windows_Data *pd) +{ + return pd->allow_remote; +} + +EOLIAN static Eo * +_efl_net_server_windows_efl_object_constructor(Eo *o, Efl_Net_Server_Windows_Data *pd) +{ + pd->first = EINA_TRUE; + + return efl_constructor(efl_super(o, MY_CLASS)); +} + +EOLIAN static void +_efl_net_server_windows_efl_object_destructor(Eo *o, Efl_Net_Server_Windows_Data *pd) +{ + if (pd->next_client) + { + efl_del(pd->next_client); + pd->next_client = NULL; + } + + while (pd->pending_clients) + { + Eo *client = pd->pending_clients->data; + pd->pending_clients = eina_list_remove_list(pd->pending_clients, pd->pending_clients); + efl_del(client); + } + + efl_destructor(efl_super(o, MY_CLASS)); +} + +EOLIAN static void +_efl_net_server_windows_efl_net_server_address_set(Eo *o EINA_UNUSED, Efl_Net_Server_Windows_Data *pd, const char *address) +{ + const char *tmp = eina_stringshare_printf("\\\\.pipe\\%s", address); + eina_stringshare_del(pd->address); + pd->address = tmp; +} + +EOLIAN static const char * +_efl_net_server_windows_efl_net_server_address_get(Eo *o EINA_UNUSED, Efl_Net_Server_Windows_Data *pd) +{ + return pd->address + strlen("\\\\.pipe\\"); +} + +static void +_efl_net_server_windows_pending_announce_job(void *data, const Efl_Event *ev EINA_UNUSED) +{ + Eo *o = data; + Efl_Net_Server_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); + Eo *client; + + pd->pending_announcer_job = NULL; + + if (!pd->pending_clients) return; + if ((pd->clients_limit > 0) && (pd->clients_limit <= pd->clients_count)) return; + + client = pd->pending_clients->data; + pd->pending_clients = eina_list_remove_list(pd->pending_clients, pd->pending_clients); + + efl_net_server_client_announce(o, client); +} + +static void +_efl_net_server_windows_pending_announce_job_schedule(Eo *o, Efl_Net_Server_Windows_Data *pd) +{ + Eo *loop; + + if (pd->pending_announcer_job) return; + if (!pd->pending_clients) return; + if ((pd->clients_limit > 0) && (pd->clients_limit <= pd->clients_count)) return; + + loop = efl_loop_get(o); + if (!loop) return; + efl_future_use(&pd->pending_announcer_job, efl_loop_job(loop, o)); + efl_future_then(pd->pending_announcer_job, _efl_net_server_windows_pending_announce_job, NULL, NULL, o); + efl_future_link(o, pd->pending_announcer_job); +} + +EOLIAN static void +_efl_net_server_windows_efl_net_server_clients_count_set(Eo *o, Efl_Net_Server_Windows_Data *pd, unsigned int count) +{ + pd->clients_count = count; + + /* a job to avoid blowing the stack with recursion, + * do each announcement from main loop + */ + _efl_net_server_windows_pending_announce_job_schedule(o, pd); +} + +EOLIAN static unsigned int +_efl_net_server_windows_efl_net_server_clients_count_get(Eo *o EINA_UNUSED, Efl_Net_Server_Windows_Data *pd) +{ + return pd->clients_count; +} + +EOLIAN static void +_efl_net_server_windows_efl_net_server_clients_limit_set(Eo *o EINA_UNUSED, Efl_Net_Server_Windows_Data *pd, unsigned int limit, Eina_Bool reject_excess) +{ + pd->clients_limit = limit; + pd->clients_reject_excess = reject_excess; + + if ((limit > 0) && (reject_excess)) + { + while (pd->pending_clients) + { + Eina_List *last = eina_list_last(pd->pending_clients); + Eo *client = eina_list_data_get(last); + efl_del(client); + pd->pending_clients = eina_list_remove_list(pd->pending_clients, last); + } + } + + _efl_net_server_windows_pending_announce_job_schedule(o, pd); +} + +EOLIAN static void +_efl_net_server_windows_efl_net_server_clients_limit_get(Eo *o EINA_UNUSED, Efl_Net_Server_Windows_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_windows_efl_net_server_serving_set(Eo *o EINA_UNUSED, Efl_Net_Server_Windows_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_Bool +_efl_net_server_windows_efl_net_server_serving_get(Eo *o EINA_UNUSED, Efl_Net_Server_Windows_Data *pd) +{ + return pd->serving; +} + +EOLIAN static Eina_Error +_efl_net_server_windows_efl_net_server_serve(Eo *o, Efl_Net_Server_Windows_Data *pd, const char *address) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(strchr(address, '/') != NULL, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(strchr(address, '\\') != NULL, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(strlen("\\\\.pipe\\") + strlen(address) >= 256, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->serving, EALREADY); + + efl_net_server_address_set(o, address); + + return _efl_net_server_windows_client_new(o, pd); +} + +static void +_efl_net_server_windows_client_event_closed(void *data, const Efl_Event *event) +{ + Eo *server = data; + Eo *client = event->object; + + efl_event_callback_del(client, EFL_IO_CLOSER_EVENT_CLOSED, _efl_net_server_windows_client_event_closed, 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); +} + +EOLIAN static Eina_Bool +_efl_net_server_windows_efl_net_server_client_announce(Eo *o, Efl_Net_Server_Windows_Data *pd, Efl_Net_Socket *client) +{ + Eina_Error err; + + EINA_SAFETY_ON_NULL_RETURN_VAL(client, EINA_FALSE); + EINA_SAFETY_ON_FALSE_GOTO(efl_isa(client, EFL_NET_SOCKET_INTERFACE), wrong_type); + EINA_SAFETY_ON_FALSE_GOTO(efl_parent_get(client) == o, wrong_parent); + efl_event_callback_call(o, EFL_NET_SERVER_EVENT_CLIENT_ADD, client); + + if (efl_parent_get(client) != o) + { + DBG("client %s was reparented! Ignoring it...", + efl_net_socket_address_remote_get(client)); + return EINA_TRUE; + } + + 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)); + if (pd->next_client) + efl_del(client); + else + { + HANDLE h = _efl_net_socket_windows_handle_get(client); + DisconnectNamedPipe(h); + + /* reuse existing pipe for a new connection */ + pd->next_client = client; + _efl_net_server_windows_client_listen(o, pd); + } + return EINA_FALSE; + } + else if (efl_io_closer_closed_get(client)) + { + DBG("client %s was closed from 'client,add', delete it...", + efl_net_socket_address_remote_get(client)); + efl_del(client); + return EINA_FALSE; + } + + efl_net_server_clients_count_set(o, efl_net_server_clients_count_get(o) + 1); + efl_event_callback_add(client, EFL_IO_CLOSER_EVENT_CLOSED, _efl_net_server_windows_client_event_closed, o); + + err = _efl_net_socket_windows_io_start(client); + if (err) + { + WRN("server=%p client=%p failed to start I/O: %s", o, client, eina_error_msg_get(err)); + if (!efl_io_closer_closed_get(client)) + efl_io_closer_close(client); + } + + return EINA_TRUE; + + wrong_type: + ERR("%p client %p (%s) doesn't implement Efl.Net.Socket interface, deleting it.", o, client, efl_class_name_get(efl_class_get(client))); + efl_io_closer_close(client); + efl_del(client); + return EINA_FALSE; + + wrong_parent: + ERR("%p client %p (%s) parent=%p is not our child, deleting it.", o, client, efl_class_name_get(efl_class_get(client)), efl_parent_get(client)); + efl_io_closer_close(client); + efl_del(client); + return EINA_FALSE; + +} + +#include "efl_net_server_windows.eo.c" diff --git a/src/lib/ecore_con/efl_net_server_windows.eo b/src/lib/ecore_con/efl_net_server_windows.eo new file mode 100644 index 0000000000..74c4e51917 --- /dev/null +++ b/src/lib/ecore_con/efl_net_server_windows.eo @@ -0,0 +1,36 @@ +class Efl.Net.Server.Windows (Efl.Loop_User, Efl.Net.Server) { + [[A Windows NamedPipe server. + + The @Efl.Net.Server.serve method will call CreateNamedPipe() + directly, thus path will be accessed and created in that + method. If the created socket must be subject to some special + mode or user, change before executing that method. + + @since 1.19 + ]] + + methods { + @property allow_remote { + [[If server allows remote (different machine) clients. + + If this property is $true, then it will allow clients to + connect from remote machines. If $false (default), then + just local clients are allowed. + ]] + values { + allow_remote: bool; [[If $true, server will allow remote machines to connect.]] + } + } + } + + implements { + Efl.Object.constructor; + Efl.Object.destructor; + Efl.Net.Server.address { get; set; } + Efl.Net.Server.clients_count { get; set; } + Efl.Net.Server.clients_limit { get; set; } + Efl.Net.Server.serving { get; set; } + Efl.Net.Server.serve; [[address parameter will have "\\.pipe\" prepended]] + Efl.Net.Server.client_announce; + } +} diff --git a/src/lib/ecore_con/efl_net_socket_windows.c b/src/lib/ecore_con/efl_net_socket_windows.c new file mode 100644 index 0000000000..514c80c9c8 --- /dev/null +++ b/src/lib/ecore_con/efl_net_socket_windows.c @@ -0,0 +1,599 @@ +#define EFL_NET_SOCKET_WINDOWS_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 + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 +#elif _WIN32_WINNT < 0x0600 +#error "This version of Windows is too old" +#endif + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "Ecore.h" +#include "Ecore_Con.h" +#include "ecore_con_private.h" + +#define MY_CLASS EFL_NET_SOCKET_WINDOWS_CLASS + +#define BUFFER_SIZE (4 * 4096) + +typedef struct _Efl_Net_Socket_Windows_Data +{ + Eina_Stringshare *address_local; + Eina_Stringshare *address_remote; + Eina_List *pending_ops; + struct { + union { + uint8_t *bytes; + void *mem; + }; + DWORD len; + DWORD used; + Eina_Bool pending; + } recv; + struct { + union { + uint8_t *bytes; + void *mem; + }; + DWORD len; + DWORD used; + Eina_Bool pending; + } send; + HANDLE handle; + PTP_IO io; + Eina_Bool can_read; + Eina_Bool eos; + Eina_Bool pending_eos; + Eina_Bool can_write; + Eina_Bool io_started; +} Efl_Net_Socket_Windows_Data; + +struct _Efl_Net_Socket_Windows_Operation +{ + OVERLAPPED base; + Efl_Net_Socket_Windows_Operation_Success_Cb success_cb; + Efl_Net_Socket_Windows_Operation_Failure_Cb failure_cb; + const void *data; + Eo *o; + Eina_Bool deleting; +}; + +Efl_Net_Socket_Windows_Operation * +_efl_net_socket_windows_operation_new(Eo *o, Efl_Net_Socket_Windows_Operation_Success_Cb success_cb, Efl_Net_Socket_Windows_Operation_Failure_Cb failure_cb, const void *data) +{ + Efl_Net_Socket_Windows_Operation *op; + Efl_Net_Socket_Windows_Data *pd = efl_data_scope_get(o, EFL_NET_SOCKET_WINDOWS_CLASS); + + EINA_SAFETY_ON_NULL_RETURN_VAL(pd, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(success_cb, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(failure_cb, NULL); + + op = calloc(1, sizeof(*op)); + EINA_SAFETY_ON_NULL_RETURN_VAL(op, NULL); + + op->success_cb = success_cb; + op->failure_cb = failure_cb; + op->data = data; + op->o = o; + pd->pending_ops = eina_list_append(pd->pending_ops, op); + StartThreadpoolIo(pd->io); + + // TODO vtorri: I'm not sure the same pd->io can be used for concurrent + // operations, like read + write. If it's not, then we'll have to + // CreateThreadpoolIo() for each operation (op->io)... + + DBG("op=%p (socket=%p) success_cb=%p failure_cb=%p data=%p", + op, op->o, op->success_cb, op->failure_cb, op->data); + + return op; +} + +static void +_efl_net_socket_windows_operation_done(Efl_Net_Socket_Windows_Operation *op, Eina_Error err, Eina_Rw_Slice slice) +{ + Efl_Net_Socket_Windows_Data *pd; + + DBG("op=%p (socket=%p), success_cb=%p, failure_cb=%p, data=%p, err=%d (%s), slice=" EINA_SLICE_FMT, + op, op->o, op->success_cb, op->failure_cb, op->data, + err, eina_error_msg_get(err), EINA_SLICE_PRINT(slice)); + + op->deleting = EINA_TRUE; + + pd = efl_data_scope_get(op->o, EFL_NET_SOCKET_WINDOWS_CLASS); + if (pd) + { + pd->pending_ops = eina_list_remove(pd->pending_ops, op); + CancelThreadpoolIo(pd->io); + } + + if (err) + op->failure_cb((void *)op->data, op->o, err); + else + op->success_cb((void *)op->data, op->o, slice); + + free(op); +} + +void +_efl_net_socket_windows_operation_failed(Efl_Net_Socket_Windows_Operation *op, Eina_Error err) +{ + EINA_SAFETY_ON_NULL_RETURN(op); + EINA_SAFETY_ON_TRUE_RETURN(op->deleting); + + _efl_net_socket_windows_operation_done(op, err, (Eina_Rw_Slice){}); +} + +void +_efl_net_socket_windows_operation_succeeded(Efl_Net_Socket_Windows_Operation *op, Eina_Rw_Slice slice) +{ + EINA_SAFETY_ON_NULL_RETURN(op); + EINA_SAFETY_ON_TRUE_RETURN(op->deleting); + + _efl_net_socket_windows_operation_done(op, 0, slice); +} + +static void CALLBACK +_efl_net_socket_windows_io_completed(PTP_CALLBACK_INSTANCE inst EINA_UNUSED, + PVOID data, + PVOID overlapped, + ULONG result, + ULONG_PTR bytes_nbr, + PTP_IO io EINA_UNUSED) +{ + Eo *o = data; + Efl_Net_Socket_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); + Efl_Net_Socket_Windows_Operation *op = overlapped; + + EINA_SAFETY_ON_NULL_RETURN(pd); + + // TODO: check if this happens on the main thread, otherwise migrate to main! + + if (result == NO_ERROR) + _efl_net_socket_windows_operation_succeeded(op, (Eina_Rw_Slice){.len = bytes_nbr}); // TODO k-s: slice with actual memory + else + _efl_net_socket_windows_operation_failed(op, result); +} + +Eina_Error +_efl_net_socket_windows_init(Eo *o, HANDLE h) +{ + Efl_Net_Socket_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); + + EINA_SAFETY_ON_TRUE_RETURN_VAL(h == INVALID_HANDLE_VALUE, EINVAL); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->handle != INVALID_HANDLE_VALUE, EALREADY); + + pd->io = CreateThreadpoolIo(h, _efl_net_socket_windows_io_completed, o, 0); + if (!pd->io) + return GetLastError(); // TODO vtorri: is this compatible with errno/strerror()? + + pd->handle = h; + + DBG("socket=%p adopted handle=%p, ThreadpoolIo=%p", o, h, pd->io); + return 0; +} + +static Eina_Error _efl_net_socket_windows_recv(Eo *o, Efl_Net_Socket_Windows_Data *pd); + +static void +_efl_net_socket_windows_recv_success(void *data EINA_UNUSED, Eo *o, Eina_Rw_Slice slice) +{ + Efl_Net_Socket_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); + + pd->recv.used += slice.len; + pd->recv.pending = EINA_FALSE; + + efl_io_reader_can_read_set(o, pd->recv.used > 0); + + if (pd->handle == INVALID_HANDLE_VALUE) return; + if (pd->recv.used == pd->recv.len) return; + + _efl_net_socket_windows_recv(o, pd); +} + +static void +_efl_net_socket_windows_recv_failure(void *data EINA_UNUSED, Eo *o, Eina_Error err) +{ + Efl_Net_Socket_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); + + // TODO k-s + ERR("TODO: handle err=%d (%s)", err, eina_error_msg_get(err)); + pd->recv.pending = EINA_FALSE; + pd->pending_eos = EINA_TRUE; /* eos when buffer drains */ +} + +static Eina_Error +_efl_net_socket_windows_recv(Eo *o, Efl_Net_Socket_Windows_Data *pd) +{ + Efl_Net_Socket_Windows_Operation *op; + OVERLAPPED *ovl; + DWORD used_size = 0; + + if (pd->handle == INVALID_HANDLE_VALUE) return EBADF; + if (pd->recv.len == 0) return ENOMEM; + if (pd->recv.used == pd->recv.len) return ENOSPC; + + op = _efl_net_socket_windows_operation_new(o, + _efl_net_socket_windows_recv_success, + _efl_net_socket_windows_recv_failure, + NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(op, EINVAL); + + ovl = _efl_net_socket_windows_operation_overlapped_get(op); + + if (!ReadFile(pd->handle, + pd->recv.bytes + pd->recv.used, + pd->recv.len - pd->recv.used, + &used_size, ovl)) + { + Eina_Error err = GetLastError(); + if (err == ERROR_IO_PENDING) + { + pd->recv.pending = EINA_TRUE; + return 0; + } + else + { + _efl_net_socket_windows_operation_failed(op, err); + return err; + } + } + + _efl_net_socket_windows_operation_succeeded(op, (Eina_Rw_Slice){ + .mem = pd->recv.mem, .len = pd->recv.used + used_size}); + return 0; +} + +static Eina_Error _efl_net_socket_windows_send(Eo *o, Efl_Net_Socket_Windows_Data *pd); + +static void +_efl_net_socket_windows_send_success(void *data EINA_UNUSED, Eo *o, Eina_Rw_Slice slice) +{ + Efl_Net_Socket_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); + + if (slice.len) + memmove(pd->send.bytes, pd->send.bytes + slice.len, pd->send.used - slice.len); + + pd->send.used -= slice.len; + pd->send.pending = EINA_FALSE; + + efl_io_writer_can_write_set(o, pd->send.used < pd->send.len); + + if (pd->handle == INVALID_HANDLE_VALUE) return; + if (pd->send.used == 0) return; + + _efl_net_socket_windows_send(o, pd); +} + +static void +_efl_net_socket_windows_send_failure(void *data EINA_UNUSED, Eo *o, Eina_Error err) +{ + Efl_Net_Socket_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); + + // TODO k-s + ERR("TODO: handle err=%d (%s)", err, eina_error_msg_get(err)); + pd->send.pending = EINA_FALSE; +} + +static Eina_Error +_efl_net_socket_windows_send(Eo *o, Efl_Net_Socket_Windows_Data *pd) +{ + Efl_Net_Socket_Windows_Operation *op; + OVERLAPPED *ovl; + DWORD used_size = 0; + + if (pd->handle == INVALID_HANDLE_VALUE) return EBADF; + if (pd->send.used == 0) return EINVAL; + + op = _efl_net_socket_windows_operation_new(o, + _efl_net_socket_windows_send_success, + _efl_net_socket_windows_send_failure, + NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(op, EINVAL); + + ovl = _efl_net_socket_windows_operation_overlapped_get(op); + + if (!WriteFile(pd->handle, + pd->send.bytes, + pd->send.used, + &used_size, ovl)) + { + Eina_Error err = GetLastError(); + if (err == ERROR_IO_PENDING) + { + pd->send.pending = EINA_TRUE; + return 0; + } + else + { + _efl_net_socket_windows_operation_failed(op, err); + return err; + } + } + + _efl_net_socket_windows_operation_succeeded(op, (Eina_Rw_Slice){ + .mem = pd->send.mem, .len = used_size}); + return 0; +} + + +Eina_Error +_efl_net_socket_windows_io_start(Eo *o) +{ + Efl_Net_Socket_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); + Eina_Error err; + + EINA_SAFETY_ON_NULL_RETURN_VAL(pd, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->io_started, EALREADY); + + if (!pd->recv.mem) + { + pd->recv.mem = malloc(BUFFER_SIZE); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->recv.mem, ENOMEM); + pd->recv.len = BUFFER_SIZE; + pd->recv.used = 0; + } + + if (!pd->send.mem) + { + pd->send.mem = malloc(BUFFER_SIZE); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->send.mem, ENOMEM); + pd->send.len = BUFFER_SIZE; + pd->send.used = 0; + } + + DBG("socket=%p starting I/O...", o); + err = _efl_net_socket_windows_recv(o, pd); + if (err) return err; + + pd->io_started = EINA_TRUE; + return 0; +} + +HANDLE +_efl_net_socket_windows_handle_get(const Eo *o) +{ + Efl_Net_Socket_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd, INVALID_HANDLE_VALUE); + return pd->handle; +} + +EOLIAN static Eo * +_efl_net_socket_windows_efl_object_constructor(Eo *o, Efl_Net_Socket_Windows_Data *pd) +{ + pd->handle = INVALID_HANDLE_VALUE; + + return efl_constructor(efl_super(o, MY_CLASS)); +} + +EOLIAN static void +_efl_net_socket_windows_efl_object_destructor(Eo *o, Efl_Net_Socket_Windows_Data *pd) +{ + while (pd->pending_ops) + _efl_net_socket_windows_operation_failed(pd->pending_ops->data, ECANCELED); + + if (efl_io_closer_close_on_destructor_get(o) && + (!efl_io_closer_closed_get(o))) + { + efl_event_freeze(o); + efl_io_closer_close(o); + efl_event_thaw(o); + } + + efl_destructor(efl_super(o, MY_CLASS)); + + eina_stringshare_replace(&pd->address_local, NULL); + eina_stringshare_replace(&pd->address_remote, NULL); + + free(pd->recv.mem); + pd->recv.mem = NULL; + pd->recv.len = 0; + pd->recv.used = 0; + + free(pd->send.mem); + pd->send.mem = NULL; + pd->send.len = 0; + pd->send.used = 0; +} + +EOLIAN static Eina_Error +_efl_net_socket_windows_efl_io_closer_close(Eo *o, Efl_Net_Socket_Windows_Data *pd) +{ + HANDLE h; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EBADF); + + 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); + + if (pd->io) + { + CloseThreadpoolIo(pd->io); + pd->io = NULL; + } + + h = InterlockedExchangePointer(&pd->handle, INVALID_HANDLE_VALUE); + if (h != INVALID_HANDLE_VALUE) + CloseHandle(h); + + return 0; +} + +EOLIAN static Eina_Bool +_efl_net_socket_windows_efl_io_closer_closed_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Windows_Data *pd) +{ + return pd->handle == INVALID_HANDLE_VALUE; +} + +EOLIAN static Eina_Error +_efl_net_socket_windows_efl_io_reader_read(Eo *o, Efl_Net_Socket_Windows_Data *pd, Eina_Rw_Slice *rw_slice) +{ + Eina_Slice ro_slice; + DWORD remaining; + + EINA_SAFETY_ON_NULL_RETURN_VAL(rw_slice, EINVAL); + + ro_slice.len = pd->recv.used; + if (ro_slice.len == 0) + { + rw_slice->len = 0; + if (pd->pending_eos) + { + efl_io_reader_eos_set(o, EINA_TRUE); + efl_io_closer_close(o); + } + return EAGAIN; + } + ro_slice.bytes = pd->recv.bytes; + + *rw_slice = eina_rw_slice_copy(*rw_slice, ro_slice); + + remaining = pd->recv.used - rw_slice->len; + if (remaining) + memmove(pd->recv.bytes, pd->recv.bytes + rw_slice->len, remaining); + + pd->recv.used = remaining; + efl_io_reader_can_read_set(o, remaining > 0); + + if ((pd->pending_eos) && (remaining == 0)) + { + efl_io_reader_eos_set(o, EINA_TRUE); + efl_io_closer_close(o); + return 0; + } + + if ((!pd->recv.pending) && (pd->recv.used < pd->recv.len)) + { + DBG("recv %lu bytes more from socket=%p", pd->recv.len - pd->recv.used, o); + return _efl_net_socket_windows_recv(o, pd); + } + + return 0; +} + +EOLIAN static void +_efl_net_socket_windows_efl_io_reader_can_read_set(Eo *o, Efl_Net_Socket_Windows_Data *pd, Eina_Bool can_read) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_closer_closed_get(o) && can_read); + if (pd->can_read == can_read) return; + pd->can_read = can_read; + efl_event_callback_call(o, EFL_IO_READER_EVENT_CAN_READ_CHANGED, NULL); +} + +EOLIAN static Eina_Bool +_efl_net_socket_windows_efl_io_reader_can_read_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Windows_Data *pd) +{ + return pd->can_read; +} + +EOLIAN static void +_efl_net_socket_windows_efl_io_reader_eos_set(Eo *o, Efl_Net_Socket_Windows_Data *pd, Eina_Bool is_eos) +{ + if (pd->eos == is_eos) return; + pd->eos = is_eos; + if (is_eos) + efl_event_callback_call(o, EFL_IO_READER_EVENT_EOS, NULL); +} + +EOLIAN static Eina_Bool +_efl_net_socket_windows_efl_io_reader_eos_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Windows_Data *pd) +{ + return pd->eos; +} + +EOLIAN static Eina_Error +_efl_net_socket_windows_efl_io_writer_write(Eo *o, Efl_Net_Socket_Windows_Data *pd, Eina_Slice *slice, Eina_Slice *remaining) +{ + Eina_Error err = EINVAL; + DWORD available, todo; + + EINA_SAFETY_ON_NULL_RETURN_VAL(slice, EINVAL); + EINA_SAFETY_ON_NULL_GOTO(slice->mem, error); + EINA_SAFETY_ON_TRUE_GOTO(efl_io_closer_closed_get(o), error); + err = ENOMEM; + EINA_SAFETY_ON_TRUE_GOTO(pd->send.mem != NULL, error); + + if (pd->send.len <= pd->send.used) + { + efl_io_writer_can_write_set(o, EINA_FALSE); + return EAGAIN; + } + + available = pd->send.len - pd->send.used; + if (slice->len < available) + todo = slice->len; + else + todo = available; + + memcpy(pd->send.bytes + pd->send.used, slice->mem, todo); + if (remaining) + { + remaining->len = slice->len - todo; + remaining->bytes = slice->bytes + todo; + } + slice->len = todo; + + efl_io_writer_can_write_set(o, pd->send.used < pd->send.len); + + if ((!pd->send.pending) && (pd->send.used > 0)) + { + DBG("send %lu bytes more to socket=%p", pd->send.used, o); + return _efl_net_socket_windows_send(o, pd); + } + + return 0; + + error: + if (remaining) *remaining = *slice; + slice->len = 0; + slice->mem = NULL; + return err; +} + +EOLIAN static void +_efl_net_socket_windows_efl_io_writer_can_write_set(Eo *o, Efl_Net_Socket_Windows_Data *pd, Eina_Bool can_write) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_closer_closed_get(o) && can_write); + if (pd->can_write == can_write) return; + pd->can_write = can_write; + efl_event_callback_call(o, EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, NULL); +} + +EOLIAN static Eina_Bool +_efl_net_socket_windows_efl_io_writer_can_write_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Windows_Data *pd) +{ + return pd->can_write; +} + +EOLIAN static void +_efl_net_socket_windows_efl_net_socket_address_local_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Windows_Data *pd, const char *address) +{ + eina_stringshare_replace(&pd->address_local, address); +} + +EOLIAN static const char * +_efl_net_socket_windows_efl_net_socket_address_local_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Windows_Data *pd) +{ + return pd->address_local; +} + +EOLIAN static void +_efl_net_socket_windows_efl_net_socket_address_remote_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Windows_Data *pd, const char *address) +{ + eina_stringshare_replace(&pd->address_remote, address); +} + +EOLIAN static const char * +_efl_net_socket_windows_efl_net_socket_address_remote_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Windows_Data *pd) +{ + return pd->address_remote; +} + +#include "efl_net_socket_windows.eo.c" diff --git a/src/lib/ecore_con/efl_net_socket_windows.eo b/src/lib/ecore_con/efl_net_socket_windows.eo new file mode 100644 index 0000000000..cbe9f583f5 --- /dev/null +++ b/src/lib/ecore_con/efl_net_socket_windows.eo @@ -0,0 +1,24 @@ +class Efl.Net.Socket.Windows (Efl.Loop_User, Efl.Net.Socket) { + [[A base Windows NamedPipe socket. + + This is the common class and takes an existing file HANDLE, + usually created by an dialer (CreatFile()) or server + (CreateNamedPipe()). + + @since 1.19 + ]] + + implements { + Efl.Object.constructor; + Efl.Object.destructor; + Efl.Io.Closer.close; + Efl.Io.Closer.closed { get; } + Efl.Io.Reader.read; + Efl.Io.Reader.can_read { get; set; } + Efl.Io.Reader.eos { get; set; } + Efl.Io.Writer.write; + Efl.Io.Writer.can_write { get; set; } + Efl.Net.Socket.address_local { get; set; } + Efl.Net.Socket.address_remote { get; set; } + } +} -- cgit v1.2.1