diff options
Diffstat (limited to 'src/lib/ecore_con/efl_net_socket_windows.c')
-rw-r--r-- | src/lib/ecore_con/efl_net_socket_windows.c | 599 |
1 files changed, 599 insertions, 0 deletions
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 <config.h> +#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" |