#define EFL_NET_SERVER_WINDOWS_PROTECTED 1 #define EFL_NET_SERVER_PROTECTED 1 #define EFL_NET_SOCKET_PROTECTED 1 #ifdef HAVE_CONFIG_H # include #endif #include /* evil_format_message evil_last_error_get */ #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 */ Eina_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 Eina_Error _efl_net_server_windows_client_listen_success(void *data, Eo *client, DWORD used_size EINA_UNUSED) { Eo *o = data; Efl_Net_Server_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); char cstr[256], sstr[256]; ULONG cpid, spid; const char *addr = efl_net_server_address_get(o); EINA_SAFETY_ON_NULL_RETURN_VAL(pd, EINVAL); if (GetNamedPipeClientProcessId(_efl_net_socket_windows_handle_get(client), &cpid)) snprintf(cstr, sizeof(cstr), "%s:%lu", addr, cpid); else { WRN("server=%p (%s) could not GetNamedPipeClientProcessId(client): %s", o, addr, evil_last_error_get()); eina_strlcpy(cstr, addr, sizeof(cstr)); } if (GetNamedPipeServerProcessId(_efl_net_socket_windows_handle_get(client), &spid)) snprintf(sstr, sizeof(sstr), "%s:%lu", addr, spid); else { WRN("server=%p (%s) could not GetNamedPipeServerProcessId(client): %s", o, addr, evil_last_error_get()); eina_strlcpy(sstr, addr, sizeof(sstr)); } DBG("server=%p received incoming connection at %s<->%s", o, sstr, cstr); 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, sstr); efl_net_socket_address_remote_set(client, cstr); 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, cstr); 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 { efl_net_socket_address_local_set(client, sstr); efl_net_socket_address_remote_set(client, cstr); 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); return 0; } static Eina_Error _efl_net_server_windows_client_listen_failure(void *data, Eo *client EINA_UNUSED, DWORD win32err) { Eo *o = data; Eina_Error err = EINVAL; if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG)) { char *msg = _efl_net_windows_error_msg_get(win32err); DBG("server=%p failed to accept connection at %s: error=%s", o, efl_net_server_address_get(o), msg); free(msg); } switch (win32err) { case ERROR_PIPE_CONNECTED: err = 0; break; case ERROR_NO_DATA: err = ENOTCONN; break; case ERROR_OPERATION_ABORTED: err = ECANCELED; break; } if (err) efl_event_callback_call(o, EFL_NET_SERVER_EVENT_SERVER_ERROR, &err); // TODO: create a new one on failure? return err; } 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; DWORD win32err; BOOL r; 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); r = ConnectNamedPipe(h, ovl); win32err = GetLastError(); if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG)) { char *msg = _efl_net_windows_error_msg_get(win32err); DBG("server=%p (%s) socket=%p ConnectNamedPipe(%p, %p)=%s", o, efl_net_server_address_get(o), pd->next_client, h, ovl, msg); free(msg); } if (!r) { if (win32err == ERROR_IO_PENDING) return 0; else { return _efl_net_socket_windows_operation_failed(op, win32err); } } return _efl_net_socket_windows_operation_succeeded(op, 0); } 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) { DWORD win32err = GetLastError(); switch (win32err) { case ERROR_ACCESS_DENIED: return EACCES; case ERROR_INVALID_PARAMETER: return EINVAL; default: { ERR("Unexpected win32err=%lu (%s)", win32err, evil_format_message(win32err)); return EINVAL; } } } pd->next_client = efl_add(EFL_NET_SOCKET_WINDOWS_CLASS, o, efl_io_closer_close_on_invalidate_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(const 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_invalidate(Eo *o, Efl_Net_Server_Windows_Data *pd) { pd->next_client = NULL; efl_invalidate(efl_super(o, MY_CLASS)); } EOLIAN static void _efl_net_server_windows_efl_object_destructor(Eo *o, Efl_Net_Server_Windows_Data *pd) { 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_NS "%s", address); eina_stringshare_del(pd->address); pd->address = tmp; } EOLIAN static const char * _efl_net_server_windows_efl_net_server_address_get(const Eo *o EINA_UNUSED, Efl_Net_Server_Windows_Data *pd) { return pd->address + strlen(PIPE_NS); } static Eina_Value _efl_net_server_windows_pending_announce_job(Eo *o, void *data EINA_UNUSED, const Eina_Value v) { Efl_Net_Server_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); Eo *client; if (!pd->pending_clients) return v; if ((pd->clients_limit > 0) && (pd->clients_limit <= pd->clients_count)) return v; 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); return v; } 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_then(o, efl_loop_job(loop), .success = _efl_net_server_windows_pending_announce_job, .storage = &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(const 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(const 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(const 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_NS) + 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_count(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"