diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 760f0d1cf..c02cb7ae3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -243,7 +243,7 @@ jobs: run: | python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" chmod a+x builder - ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DAWS_USE_APPLE_DISPATCH_QUEUE=${{ matrix.eventloop == 'dispatch_queue' && 'ON' || 'OFF' }} --cmake-extra=-DENABLE_SANITIZERS=ON --cmake-extra=-DSANITIZERS="${{ matrix.sanitizers }}" + ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DAWS_USE_APPLE_NETWORK_FRAMEWORK=${{ matrix.eventloop == 'dispatch_queue' && 'ON' || 'OFF' }} --cmake-extra=-DENABLE_SANITIZERS=ON --cmake-extra=-DSANITIZERS="${{ matrix.sanitizers }}" macos-x64: runs-on: macos-14-large # latest @@ -274,7 +274,7 @@ jobs: run: | python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" chmod a+x builder - ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DAWS_USE_APPLE_DISPATCH_QUEUE=${{ matrix.eventloop == 'dispatch_queue' && 'ON' || 'OFF' }} --cmake-extra=-DENABLE_SANITIZERS=ON --cmake-extra=-DSANITIZERS="${{ matrix.sanitizers }}" --config Debug + ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DAWS_USE_APPLE_NETWORK_FRAMEWORK=${{ matrix.eventloop == 'dispatch_queue' && 'ON' || 'OFF' }} --cmake-extra=-DENABLE_SANITIZERS=ON --cmake-extra=-DSANITIZERS="${{ matrix.sanitizers }}" --config Debug freebsd: runs-on: ubuntu-24.04 # latest diff --git a/CMakeLists.txt b/CMakeLists.txt index f6a170fda..4056fe374 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ file(GLOB AWS_IO_TESTING_HEADERS "include/aws/testing/*.h" ) + file(GLOB AWS_IO_PRIV_HEADERS "include/aws/io/private/*.h" ) diff --git a/README.md b/README.md index 1441d4aac..0db858b36 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,8 @@ Core to Async-IO is the event-loop. We provide an implementation for most platfo Platform | Implementation --- | --- Linux | Edge-Triggered Epoll -BSD Variants and Apple Devices | KQueue +BSD Variants | KQueue +Apple Devices | KQueue or Apple Dispatch Queue Windows | IOCP (IO Completion Ports) Also, you can always implement your own as well. @@ -645,7 +646,7 @@ All exported functions, simply shim into the v-table and return. We include a cross-platform API for sockets. We support TCP and UDP using IPv4 and IPv6, and Unix Domain sockets. On Windows, we use Named Pipes to support the functionality of Unix Domain sockets. On Windows, this is implemented with winsock2, and on -all unix platforms we use the posix API. +all unix platforms we use the posix API. We also provides options to use Apple Network Framework on Apple. Upon a connection being established, the new socket (either as the result of a `connect()` or `start_accept()` call) will not be attached to any event loops. It is your responsibility to register it with an event loop to begin receiving @@ -715,47 +716,53 @@ upon completion of asynchronous operations. If you are using UDP or LOCAL, `conn Shuts down any pending operations on the socket, and cleans up state. The socket object can be re initialized after this operation. - int aws_socket_connect(struct aws_socket *socket, struct aws_socket_endpoint *remote_endpoint); + int aws_socket_set_cleanup_complete_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data); -Connects to a remote endpoint. In UDP, this simply binds the socket to a remote address for use with `aws_socket_write()`, -and if the operation is successful, the socket can immediately be used for write operations. +Sets the clean up completion callback. The callback will be invoked if `aws_socket_clean_up()` finish to clean up the socket resources. It is safe to release the socket memory after this callback is invoked. -In TCP, this will function will not block. If the return value is successful, then you must wait on the `on_connection_established()` -callback to be invoked before using the socket. + int aws_socket_connect(struct aws_socket *socket, const struct aws_socket_endpoint *remote_endpoint, struct aws_event_loop *event_loop, aws_socket_on_connection_result_fn *on_connection_result, void *user_data); + +Connects to a remote endpoint. In TCP and all Apple Network Framework connections (regardless it is UDP, TCP or LOCAL), when the connection succeed, you still must wait on the `on_connection_result()` callback to be invoked before using the socket. + +In UDP, this simply binds the socket to a remote address for use with `aws_socket_write()`, and if the operation is successful, +the socket can immediately be used for write operations. For LOCAL (Unix Domain Sockets or Named Pipes), the socket will be immediately ready for use upon a successful return. int aws_socket_bind(struct aws_socket *socket, struct aws_socket_endpoint *local_endpoint); -Binds the socket to a local address. In UDP mode, the socket is ready for `aws_socket_read()` operations. In connection oriented -modes, you still must call `aws_socket_listen()` and `aws_socket_start_accept()` before using the socket. +Binds the socket to a local address. In UDP mode, the socket is ready for `aws_socket_read()` operations. In connection oriented +modes or if you are using Apple Network Framework (regardless it is UDP or TCP), you still must call `aws_socket_listen()` and +`aws_socket_start_accept()` before using the socket. int aws_socket_listen(struct aws_socket *socket, int backlog_size); -TCP and LOCAL only. Sets up the socket to listen on the address bound to in `aws_socket_bind()`. +TCP, LOCAL, and Apple Network Framework only. Sets up the socket to listen on the address bound to in `aws_socket_bind()`. - int aws_socket_start_accept(struct aws_socket *socket); + int aws_socket_start_accept(struct aws_socket *socket, struct aws_event_loop *accept_loop, struct aws_socket_listener_options options); -TCP and LOCAL only. The socket will begin accepting new connections. This is an asynchronous operation. New connections will -arrive via the `on_incoming_connection()` callback. +TCP, LOCAL, and Apple Network Framework only. The socket will begin accepting new connections. This is an asynchronous operation. `on_accept_start()` will be invoked when the listener is ready to accept new connection. New connections will arrive via the `on_accept_result()` callback. int aws_socket_stop_accept(struct aws_socket *socket); -TCP and LOCAL only. The socket will shutdown the listener. It is safe to call `aws_socket_start_accept()` again after this -operation. +TCP, LOCAL, and Apple Network Framework only. The socket will shutdown the listener. It is safe to call `aws_socket_start_accept()` +again after this operation. int aws_socket_close(struct aws_socket *socket); Calls `close()` on the socket and unregisters all io operations from the event loop. + int aws_socket_set_close_complete_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data); + +Sets the close completion callback. The callback will be invoked if `aws_socket_close()` finish to process all the I/O events and close the socket. + struct aws_io_handle *aws_socket_get_io_handle(struct aws_socket *socket); Fetches the underlying io handle for use in event loop registrations and channel handlers. int aws_socket_set_options(struct aws_socket *socket, struct aws_socket_options *options); -Sets new socket options on the underlying socket. This is mainly useful in context of accepting a new connection via: -`on_incoming_connection()`. +Sets new socket options on the underlying socket. int aws_socket_read(struct aws_socket *socket, struct aws_byte_buf *buffer, size_t *amount_read); diff --git a/include/aws/io/channel_bootstrap.h b/include/aws/io/channel_bootstrap.h index 81ce0696f..b1e6149c0 100644 --- a/include/aws/io/channel_bootstrap.h +++ b/include/aws/io/channel_bootstrap.h @@ -132,6 +132,14 @@ typedef void(aws_server_bootstrap_on_accept_channel_shutdown_fn)( struct aws_channel *channel, void *user_data); +/** + * This function is only used for async listener (Apple Network Framework in this case). + * Once the server listener socket is finished setup and starting listening, this fuction + * will be invoked. + */ +typedef void( + aws_server_bootstrap_on_listener_setup_fn)(struct aws_server_bootstrap *bootstrap, int error_code, void *user_data); + /** * Once the server listener socket is finished destroying, and all the existing connections are closed, this fuction * will be invoked. @@ -210,6 +218,7 @@ struct aws_server_socket_channel_bootstrap_options { uint32_t port; const struct aws_socket_options *socket_options; const struct aws_tls_connection_options *tls_options; + aws_server_bootstrap_on_listener_setup_fn *setup_callback; aws_server_bootstrap_on_accept_channel_setup_fn *incoming_callback; aws_server_bootstrap_on_accept_channel_shutdown_fn *shutdown_callback; aws_server_bootstrap_on_server_listener_destroy_fn *destroy_callback; @@ -288,6 +297,10 @@ AWS_IO_API int aws_server_bootstrap_set_alpn_callback( * shutting down. Immediately after the `shutdown_callback` returns, the channel is cleaned up automatically. All * callbacks are invoked the thread of the event-loop that the listening socket is assigned to * + * `setup_callback`. If set, the callback will be asynchronously invoked when the listener is ready for use. For Apple + * Network Framework, the listener is not usable until the callback is invoked. If the listener creation failed + * (return NULL), the `setup_callback` will not be invoked. + * * Upon shutdown of your application, you'll want to call `aws_server_bootstrap_destroy_socket_listener` with the return * value from this function. * diff --git a/include/aws/io/private/socket_impl.h b/include/aws/io/private/socket_impl.h index 2cfcf7ff1..18a428995 100644 --- a/include/aws/io/private/socket_impl.h +++ b/include/aws/io/private/socket_impl.h @@ -18,6 +18,20 @@ typedef void (*aws_ms_fn_ptr)(void); void aws_check_and_init_winsock(void); aws_ms_fn_ptr aws_winsock_get_connectex_fn(void); aws_ms_fn_ptr aws_winsock_get_acceptex_fn(void); +#else // NOT ON WINDOWS +struct socket_address { + union sock_addr_types { + struct sockaddr_in addr_in; + struct sockaddr_in6 addr_in6; + struct sockaddr_un un_addr; +# ifdef __APPLE__ + struct sockaddr addr_base; +# endif +# ifdef USE_VSOCK + struct sockaddr_vm vm_addr; +# endif + } sock_addr_types; +}; #endif int aws_socket_init_posix( @@ -48,8 +62,7 @@ struct aws_socket_vtable { int (*socket_start_accept_fn)( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data); + struct aws_socket_listener_options options); int (*socket_stop_accept_fn)(struct aws_socket *socket); int (*socket_close_fn)(struct aws_socket *socket); int (*socket_shutdown_dir_fn)(struct aws_socket *socket, enum aws_channel_direction dir); @@ -67,6 +80,20 @@ struct aws_socket_vtable { void *user_data); int (*socket_get_error_fn)(struct aws_socket *socket); bool (*socket_is_open_fn)(struct aws_socket *socket); + int (*socket_set_close_callback)(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data); + int (*socket_set_cleanup_callback)( + struct aws_socket *socket, + aws_socket_on_shutdown_complete_fn fn, + void *user_data); +}; + +struct on_start_accept_result_args { + struct aws_task task; + int error; + struct aws_allocator *allocator; + struct aws_socket *socket; + aws_socket_on_accept_started_fn *on_accept_start; + void *on_accept_start_user_data; }; #endif // AWS_IO_SOCKET_IMPL_H diff --git a/include/aws/io/socket.h b/include/aws/io/socket.h index 15a0f71b3..0db8cf169 100644 --- a/include/aws/io/socket.h +++ b/include/aws/io/socket.h @@ -50,6 +50,8 @@ enum aws_socket_impl_type { #define AWS_NETWORK_INTERFACE_NAME_MAX 16 +typedef void(aws_socket_on_shutdown_complete_fn)(void *user_data); + struct aws_socket_options { enum aws_socket_type type; enum aws_socket_domain domain; @@ -90,6 +92,15 @@ struct aws_event_loop; */ typedef void(aws_socket_on_connection_result_fn)(struct aws_socket *socket, int error_code, void *user_data); +/** + * Called by a listening socket when a listener accept has successfully initialized or an error has occurred. + * If the listener was successful error_code will be AWS_ERROR_SUCCESS and the socket has already been assigned + * to the event loop specified in aws_socket_start_accept(). + * + * If an error occurred error_code will be non-zero. + */ +typedef void(aws_socket_on_accept_started_fn)(struct aws_socket *socket, int error_code, void *user_data); + /** * Called by a listening socket when either an incoming connection has been received or an error occurred. * @@ -116,14 +127,15 @@ typedef void(aws_socket_on_accept_result_fn)( * Callback for when the data passed to a call to aws_socket_write() has either completed or failed. * On success, error_code will be AWS_ERROR_SUCCESS. * - * `socket` may be NULL in the callback if the socket is released and cleaned up before a callback is triggered. - * by the system I/O handler, + * `socket` may be NULL in the callback if the socket is released and cleaned up before the callback is triggered. */ typedef void( aws_socket_on_write_completed_fn)(struct aws_socket *socket, int error_code, size_t bytes_written, void *user_data); /** * Callback for when socket is either readable (edge-triggered) or when an error has occurred. If the socket is * readable, error_code will be AWS_ERROR_SUCCESS. + * + * `socket` may be NULL in the callback if the socket is released and cleaned up before the callback is triggered. */ typedef void(aws_socket_on_readable_fn)(struct aws_socket *socket, int error_code, void *user_data); @@ -156,6 +168,16 @@ struct aws_socket { void *impl; }; +struct aws_socket_listener_options { + aws_socket_on_accept_result_fn *on_accept_result; + void *on_accept_result_user_data; + + // This callback is invoked when the listener starts accepting incoming connections. + // If the callback set, the socket must not be released before the callback invoked. + aws_socket_on_accept_started_fn *on_accept_start; + void *on_accept_start_user_data; +}; + struct aws_byte_buf; struct aws_byte_cursor; @@ -227,8 +249,7 @@ AWS_IO_API int aws_socket_listen(struct aws_socket *socket, int backlog_size); AWS_IO_API int aws_socket_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data); + struct aws_socket_listener_options options); /** * TCP, LOCAL and VSOCK only. The listening socket will stop accepting new connections. @@ -245,6 +266,9 @@ AWS_IO_API int aws_socket_stop_accept(struct aws_socket *socket); * non-event-loop thread or the event-loop the socket is currently assigned to. If called from outside the event-loop, * this function will block waiting on the socket to close. If this is called from an event-loop thread other than * the one it's assigned to, it presents the possibility of a deadlock, so don't do it. + * + * If you are using Apple Network Framework, you should always call this function from an event-loop thread regardless + * it is a server or client socket. */ AWS_IO_API int aws_socket_close(struct aws_socket *socket); @@ -254,8 +278,7 @@ AWS_IO_API int aws_socket_close(struct aws_socket *socket); AWS_IO_API int aws_socket_shutdown_dir(struct aws_socket *socket, enum aws_channel_direction dir); /** - * Sets new socket options on the underlying socket. This is mainly useful in context of accepting a new connection via: - * `on_incoming_connection()`. options is copied. + * Sets new socket options on the underlying socket. */ AWS_IO_API int aws_socket_set_options(struct aws_socket *socket, const struct aws_socket_options *options); @@ -319,6 +342,24 @@ AWS_IO_API int aws_socket_write( aws_socket_on_write_completed_fn *written_fn, void *user_data); +/** + * Apple Network Framework only. The callback that will triggered when aws_socket_close() finished. The callback + * will be called from the socket event loop. + */ +AWS_IO_API int aws_socket_set_close_complete_callback( + struct aws_socket *socket, + aws_socket_on_shutdown_complete_fn fn, + void *user_data); + +/** + * Apple Network Framework only. The callback that will triggered when aws_socket_cleanup() finished. And + * it is only safe to release the socket afterwards. The callback will be called from the socket event loop. + */ +AWS_IO_API int aws_socket_set_cleanup_complete_callback( + struct aws_socket *socket, + aws_socket_on_shutdown_complete_fn fn, + void *user_data); + /** * Gets the latest error from the socket. If no error has occurred AWS_OP_SUCCESS will be returned. This function does * not raise any errors to the installed error handlers. @@ -358,6 +399,12 @@ AWS_IO_API void aws_socket_endpoint_init_local_address_for_test(struct aws_socke * network_interface_name on Windows */ AWS_IO_API bool aws_is_network_interface_name_valid(const char *interface_name); +/** + * Get default impl type based on the platform. + * For user in internal tests only. + */ +AWS_IO_API enum aws_socket_impl_type aws_socket_get_default_impl_type(void); + AWS_EXTERN_C_END AWS_POP_SANE_WARNING_LEVEL diff --git a/source/channel_bootstrap.c b/source/channel_bootstrap.c index 2ccd3873a..a2b3a0a73 100644 --- a/source/channel_bootstrap.c +++ b/source/channel_bootstrap.c @@ -21,6 +21,11 @@ # pragma warning(disable : 4221) #endif +// Define a macro to allocate and initialize a structure +#define SETUP_SOCKET_SHUTDOWN_CALLBACKS(allocator, socket, struct_type, init_function, ...) \ + struct struct_type *shutdown_args = struct_type##_new(allocator, __VA_ARGS__); \ + aws_socket_set_cleanup_complete_callback(socket, init_function, shutdown_args); + static void s_client_bootstrap_destroy_impl(struct aws_client_bootstrap *bootstrap) { AWS_ASSERT(bootstrap); AWS_LOGF_DEBUG(AWS_LS_IO_CHANNEL_BOOTSTRAP, "id=%p: bootstrap destroying", (void *)bootstrap); @@ -499,6 +504,11 @@ static void s_on_client_channel_on_setup_completed(struct aws_channel *channel, /* the channel shutdown callback will clean the channel up */ } +static void s_socket_shutdown_complete_release_client_connection_fn(void *user_data) { + struct client_connection_args *connection_args = user_data; + s_client_connection_args_release(connection_args); +} + static void s_on_client_channel_on_shutdown(struct aws_channel *channel, int error_code, void *user_data) { struct client_connection_args *connection_args = user_data; @@ -509,20 +519,82 @@ static void s_on_client_channel_on_shutdown(struct aws_channel *channel, int err (void *)channel, error_code); - /* note it's not safe to reference the bootstrap after the callback. */ + struct aws_socket *socket = connection_args->channel_data.socket; struct aws_allocator *allocator = connection_args->bootstrap->allocator; + s_connection_args_shutdown_callback(connection_args, error_code, channel); + /* note it's not safe to reference the bootstrap after the callback. */ aws_channel_destroy(channel); - aws_socket_clean_up(connection_args->channel_data.socket); - aws_mem_release(allocator, connection_args->channel_data.socket); - s_client_connection_args_release(connection_args); + + aws_socket_set_cleanup_complete_callback( + socket, s_socket_shutdown_complete_release_client_connection_fn, connection_args); + + aws_socket_clean_up(socket); + + aws_mem_release(allocator, socket); } static bool s_aws_socket_domain_uses_dns(enum aws_socket_domain domain) { return domain == AWS_SOCKET_IPV4 || domain == AWS_SOCKET_IPV6; } +struct socket_shutdown_setup_channel_args { + struct aws_allocator *allocator; + struct client_connection_args *connection_args; + int error_code; + bool release_connection_args; +}; + +struct socket_shutdown_setup_channel_args *socket_shutdown_setup_channel_args_new( + struct aws_allocator *allocator, + struct client_connection_args *connection_args, + int error_code, + bool release_connection_args) { + struct socket_shutdown_setup_channel_args *shutdown_args = + aws_mem_calloc(allocator, 1, sizeof(struct socket_shutdown_setup_channel_args)); + shutdown_args->allocator = allocator; + shutdown_args->connection_args = connection_args; + shutdown_args->error_code = error_code; + shutdown_args->release_connection_args = release_connection_args; + return shutdown_args; +} + +static void socket_shutdown_setup_channel_args_destroy(struct socket_shutdown_setup_channel_args *args) { + aws_mem_release(args->allocator, args); +} + +static void s_socket_shutdown_complete_setup_connection_args_fn(void *user_data) { + struct socket_shutdown_setup_channel_args *shutdown_args = user_data; + struct client_connection_args *connection_args = shutdown_args->connection_args; + + // The failed count should be set before validation + if (shutdown_args->error_code || !connection_args->channel_data.channel) { + connection_args->failed_count++; + } + + /* if this is the last attempted connection and it failed, notify the user */ + if (connection_args->failed_count == connection_args->addresses_count) { + AWS_LOGF_ERROR( + AWS_LS_IO_CHANNEL_BOOTSTRAP, + "id=%p: Connection failed with error_code %d.", + (void *)connection_args->bootstrap, + shutdown_args->error_code); + /* connection_args will be released after setup_callback */ + s_connection_args_setup_callback(connection_args, shutdown_args->error_code, NULL); + } + + if (shutdown_args->release_connection_args) { + /* every connection task adds a ref, so every failure or cancel needs to dec one */ + s_client_connection_args_release(connection_args); + } + socket_shutdown_setup_channel_args_destroy(shutdown_args); +} + +/* Called when a socket connection attempt task completes. First socket to successfully open + * assigns itself to connection_args->channel_data.socket and flips connection_args->connection_chosen + * to true. Subsequent successful sockets will be released and cleaned up + */ static void s_on_client_connection_established(struct aws_socket *socket, int error_code, void *user_data) { struct client_connection_args *connection_args = user_data; @@ -533,16 +605,13 @@ static void s_on_client_connection_established(struct aws_socket *socket, int er (void *)socket, error_code); - if (error_code) { - connection_args->failed_count++; - } + struct aws_allocator *allocator = connection_args->bootstrap->allocator; if (error_code || connection_args->connection_chosen) { if (s_aws_socket_domain_uses_dns(connection_args->outgoing_options.domain) && error_code) { struct aws_host_address host_address; host_address.host = connection_args->host_name; - host_address.address = - aws_string_new_from_c_str(connection_args->bootstrap->allocator, socket->remote_endpoint.address); + host_address.address = aws_string_new_from_c_str(allocator, socket->remote_endpoint.address); host_address.record_type = connection_args->outgoing_options.domain == AWS_SOCKET_IPV6 ? AWS_ADDRESS_RECORD_TYPE_AAAA : AWS_ADDRESS_RECORD_TYPE_A; @@ -564,24 +633,19 @@ static void s_on_client_connection_established(struct aws_socket *socket, int er "successful connection or because it errored out.", (void *)connection_args->bootstrap, (void *)socket); - aws_socket_close(socket); + SETUP_SOCKET_SHUTDOWN_CALLBACKS( + allocator, + socket, + socket_shutdown_setup_channel_args, + s_socket_shutdown_complete_setup_connection_args_fn, + connection_args, + error_code, + true) + aws_socket_close(socket); aws_socket_clean_up(socket); - aws_mem_release(connection_args->bootstrap->allocator, socket); - - /* if this is the last attempted connection and it failed, notify the user */ - if (connection_args->failed_count == connection_args->addresses_count) { - AWS_LOGF_ERROR( - AWS_LS_IO_CHANNEL_BOOTSTRAP, - "id=%p: Connection failed with error_code %d.", - (void *)connection_args->bootstrap, - error_code); - /* connection_args will be released after setup_callback */ - s_connection_args_setup_callback(connection_args, error_code, NULL); - } + aws_mem_release(allocator, socket); - /* every connection task adds a ref, so every failure or cancel needs to dec one */ - s_client_connection_args_release(connection_args); return; } @@ -607,14 +671,17 @@ static void s_on_client_connection_established(struct aws_socket *socket, int er connection_args->channel_data.channel = aws_channel_new(connection_args->bootstrap->allocator, &args); if (!connection_args->channel_data.channel) { + + SETUP_SOCKET_SHUTDOWN_CALLBACKS( + connection_args->bootstrap->allocator, + socket, + socket_shutdown_setup_channel_args, + s_socket_shutdown_complete_setup_connection_args_fn, + connection_args, + error_code, + false) aws_socket_clean_up(socket); aws_mem_release(connection_args->bootstrap->allocator, connection_args->channel_data.socket); - connection_args->failed_count++; - - /* if this is the last attempted connection and it failed, notify the user */ - if (connection_args->failed_count == connection_args->addresses_count) { - s_connection_args_setup_callback(connection_args, error_code, NULL); - } } else { s_connection_args_creation_callback(connection_args, connection_args->channel_data.channel); } @@ -629,6 +696,55 @@ struct connection_task_data { struct aws_event_loop *connect_loop; }; +struct socket_shutdown_attempt_connection_args { + struct aws_allocator *allocator; + struct connection_task_data *task_data; + int error_code; +}; + +struct socket_shutdown_attempt_connection_args *socket_shutdown_attempt_connection_args_new( + struct aws_allocator *allocator, + struct connection_task_data *task_data, + int error_code) { + struct socket_shutdown_attempt_connection_args *close_args = + aws_mem_calloc(allocator, 1, sizeof(struct socket_shutdown_attempt_connection_args)); + close_args->allocator = allocator; + close_args->task_data = task_data; + close_args->error_code = error_code; + return close_args; +} + +static void s_socket_shutdown_complete_attempt_connection_fn(void *user_data) { + struct socket_shutdown_attempt_connection_args *shutdown_args = user_data; + struct connection_task_data *task_data = shutdown_args->task_data; + int err_code = shutdown_args->error_code; + + /* if this is the last attempted connection and it failed, notify the user */ + if (++task_data->args->failed_count == task_data->args->addresses_count) { + AWS_LOGF_ERROR( + AWS_LS_IO_CHANNEL_BOOTSTRAP, + "id=%p: Last attempt failed to create socket with error %d", + (void *)task_data->args->bootstrap, + err_code); + s_connection_args_setup_callback(task_data->args, err_code, NULL); + } else { + AWS_LOGF_DEBUG( + AWS_LS_IO_CHANNEL_BOOTSTRAP, + "id=%p: Socket connect attempt %d/%d failed with error %d. More attempts ongoing...", + (void *)task_data->args->bootstrap, + task_data->args->failed_count, + task_data->args->addresses_count, + err_code); + } + + s_client_connection_args_release(task_data->args); + + aws_host_address_clean_up(&task_data->host_address); + + aws_mem_release(shutdown_args->allocator, task_data); + aws_mem_release(shutdown_args->allocator, shutdown_args); +} + static void s_attempt_connection(struct aws_task *task, void *arg, enum aws_task_status status) { (void)task; struct connection_task_data *task_data = arg; @@ -639,7 +755,7 @@ static void s_attempt_connection(struct aws_task *task, void *arg, enum aws_task goto task_cancelled; } - struct aws_socket *outgoing_socket = aws_mem_acquire(allocator, sizeof(struct aws_socket)); + struct aws_socket *outgoing_socket = aws_mem_calloc(allocator, 1, sizeof(struct aws_socket)); if (aws_socket_init(outgoing_socket, allocator, &task_data->options)) { goto socket_init_failed; } @@ -658,9 +774,24 @@ static void s_attempt_connection(struct aws_task *task, void *arg, enum aws_task socket_connect_failed: aws_host_resolver_record_connection_failure(task_data->args->bootstrap->host_resolver, &task_data->host_address); + + SETUP_SOCKET_SHUTDOWN_CALLBACKS( + allocator, + outgoing_socket, + socket_shutdown_attempt_connection_args, + s_socket_shutdown_complete_attempt_connection_fn, + task_data, + aws_last_error()) + aws_socket_clean_up(outgoing_socket); + aws_mem_release(allocator, outgoing_socket); + + // The socket shutdown callback should handle the cleanup + return; + socket_init_failed: aws_mem_release(allocator, outgoing_socket); + task_cancelled: err_code = aws_last_error(); task_data->args->failed_count++; @@ -832,10 +963,6 @@ int aws_client_bootstrap_new_socket_channel(struct aws_socket_channel_bootstrap_ struct client_connection_args *client_connection_args = aws_mem_calloc(bootstrap->allocator, 1, sizeof(struct client_connection_args)); - if (!client_connection_args) { - return AWS_OP_ERR; - } - const char *host_name = options->host_name; uint32_t port = options->port; @@ -948,9 +1075,12 @@ int aws_client_bootstrap_new_socket_channel(struct aws_socket_channel_bootstrap_ s_client_connection_args_acquire(client_connection_args); if (aws_socket_connect( outgoing_socket, &endpoint, connect_loop, s_on_client_connection_established, client_connection_args)) { + + aws_socket_set_cleanup_complete_callback( + outgoing_socket, s_socket_shutdown_complete_release_client_connection_fn, client_connection_args); + aws_socket_clean_up(outgoing_socket); aws_mem_release(client_connection_args->bootstrap->allocator, outgoing_socket); - s_client_connection_args_release(client_connection_args); goto error; } } @@ -1021,6 +1151,7 @@ struct server_connection_args { aws_server_bootstrap_on_accept_channel_setup_fn *incoming_callback; aws_server_bootstrap_on_accept_channel_shutdown_fn *shutdown_callback; aws_server_bootstrap_on_server_listener_destroy_fn *destroy_callback; + aws_server_bootstrap_on_listener_setup_fn *setup_callback; struct aws_tls_connection_options tls_options; aws_channel_on_protocol_negotiated_fn *on_protocol_negotiated; aws_tls_on_data_read_fn *user_on_data_read; @@ -1074,6 +1205,31 @@ static void s_server_connection_args_release(struct server_connection_args *args } } +struct socket_shutdown_release_server_connection_args { + struct aws_allocator *allocator; + struct server_connection_args *connection_args; +}; + +struct socket_shutdown_release_server_connection_args *socket_shutdown_release_server_connection_args_new( + struct aws_allocator *allocator, + struct server_connection_args *connection_args) { + struct socket_shutdown_release_server_connection_args *shutdown_args = + aws_mem_calloc(allocator, 1, sizeof(struct socket_shutdown_release_server_connection_args)); + shutdown_args->allocator = allocator; + shutdown_args->connection_args = connection_args; + return shutdown_args; +} + +static void s_socket_shutdown_complete_release_server_connection_fn(void *user_data) { + struct socket_shutdown_release_server_connection_args *shutdown_args = user_data; + struct server_connection_args *connection_args = shutdown_args->connection_args; + struct aws_allocator *allocator = shutdown_args->allocator; + + s_server_connection_args_release(connection_args); + + aws_mem_release(allocator, shutdown_args); +} + static void s_server_incoming_callback( struct server_channel_data *channel_data, int error_code, @@ -1226,11 +1382,43 @@ static inline int s_setup_server_tls(struct server_channel_data *channel_data, s return AWS_OP_SUCCESS; } +struct socket_shutdown_server_channel_setup_complete_args { + struct aws_allocator *allocator; + struct server_channel_data *channel_data; + int error_code; +}; + +struct socket_shutdown_server_channel_setup_complete_args *socket_shutdown_server_channel_setup_complete_args_new( + struct aws_allocator *allocator, + struct server_channel_data *channel_data, + int error_code) { + struct socket_shutdown_server_channel_setup_complete_args *shutdown_args = + aws_mem_calloc(allocator, 1, sizeof(struct socket_shutdown_server_channel_setup_complete_args)); + shutdown_args->allocator = allocator; + shutdown_args->channel_data = channel_data; + shutdown_args->error_code = error_code; + return shutdown_args; +} + +static void socket_shutdown_server_channel_setup_complete_fn(void *user_data) { + struct socket_shutdown_server_channel_setup_complete_args *shutdown_args = user_data; + struct server_channel_data *channel_data = shutdown_args->channel_data; + struct server_connection_args *connection_args = channel_data->server_connection_args; + struct aws_allocator *allocator = shutdown_args->allocator; + + s_server_incoming_callback(shutdown_args->channel_data, shutdown_args->error_code, NULL); + s_server_connection_args_release(connection_args); + aws_mem_release(allocator, shutdown_args->channel_data); + + aws_mem_release(allocator, shutdown_args); +} + static void s_on_server_channel_on_setup_completed(struct aws_channel *channel, int error_code, void *user_data) { struct server_channel_data *channel_data = user_data; int err_code = error_code; if (err_code) { + /* channel fail to set up no destroy callback will fire */ AWS_LOGF_ERROR( AWS_LS_IO_CHANNEL_BOOTSTRAP, @@ -1240,13 +1428,20 @@ static void s_on_server_channel_on_setup_completed(struct aws_channel *channel, err_code); aws_channel_destroy(channel); + struct aws_allocator *allocator = channel_data->socket->allocator; + struct aws_socket *socket = channel_data->socket; + + SETUP_SOCKET_SHUTDOWN_CALLBACKS( + allocator, + socket, + socket_shutdown_server_channel_setup_complete_args, + socket_shutdown_server_channel_setup_complete_fn, + channel_data, + aws_last_error()) + aws_socket_clean_up(channel_data->socket); - aws_mem_release(allocator, (void *)channel_data->socket); - s_server_incoming_callback(channel_data, err_code, NULL); - aws_mem_release(channel_data->server_connection_args->bootstrap->allocator, channel_data); - /* no shutdown call back will be fired, we release the ref_count of connection arg here */ - s_server_connection_args_release(channel_data->server_connection_args); + aws_mem_release(socket->allocator, socket); return; } @@ -1307,33 +1502,111 @@ static void s_on_server_channel_on_setup_completed(struct aws_channel *channel, aws_channel_shutdown(channel, err_code); } -static void s_on_server_channel_on_shutdown(struct aws_channel *channel, int error_code, void *user_data) { - struct server_channel_data *channel_data = user_data; - struct server_connection_args *args = channel_data->server_connection_args; +struct socket_shutdown_server_channel_shutdown_args { + struct aws_allocator *allocator; + struct server_channel_data *channel_data; + struct aws_channel *channel; + int error_code; +}; + +struct socket_shutdown_server_channel_shutdown_args *socket_shutdown_server_channel_shutdown_args_new( + struct aws_allocator *allocator, + struct server_channel_data *channel_data, + struct aws_channel *channel, + int error_code) { + struct socket_shutdown_server_channel_shutdown_args *shutdown_args = + aws_mem_calloc(allocator, 1, sizeof(struct socket_shutdown_server_channel_shutdown_args)); + shutdown_args->allocator = allocator; + shutdown_args->channel_data = channel_data; + shutdown_args->channel = channel; + shutdown_args->error_code = error_code; + return shutdown_args; +} + +static void socket_shutdown_server_channel_shutdown_fn(void *user_data) { + struct socket_shutdown_server_channel_shutdown_args *shutdown_args = user_data; + struct server_channel_data *channel_data = shutdown_args->channel_data; + struct server_connection_args *connection_args = channel_data->server_connection_args; + struct aws_allocator *allocator = shutdown_args->allocator; + AWS_LOGF_DEBUG( AWS_LS_IO_CHANNEL_BOOTSTRAP, "id=%p: channel %p shutdown with error %d.", - (void *)args->bootstrap, - (void *)channel, - error_code); + (void *)connection_args->bootstrap, + (void *)shutdown_args->channel, + shutdown_args->error_code); - void *server_shutdown_user_data = args->user_data; - struct aws_server_bootstrap *server_bootstrap = args->bootstrap; - struct aws_allocator *allocator = server_bootstrap->allocator; + void *server_shutdown_user_data = connection_args->user_data; + struct aws_server_bootstrap *server_bootstrap = connection_args->bootstrap; + + int error_code = shutdown_args->error_code; + if (channel_data->incoming_called) { + connection_args->shutdown_callback( + server_bootstrap, error_code, shutdown_args->channel, server_shutdown_user_data); + } + + aws_channel_destroy(shutdown_args->channel); + s_server_connection_args_release(channel_data->server_connection_args); + aws_mem_release(allocator, channel_data); + + aws_mem_release(allocator, shutdown_args); +} + +static void s_on_server_channel_on_shutdown(struct aws_channel *channel, int error_code, void *user_data) { + struct server_channel_data *channel_data = user_data; + struct server_connection_args *args = channel_data->server_connection_args; + struct aws_allocator *allocator = args->bootstrap->allocator; if (!channel_data->incoming_called) { error_code = (error_code) ? error_code : AWS_ERROR_UNKNOWN; s_server_incoming_callback(channel_data, error_code, NULL); - } else { - args->shutdown_callback(server_bootstrap, error_code, channel, server_shutdown_user_data); } - aws_channel_destroy(channel); - aws_socket_clean_up(channel_data->socket); - aws_mem_release(allocator, channel_data->socket); - s_server_connection_args_release(channel_data->server_connection_args); + struct aws_socket *socket = channel_data->socket; - aws_mem_release(allocator, channel_data); + SETUP_SOCKET_SHUTDOWN_CALLBACKS( + allocator, + socket, + socket_shutdown_server_channel_shutdown_args, + socket_shutdown_server_channel_shutdown_fn, + channel_data, + channel, + error_code) + + aws_socket_clean_up(socket); + aws_mem_release(allocator, socket); +} + +struct socket_shutdown_server_connection_result_args { + struct aws_allocator *allocator; + struct server_connection_args *connection_args; + int error_code; +}; + +struct socket_shutdown_server_connection_result_args *socket_shutdown_server_connection_result_args_new( + struct aws_allocator *allocator, + struct server_connection_args *connection_args, + int error_code) { + struct socket_shutdown_server_connection_result_args *shutdown_args = + aws_mem_calloc(allocator, 1, sizeof(struct socket_shutdown_server_connection_result_args)); + shutdown_args->allocator = allocator; + shutdown_args->connection_args = connection_args; + shutdown_args->error_code = error_code; + return shutdown_args; +} + +static void s_socket_shutdown_server_connection_result_fn(void *user_data) { + struct socket_shutdown_server_connection_result_args *shutdown_args = user_data; + struct server_connection_args *connection_args = shutdown_args->connection_args; + struct aws_allocator *allocator = shutdown_args->allocator; + + /* no channel is created */ + connection_args->incoming_callback( + connection_args->bootstrap, shutdown_args->error_code, NULL, connection_args->user_data); + + s_server_connection_args_release(connection_args); + + aws_mem_release(allocator, shutdown_args); } void s_on_server_connection_result( @@ -1361,9 +1634,7 @@ void s_on_server_connection_result( (void *)socket); struct server_channel_data *channel_data = aws_mem_calloc(connection_args->bootstrap->allocator, 1, sizeof(struct server_channel_data)); - if (!channel_data) { - goto error_cleanup; - } + channel_data->incoming_called = false; channel_data->socket = new_socket; channel_data->server_connection_args = connection_args; @@ -1376,11 +1647,10 @@ void s_on_server_connection_result( .setup_user_data = channel_data, .shutdown_user_data = channel_data, .on_shutdown_completed = s_on_server_channel_on_shutdown, + .event_loop = event_loop, + .enable_read_back_pressure = channel_data->server_connection_args->enable_read_back_pressure, }; - channel_args.event_loop = event_loop; - channel_args.enable_read_back_pressure = channel_data->server_connection_args->enable_read_back_pressure; - if (aws_socket_assign_to_event_loop(new_socket, event_loop)) { aws_mem_release(connection_args->bootstrap->allocator, (void *)channel_data); goto error_cleanup; @@ -1402,12 +1672,19 @@ void s_on_server_connection_result( error_cleanup: /* no channel is created */ - connection_args->incoming_callback(connection_args->bootstrap, aws_last_error(), NULL, connection_args->user_data); - + ; // to avoid expression error after a label struct aws_allocator *allocator = new_socket->allocator; + + SETUP_SOCKET_SHUTDOWN_CALLBACKS( + allocator, + socket, + socket_shutdown_server_connection_result_args, + s_socket_shutdown_server_connection_result_fn, + connection_args, + aws_last_error()) + aws_socket_clean_up(new_socket); aws_mem_release(allocator, (void *)new_socket); - s_server_connection_args_release(connection_args); } static void s_listener_destroy_task(struct aws_task *task, void *arg, enum aws_task_status status) { @@ -1416,8 +1693,49 @@ static void s_listener_destroy_task(struct aws_task *task, void *arg, enum aws_t struct server_connection_args *server_connection_args = arg; aws_socket_stop_accept(&server_connection_args->listener); + + SETUP_SOCKET_SHUTDOWN_CALLBACKS( + server_connection_args->bootstrap->allocator, + &server_connection_args->listener, + socket_shutdown_release_server_connection_args, + s_socket_shutdown_complete_release_server_connection_fn, + server_connection_args) + aws_socket_clean_up(&server_connection_args->listener); +} + +/* Called when a listener connection attempt task completes. + */ +static void s_on_listener_connection_established(struct aws_socket *socket, int error_code, void *user_data) { + struct server_connection_args *server_connection_args = user_data; + + AWS_LOGF_DEBUG( + AWS_LS_IO_CHANNEL_BOOTSTRAP, + "id=%p: listener connection on socket %p completed with error %d.", + (void *)server_connection_args->bootstrap, + (void *)socket, + error_code); + + if (error_code) { + + SETUP_SOCKET_SHUTDOWN_CALLBACKS( + server_connection_args->bootstrap->allocator, + &server_connection_args->listener, + socket_shutdown_release_server_connection_args, + s_socket_shutdown_complete_release_server_connection_fn, + server_connection_args) + + aws_socket_clean_up(&server_connection_args->listener); + } + + if (server_connection_args->setup_callback) { + server_connection_args->setup_callback( + server_connection_args->bootstrap, error_code, server_connection_args->user_data); + } + s_server_connection_args_release(server_connection_args); + + return; } struct aws_socket *aws_server_bootstrap_new_socket_listener( @@ -1427,6 +1745,8 @@ struct aws_socket *aws_server_bootstrap_new_socket_listener( AWS_PRECONDITION(bootstrap_options->incoming_callback); AWS_PRECONDITION(bootstrap_options->shutdown_callback); + bool async_setup = bootstrap_options->setup_callback != NULL; + struct server_connection_args *server_connection_args = aws_mem_calloc(bootstrap_options->bootstrap->allocator, 1, sizeof(struct server_connection_args)); if (!server_connection_args) { @@ -1452,6 +1772,7 @@ struct aws_socket *aws_server_bootstrap_new_socket_listener( server_connection_args->destroy_callback = bootstrap_options->destroy_callback; server_connection_args->on_protocol_negotiated = bootstrap_options->bootstrap->on_protocol_negotiated; server_connection_args->enable_read_back_pressure = bootstrap_options->enable_read_back_pressure; + server_connection_args->setup_callback = bootstrap_options->setup_callback; aws_task_init( &server_connection_args->listener_destroy_task, @@ -1522,18 +1843,44 @@ struct aws_socket *aws_server_bootstrap_new_socket_listener( goto cleanup_listener; } - if (aws_socket_start_accept( - &server_connection_args->listener, - connection_loop, - s_on_server_connection_result, - server_connection_args)) { + struct aws_socket_listener_options options = { + .on_accept_result = s_on_server_connection_result, + .on_accept_result_user_data = server_connection_args, + .on_accept_start = NULL, + .on_accept_start_user_data = NULL, + }; + + if (async_setup) { + // If we use an async socket, acquire the connection args for listener establish callbacks, if + // aws_socket_start_accept succeed, the args should be released in `s_on_listener_connection_established` + s_server_connection_args_acquire(server_connection_args); + options.on_accept_start = s_on_listener_connection_established; + options.on_accept_start_user_data = server_connection_args; + } + + if (aws_socket_start_accept(&server_connection_args->listener, connection_loop, options)) { + if (async_setup) { + // release the args we acquired above + s_server_connection_args_release(server_connection_args); + } goto cleanup_listener; } return &server_connection_args->listener; cleanup_listener: + + ; // This line just used to avoid expression error after the label + + SETUP_SOCKET_SHUTDOWN_CALLBACKS( + bootstrap_options->bootstrap->allocator, + &server_connection_args->listener, + socket_shutdown_release_server_connection_args, + s_socket_shutdown_complete_release_server_connection_fn, + server_connection_args) + aws_socket_clean_up(&server_connection_args->listener); + return NULL; cleanup_server_connection_args: s_server_connection_args_release(server_connection_args); diff --git a/source/darwin/nw_socket.c b/source/darwin/nw_socket.c new file mode 100644 index 000000000..57cebb5de --- /dev/null +++ b/source/darwin/nw_socket.c @@ -0,0 +1,2214 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +static int s_determine_socket_error(int error) { + switch (error) { + case ECONNREFUSED: + return AWS_IO_SOCKET_CONNECTION_REFUSED; + case ETIMEDOUT: + return AWS_IO_SOCKET_TIMEOUT; + case EHOSTUNREACH: + case ENETUNREACH: + return AWS_IO_SOCKET_NO_ROUTE_TO_HOST; + case EADDRNOTAVAIL: + return AWS_IO_SOCKET_INVALID_ADDRESS; + case ENETDOWN: + return AWS_IO_SOCKET_NETWORK_DOWN; + case ECONNABORTED: + return AWS_IO_SOCKET_CONNECT_ABORTED; + case EADDRINUSE: + return AWS_IO_SOCKET_ADDRESS_IN_USE; + case ENOBUFS: + case ENOMEM: + return AWS_ERROR_OOM; + case EAGAIN: + return AWS_IO_READ_WOULD_BLOCK; + case EMFILE: + case ENFILE: + return AWS_ERROR_MAX_FDS_EXCEEDED; + case ENOENT: + case EINVAL: + return AWS_ERROR_FILE_INVALID_PATH; + case EAFNOSUPPORT: + return AWS_IO_SOCKET_UNSUPPORTED_ADDRESS_FAMILY; + case EACCES: + return AWS_ERROR_NO_PERMISSION; + default: + return AWS_IO_SOCKET_NOT_CONNECTED; + } +} + +static int s_convert_nw_error(nw_error_t nw_error) { + int nw_error_code = nw_error ? nw_error_get_error_code(nw_error) : 0; + int crt_error_code = nw_error_code ? s_determine_socket_error(nw_error_code) : AWS_OP_SUCCESS; + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "s_convert_nw_error invoked with nw_error_code %d, maps to CRT error code %d", + nw_error_code, + crt_error_code); + return crt_error_code; +} + +static inline int s_convert_pton_error(int pton_code) { + if (pton_code == 0) { + return AWS_IO_SOCKET_INVALID_ADDRESS; + } + + return s_determine_socket_error(errno); +} + +/* + * A socket is only in one of these states at a time, except for CONNECTED_READ | CONNECTED_WRITE. + * + * The state can only go increasing, except for the following cases + * 1. LISTENING and STOPPED: They can switch between each other. + * 2. CLOSING -> ERROR: It is a valid case where socket state tries to transfer from CLOSING to ERROR, but we never + * actually set it to ERROR if we are already in CLOSING state. This happened in the following scenario: After we + * called aws_socket_close(), the socket state is set to CLOSING. And if a read callback invoked at this time, it + * is possible that the socket reads an ERROR and tries to set the socket state to ERROR, which makes the socket + * state goes backwards. Though this is a valid case, we don't actually set it back to ERROR as we are shutting down the + * socket. + * 3. CONNECT_WRITE and CONNECT_READ: you are allow to flip the flags for these two state, while not going + * backwards to `CONNECTING` and `INIT` state. + */ +enum aws_nw_socket_state { + INVALID = 0x000, + INIT = 0x001, + CONNECTING = 0x002, + CONNECTED_READ = 0x004, + CONNECTED_WRITE = 0x008, + BOUND = 0x010, + LISTENING = 0x020, + STOPPED = 0x040, // Stop the io events, while we could restart it later + ERROR = 0x080, + CLOSING = 0X100, // Only set when aws_socket_close() is called. + CLOSED = 0x200, +}; + +enum aws_nw_socket_mode { + NWSM_CONNECTION, + NWSM_LISTENER, +}; + +struct nw_listener_connection_args { + struct aws_task task; + int error_code; + struct aws_allocator *allocator; + struct nw_socket *nw_socket; + nw_connection_t new_connection; + void *user_data; +}; + +struct nw_socket_timeout_args { + struct aws_task task; + struct aws_allocator *allocator; + struct nw_socket *nw_socket; +}; + +struct nw_socket_scheduled_task_args { + struct aws_task task; + int error_code; + struct aws_allocator *allocator; + struct nw_socket *nw_socket; + dispatch_data_t data; + bool is_complete; +}; + +struct nw_socket_written_args { + struct aws_task task; + int error_code; + struct aws_allocator *allocator; + struct nw_socket *nw_socket; + aws_socket_on_write_completed_fn *written_fn; + void *user_data; + size_t bytes_written; +}; + +struct nw_socket_cancel_task_args { + struct aws_allocator *allocator; + struct nw_socket *nw_socket; + struct aws_task task; +}; + +struct nw_socket { + struct aws_allocator *allocator; + + /* The `nw_socket_ref_count` that keeps the nw_socket alive. The `nw_socket_ref_count` initalized on + * aws_socket_init() and decreased on aws_socket_clean_up() called. The `internal_ref_count` will also keep a + * reference of the `nw_socket_ref_count` so that the nw_socket would alive until all system callbacks and tasks are + * handled. On `nw_socket_ref_count` drops to 0, it invokes s_socket_impl_destroy, which cleanup the nw_socket + * memory and invoke on_socket_cleanup_complete_fn. + */ + struct aws_ref_count nw_socket_ref_count; + + /* The `internal_ref_count` is used to track any in-flight socket operations. It would be init on socket init, and + * acquired on aws_socket_connect()/aws_socket_listen() called. The reference will be decreased on + * nw_connection/listener_state_changed_handler is invoked with a "nw_connection/listener_state_cancelled" state. + * Besides this, each network framework system call or each scheduled task in event loop would also acquire an + * internal reference, and release when the callback invoked or the task executed. + */ + struct aws_ref_count internal_ref_count; + + /* The `write_ref_count` is used to track any in-flight write operations. It would be init on aws_socket_init() and + * dropped on aws_socket_close() call. Each aws_socket_write() function call will acquire a ref-count, and released + * the ref-count on nw_connection_send handler is invoked. + * When the reference is dropped to 0, it invoked the destroy function `s_nw_socket_canceled()`, and start to cancel + * and close the Apple nw_connection/nw_listener. + */ + struct aws_ref_count write_ref_count; + + int last_error; + + /* Apple's native structs for connection and listener. */ + union { + nw_connection_t nw_connection; + nw_listener_t nw_listener; + } os_handle; + nw_parameters_t socket_options_to_params; + /* The socket would be either setup as nw_connection or nw_listener. */ + enum aws_nw_socket_mode mode; + + /* The linked list of `read_queue_node`. The read queue to store read data from io events. aws_socket_read() + * function would read data from the queue. + + * WARNING: The read_queue is not lock protected so far, as we always access it on event loop thread. */ + struct aws_linked_list read_queue; + + /* + * nw_socket is ref counted. It is possible that the aws_socket object is released while nw_socket is still alive + * and processing events. We keep the callbacks and parameters on nw_socket to avoid bad access after the aws_socket + * is released. + */ + aws_socket_on_readable_fn *on_readable; + void *on_readable_user_data; + aws_socket_on_connection_result_fn *on_connection_result_fn; + void *connect_result_user_data; + aws_socket_on_accept_started_fn *on_accept_started_fn; + void *listen_accept_started_user_data; + aws_socket_on_shutdown_complete_fn *on_socket_close_complete_fn; + void *close_user_data; + aws_socket_on_shutdown_complete_fn *on_socket_cleanup_complete_fn; + void *cleanup_user_data; + + /* nw_socket had to be assigned to an event loop to process events. The nw_socket will acquire a reference of the + * event_loop's base event group to kept the event loop alive. + * + * For client socket (nw_connection): setup on aws_socket_connect() + * For listener (nw_listener) : setup on aws_socket_start_accept() + * For incoming socket / server socket (nw_connection accepted on a listener): setup by calling + * aws_socket_assign_event_loop() + */ + struct aws_event_loop *event_loop; + + /* Indicate the connection result is updated. This argument is used to cancel the timeout task. The argument should + * be only set on socket event loop. The value will be set to true if: + * 1. nw_connection returned with state=`nw_connection_state_ready`, indicating the connection succeed + * 2. nw_connection returned with state=`nw_connection_state_failed`, indicating the connection failed + * 3. directly set to true for the incoming socket, as the incoming socket is already connected + */ + bool connection_setup; + + /* Timeout task that is created on aws_socket_connect(). The task will be flagged to be canceled if the connection + * succeed or failed. */ + struct nw_socket_timeout_args *timeout_args; + + /* synced_data and the lock to protect the synced data. */ + struct { + /* Used to avoid scheduling a duplicate read call. We would like to wait for the read call complete back before + * we schedule another one. */ + bool read_scheduled; + /* The aws_nw_socket_state. aws_socket also has a field `state` which should be represent the same parameter, + * however, as it is possible that the aws_socket object is released while nw_socket is still alive, we will use + * nw_socket->state instead of socket->state to verify the socket_state. + */ + enum aws_nw_socket_state state; + struct aws_mutex lock; + } synced_data; + + /* + * The synced data to protect base_socket access. As aws_socket is not ref-counted. It is possible that the user + * called aws_socket_cleanup() to release the aws_socket(base_socket), while the nw_socket is still alive and the + * underlying system calls are still processing the data. Therefore, here nw_socket kept a point to base_socket to + * avoid bad access after aws_socket is cleaned up. The lock is acquired before we do any callback that might access + * the base_socket. + * We put aws_socket in a different base_socket_synced_data struct to avoid the lock contention between other + * cross-thread data, especially when we do a socket operation in a callback when the socket lock is acquired. + * + * As all the callbacks will hold the lock to make sure the base_socket is alive, we should avoid to use the lock in + * user API calls. So far we used it only in aws_socket_cleanup. And handle it in this way to avoid deadlock: if we + * are on the assigned event loop, we assume we are fired on the event loop thread, and we don't need to acquire the + * lock, otherwise, we acquire the lock. + */ + struct { + struct aws_mutex lock; + struct aws_socket *base_socket; + } base_socket_synced_data; +}; + +static size_t KB_16 = 16 * 1024; + +static void *s_socket_acquire_internal_ref(struct nw_socket *nw_socket) { + return aws_ref_count_acquire(&nw_socket->internal_ref_count); +} + +static size_t s_socket_release_internal_ref(struct nw_socket *nw_socket) { + return aws_ref_count_release(&nw_socket->internal_ref_count); +} + +static void *s_socket_acquire_write_ref(struct nw_socket *nw_socket) { + return aws_ref_count_acquire(&nw_socket->write_ref_count); +} + +static size_t s_socket_release_write_ref(struct nw_socket *nw_socket) { + return aws_ref_count_release(&nw_socket->write_ref_count); +} + +static int s_lock_base_socket(struct nw_socket *nw_socket) { + return aws_mutex_lock(&nw_socket->base_socket_synced_data.lock); +} + +static int s_unlock_base_socket(struct nw_socket *nw_socket) { + return aws_mutex_unlock(&nw_socket->base_socket_synced_data.lock); +} + +static int s_lock_socket_synced_data(struct nw_socket *nw_socket) { + return aws_mutex_lock(&nw_socket->synced_data.lock); +} + +static int s_unlock_socket_synced_data(struct nw_socket *nw_socket) { + return aws_mutex_unlock(&nw_socket->synced_data.lock); +} + +static bool s_validate_event_loop(struct aws_event_loop *event_loop) { + return event_loop && event_loop->vtable && event_loop->impl_data; +} + +static void s_set_event_loop(struct aws_socket *aws_socket, struct aws_event_loop *event_loop) { + aws_socket->event_loop = event_loop; + struct nw_socket *nw_socket = aws_socket->impl; + // Never re-assign an event loop + AWS_FATAL_ASSERT(nw_socket->event_loop == NULL); + nw_socket->event_loop = event_loop; + + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKET, "id=%p: s_set_event_loop: socket acquire event loop group.", (void *)nw_socket); + aws_event_loop_group_acquire(get_base_event_loop_group(event_loop)); +} + +static void s_release_event_loop(struct nw_socket *nw_socket) { + if (nw_socket->event_loop == NULL) { + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKET, "id=%p: s_release_event_loop: socket has not event loop.", (void *)nw_socket); + return; + } + aws_event_loop_group_release(get_base_event_loop_group(nw_socket->event_loop)); + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, "id=%p: s_release_event_loop: socket release event loop group.", (void *)nw_socket); + nw_socket->event_loop = NULL; +} + +/* The help function to update the socket state. The function must be called with synced_data locked (use + * s_lock_socket_synced_data() / s_unlock_socket_synced_data()), as the function touches the synced_data.state. */ +static void s_set_socket_state(struct nw_socket *nw_socket, enum aws_nw_socket_state state) { + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p: s_set_socket_state: socket state set from %d to %d.", + (void *)nw_socket, + nw_socket->synced_data.state, + state); + enum aws_nw_socket_state result_state = nw_socket->synced_data.state; + + // clip the read/write bits + enum aws_nw_socket_state read_write_bits = state & (CONNECTED_WRITE | CONNECTED_READ); + result_state = result_state & ~CONNECTED_WRITE & ~CONNECTED_READ; + + // If the caller would like simply flip the read/write bits, set the state to invalid, as we dont have further + // information there. + if (~CONNECTED_WRITE == (int)state || ~CONNECTED_READ == (int)state) { + state = INVALID; + } + + // The state can only go increasing, except for the following cases + // 1. LISTENING and STOPPED: They can switch between each other. + // 2. CLOSING -> ERROR: It is a valid case where socket state tries to transfer from CLOSING to ERROR. This + // happened in the following scenario: After we called aws_socket_close(), the socket state is set to CLOSING. And + // if a read callback invoked at this time, it is possible that the socket reads an ERROR and tries to set the + // socket state to ERROR, which makes the socket state goes backwards. Though this is a valid case, we don't + // actually set it back to ERROR as we are shutting down the socket. + // 3. CONNECT_WRITE and CONNECT_READ: you are allow to flip the flags for these two state, while not going + // backwards to `CONNECTING` and `INIT` state. + if (result_state < state || (state == LISTENING && result_state == STOPPED)) { + result_state = state; + } + + // Set CONNECTED_WRITE and CONNECTED_READ + result_state = result_state | read_write_bits; + + nw_socket->synced_data.state = result_state; + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p: s_set_socket_state: socket state set to %d.", + (void *)nw_socket, + nw_socket->synced_data.state); +} + +static int s_setup_socket_params(struct nw_socket *nw_socket, const struct aws_socket_options *options) { + if (options->type == AWS_SOCKET_STREAM) { + /* if TCP, setup all the tcp options */ + switch (options->domain) { + case AWS_SOCKET_IPV4: + case AWS_SOCKET_IPV6: { + // DEBUG WIP NW_PARAMETERS_DISABLE_PROTOCOL will need to be changed to use MTLS With SecItem + nw_socket->socket_options_to_params = nw_parameters_create_secure_tcp( + NW_PARAMETERS_DISABLE_PROTOCOL, ^(nw_protocol_options_t nw_options) { + if (options->connect_timeout_ms) { + /* this value gets set in seconds. */ + nw_tcp_options_set_connection_timeout( + nw_options, options->connect_timeout_ms / AWS_TIMESTAMP_MILLIS); + } + + // Only change default keepalive values if keepalive is true and both interval and timeout + // are not zero. + if (options->keepalive && options->keep_alive_interval_sec != 0 && + options->keep_alive_timeout_sec != 0) { + nw_tcp_options_set_enable_keepalive(nw_options, options->keepalive); + nw_tcp_options_set_keepalive_idle_time(nw_options, options->keep_alive_interval_sec); + nw_tcp_options_set_keepalive_interval(nw_options, options->keep_alive_timeout_sec); + } + + if (options->keep_alive_max_failed_probes) { + nw_tcp_options_set_keepalive_count(nw_options, options->keep_alive_max_failed_probes); + } + + if (g_aws_channel_max_fragment_size < KB_16) { + nw_tcp_options_set_maximum_segment_size(nw_options, g_aws_channel_max_fragment_size); + } + }); + break; + } + case AWS_SOCKET_LOCAL: { + nw_socket->socket_options_to_params = nw_parameters_create_secure_tcp( + NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION); + break; + } + default: + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p options=%p: AWS_SOCKET_VSOCK is not supported on nw_socket.", + (void *)nw_socket, + (void *)options); + return aws_raise_error(AWS_IO_SOCKET_UNSUPPORTED_ADDRESS_FAMILY); + } + } else if (options->type == AWS_SOCKET_DGRAM) { + nw_socket->socket_options_to_params = + nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION); + } + + if (!nw_socket->socket_options_to_params) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p options=%p: failed to create nw_parameters_t for nw_socket.", + (void *)nw_socket, + (void *)options); + return aws_raise_error(AWS_IO_SOCKET_INVALID_OPTIONS); + } + + nw_parameters_set_reuse_local_address(nw_socket->socket_options_to_params, true); + + return AWS_OP_SUCCESS; +} + +static void s_socket_cleanup_fn(struct aws_socket *socket); +static int s_socket_connect_fn( + struct aws_socket *socket, + const struct aws_socket_endpoint *remote_endpoint, + struct aws_event_loop *event_loop, + aws_socket_on_connection_result_fn *on_connection_result, + void *user_data); +static int s_socket_bind_fn(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint); +static int s_socket_listen_fn(struct aws_socket *socket, int backlog_size); +static int s_socket_start_accept_fn( + struct aws_socket *socket, + struct aws_event_loop *accept_loop, + struct aws_socket_listener_options options); +static int s_socket_stop_accept_fn(struct aws_socket *socket); +static int s_socket_close_fn(struct aws_socket *socket); +static int s_socket_shutdown_dir_fn(struct aws_socket *socket, enum aws_channel_direction dir); +static int s_socket_set_options_fn(struct aws_socket *socket, const struct aws_socket_options *options); +static int s_socket_assign_to_event_loop_fn(struct aws_socket *socket, struct aws_event_loop *event_loop); +static int s_socket_subscribe_to_readable_events_fn( + struct aws_socket *socket, + aws_socket_on_readable_fn *on_readable, + void *user_data); +static int s_socket_read_fn(struct aws_socket *socket, struct aws_byte_buf *buffer, size_t *amount_read); +static int s_socket_write_fn( + struct aws_socket *socket, + const struct aws_byte_cursor *cursor, + aws_socket_on_write_completed_fn *written_fn, + void *user_data); +static int s_socket_get_error_fn(struct aws_socket *socket); +static bool s_socket_is_open_fn(struct aws_socket *socket); +static int s_set_close_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data); +static int s_set_cleanup_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data); + +static struct aws_socket_vtable s_vtable = { + .socket_cleanup_fn = s_socket_cleanup_fn, + .socket_connect_fn = s_socket_connect_fn, + .socket_bind_fn = s_socket_bind_fn, + .socket_listen_fn = s_socket_listen_fn, + .socket_start_accept_fn = s_socket_start_accept_fn, + .socket_stop_accept_fn = s_socket_stop_accept_fn, + .socket_close_fn = s_socket_close_fn, + .socket_shutdown_dir_fn = s_socket_shutdown_dir_fn, + .socket_set_options_fn = s_socket_set_options_fn, + .socket_assign_to_event_loop_fn = s_socket_assign_to_event_loop_fn, + .socket_subscribe_to_readable_events_fn = s_socket_subscribe_to_readable_events_fn, + .socket_read_fn = s_socket_read_fn, + .socket_write_fn = s_socket_write_fn, + .socket_get_error_fn = s_socket_get_error_fn, + .socket_is_open_fn = s_socket_is_open_fn, + .socket_set_close_callback = s_set_close_callback, + .socket_set_cleanup_callback = s_set_cleanup_callback, +}; + +static int s_schedule_next_read(struct nw_socket *socket); + +static void s_socket_cleanup_fn(struct aws_socket *socket) { + if (!socket->impl) { + /* protect from double clean */ + return; + } + + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKET, "id=%p nw_socket=%p: is cleanup...", (void *)socket, (void *)socket->impl); + if (aws_socket_is_open(socket)) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, "id=%p nw_socket=%p: is still open, closing...", (void *)socket, (void *)socket->impl); + aws_socket_close(socket); + } + + struct nw_socket *nw_socket = socket->impl; + + if (s_validate_event_loop(socket->event_loop) && !aws_event_loop_thread_is_callers_thread(socket->event_loop)) { + s_lock_base_socket(nw_socket); + nw_socket->base_socket_synced_data.base_socket = NULL; + s_unlock_base_socket(nw_socket); + } else { + // If we are already on event loop or event loop is unavailable, we should already acquire the lock for base + // socket access + nw_socket->base_socket_synced_data.base_socket = NULL; + } + + aws_ref_count_release(&nw_socket->nw_socket_ref_count); + socket->impl = NULL; + AWS_ZERO_STRUCT(*socket); +} + +struct read_queue_node { + struct aws_allocator *allocator; + dispatch_data_t received_data; + struct aws_linked_list_node node; + size_t region_offset; + // If we didn't finish reading the received_data, we need to keep track of the region offset that we would + // like to resume with + size_t resume_region; +}; + +static void s_read_queue_node_destroy(struct read_queue_node *node) { + /* releases reference count on dispatch_data_t that was increased during creation of read_queue_node */ + dispatch_release(node->received_data); + aws_mem_release(node->allocator, node); +} + +struct socket_close_complete_args { + struct aws_task task; + struct aws_allocator *allocator; + aws_socket_on_shutdown_complete_fn *shutdown_complete_fn; + void *user_data; + struct nw_socket *nw_socket; +}; + +static void s_close_complete_callback(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)status; + (void)task; + struct socket_close_complete_args *task_arg = arg; + struct aws_allocator *allocator = task_arg->allocator; + if (task_arg->shutdown_complete_fn) { + task_arg->shutdown_complete_fn(task_arg->user_data); + } + aws_ref_count_release(&task_arg->nw_socket->nw_socket_ref_count); + aws_mem_release(allocator, task_arg); +} + +static void s_socket_impl_destroy(void *sock_ptr) { + struct nw_socket *nw_socket = sock_ptr; + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKET, "id=%p : start s_socket_impl_destroy", (void *)sock_ptr); + /* In case we have leftovers from the read queue, clean them up. */ + while (!aws_linked_list_empty(&nw_socket->read_queue)) { + struct aws_linked_list_node *node = aws_linked_list_pop_front(&nw_socket->read_queue); + struct read_queue_node *read_queue_node = AWS_CONTAINER_OF(node, struct read_queue_node, node); + s_read_queue_node_destroy(read_queue_node); + } + + /* Network Framework cleanup */ + if (nw_socket->socket_options_to_params) { + nw_release(nw_socket->socket_options_to_params); + nw_socket->socket_options_to_params = NULL; + } + + aws_socket_on_shutdown_complete_fn *on_cleanup_complete = nw_socket->on_socket_cleanup_complete_fn; + void *cleanup_user_data = nw_socket->cleanup_user_data; + + aws_mutex_clean_up(&nw_socket->synced_data.lock); + aws_mutex_clean_up(&nw_socket->base_socket_synced_data.lock); + aws_mem_release(nw_socket->allocator, nw_socket); + + nw_socket = NULL; + + if (on_cleanup_complete) { + on_cleanup_complete(cleanup_user_data); + } +} + +static void s_process_socket_cancel_task(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + (void)status; + struct nw_socket_cancel_task_args *args = arg; + struct nw_socket *nw_socket = args->nw_socket; + + AWS_LOGF_TRACE(AWS_LS_IO_SOCKET, "id=%p: start to process socket cancel task.", (void *)nw_socket); + + // The task should always run event when status == AWS_TASK_STATUS_CANCELLED. We rely on the task to clean up the + // system connection/listener. And release the socket memory. + + if ((nw_socket->mode == NWSM_CONNECTION && nw_socket->os_handle.nw_connection != NULL) || + (nw_socket->mode == NWSM_LISTENER && nw_socket->os_handle.nw_listener != NULL)) { + // The timeout_args only setup for connected client connections. + if (nw_socket->mode == NWSM_CONNECTION && nw_socket->timeout_args && !nw_socket->connection_setup) { + // if the connection setup is not set, the timeout task has not yet triggered, cancel it. + aws_event_loop_cancel_task(nw_socket->event_loop, &nw_socket->timeout_args->task); + } + + if (nw_socket->mode == NWSM_LISTENER) { + nw_listener_cancel(nw_socket->os_handle.nw_listener); + nw_release(nw_socket->os_handle.nw_listener); + nw_socket->os_handle.nw_listener = NULL; + } else if (nw_socket->mode == NWSM_CONNECTION) { + nw_connection_cancel(nw_socket->os_handle.nw_connection); + nw_release(nw_socket->os_handle.nw_connection); + nw_socket->os_handle.nw_connection = NULL; + } + } + + s_socket_release_internal_ref(nw_socket); + aws_mem_release(args->allocator, args); +} + +// Cancel the socket and close the connection. The cancel should happened on the event loop. +static void s_handle_socket_canceled(void *socket_ptr) { + struct nw_socket *nw_socket = socket_ptr; + + struct nw_socket_cancel_task_args *args = + aws_mem_calloc(nw_socket->allocator, 1, sizeof(struct nw_socket_cancel_task_args)); + + args->allocator = nw_socket->allocator; + args->nw_socket = nw_socket; + + /* The socket cancel should happened on the event loop if possible. The event loop will not set + * in the case where the socket is never connected/ listener is never started accept. + */ + if (s_validate_event_loop(nw_socket->event_loop)) { + + aws_task_init(&args->task, s_process_socket_cancel_task, args, "SocketCanceledTask"); + + aws_event_loop_schedule_task_now(nw_socket->event_loop, &args->task); + } else { + s_process_socket_cancel_task(&args->task, args, AWS_TASK_STATUS_RUN_READY); + } +} + +static void s_socket_internal_destroy(void *sock_ptr) { + struct nw_socket *nw_socket = sock_ptr; + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKET, "id=%p : start s_socket_internal_destroy", (void *)sock_ptr); + + if (s_validate_event_loop(nw_socket->event_loop)) { + struct socket_close_complete_args *args = + aws_mem_calloc(nw_socket->allocator, 1, sizeof(struct socket_close_complete_args)); + + args->shutdown_complete_fn = nw_socket->on_socket_close_complete_fn; + args->user_data = nw_socket->close_user_data; + args->allocator = nw_socket->allocator; + args->nw_socket = nw_socket; + // At this point the internal ref count has been dropped to 0, and we are about to release the external ref + // count. + // However, we would still keep the external ref count alive until the s_close_complete_callback callback is + // invoked. Acquire another external ref count to keep the socket alive. It will be released in + // s_close_complete_callback. + aws_ref_count_acquire(&nw_socket->nw_socket_ref_count); + aws_task_init(&args->task, s_close_complete_callback, args, "SocketShutdownCompleteTask"); + + aws_event_loop_schedule_task_now(nw_socket->event_loop, &args->task); + } else { + // If we are not on the event loop + if (nw_socket->on_socket_close_complete_fn) { + nw_socket->on_socket_close_complete_fn(nw_socket->close_user_data); + } + } + s_release_event_loop(nw_socket); + aws_ref_count_release(&nw_socket->nw_socket_ref_count); +} + +int aws_socket_init_apple_nw_socket( + struct aws_socket *socket, + struct aws_allocator *alloc, + const struct aws_socket_options *options) { + AWS_FATAL_ASSERT(options); + AWS_ZERO_STRUCT(*socket); + + // Network Interface is not supported with Apple Network Framework yet + if (options->network_interface_name[0] != 0) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p fd=%d: network_interface_name is not supported on this platform.", + (void *)socket, + socket->io_handle.data.fd); + return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); + } + + struct nw_socket *nw_socket = aws_mem_calloc(alloc, 1, sizeof(struct nw_socket)); + nw_socket->allocator = alloc; + + socket->allocator = alloc; + socket->options = *options; + socket->impl = nw_socket; + socket->vtable = &s_vtable; + + if (s_setup_socket_params(nw_socket, options)) { + aws_mem_release(alloc, nw_socket); + return AWS_OP_ERR; + } + + aws_mutex_init(&nw_socket->synced_data.lock); + aws_mutex_init(&nw_socket->base_socket_synced_data.lock); + nw_socket->base_socket_synced_data.base_socket = socket; + + nw_socket->synced_data.state = INIT; + socket->state = INIT; + + aws_ref_count_init(&nw_socket->nw_socket_ref_count, nw_socket, s_socket_impl_destroy); + aws_ref_count_init(&nw_socket->internal_ref_count, nw_socket, s_socket_internal_destroy); + // The internal_ref_count should keep a reference of the nw_socket_ref_count. When the internal_ref_count + // drop to 0, it would release the nw_socket_ref_count. + aws_ref_count_acquire(&nw_socket->nw_socket_ref_count); + aws_ref_count_init(&nw_socket->write_ref_count, nw_socket, s_handle_socket_canceled); + + aws_linked_list_init(&nw_socket->read_queue); + + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKET, "id=%p fd=%d: socket created.", (void *)nw_socket, socket->io_handle.data.fd); + + return AWS_OP_SUCCESS; +} + +static void s_client_set_dispatch_queue(struct aws_io_handle *handle, void *queue) { + nw_connection_set_queue(handle->data.handle, queue); +} + +static void s_handle_socket_timeout(struct aws_task *task, void *args, aws_task_status status) { + (void)task; + (void)status; + + struct nw_socket_timeout_args *timeout_args = args; + struct nw_socket *nw_socket = timeout_args->nw_socket; + + AWS_LOGF_TRACE(AWS_LS_IO_SOCKET, "task_id=%p: timeout task triggered, evaluating timeouts.", (void *)task); + + s_lock_base_socket(nw_socket); + struct aws_socket *socket = nw_socket->base_socket_synced_data.base_socket; + if (!nw_socket->connection_setup && socket) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: timed out, shutting down.", + (void *)socket, + (void *)nw_socket->os_handle.nw_connection); + + int error_code = AWS_IO_SOCKET_TIMEOUT; + + // Must set timeout_args to NULL to avoid double cancel. Clean up the timeout task + aws_mem_release(nw_socket->allocator, nw_socket->timeout_args); + nw_socket->timeout_args = NULL; + aws_socket_close(socket); + nw_socket->on_connection_result_fn(socket, error_code, nw_socket->connect_result_user_data); + } else { + // If the socket is already setup (either succeed or failed), we have already invoked the callback to notify the + // connection result. No need to invoke again. If the aws_socket is NULL (cleaned up by user), there is no + // meaning to invoke the callback anymore. Simply release the memory in these two cases. + aws_mem_release(nw_socket->allocator, nw_socket->timeout_args); + nw_socket->timeout_args = NULL; + } + + s_unlock_base_socket(nw_socket); + + s_socket_release_internal_ref(nw_socket); + // No need to release task, as task lives on timeout_args on nw_socket. +} + +static void s_process_incoming_data_task(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)task; + (void)status; + struct nw_socket_scheduled_task_args *readable_args = arg; + struct nw_socket *nw_socket = readable_args->nw_socket; + int crt_error = readable_args->error_code; + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: start to process read data.", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection); + + // If data is valid, push it in read_queue. The read_queue should be only accessed in event loop, as the + // task is scheduled in event loop, it is fine to directly access it. + if (readable_args->data) { + // We directly store the dispatch_data returned from kernel. This could potentially be performance concern. + // Another option is to read the data out into heap buffer and store the heap buffer in read_queue. However, + // this would introduce extra memory copy. We would like to keep the dispatch_data_t in read_queue for now. + struct read_queue_node *node = aws_mem_calloc(nw_socket->allocator, 1, sizeof(struct read_queue_node)); + node->allocator = nw_socket->allocator; + node->received_data = readable_args->data; + aws_linked_list_push_back(&nw_socket->read_queue, &node->node); + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: read data is not empty, push data to read_queue", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection); + } + + if (status != AWS_TASK_STATUS_CANCELED) { + s_lock_base_socket(nw_socket); + struct aws_socket *socket = nw_socket->base_socket_synced_data.base_socket; + + // If the protocol is TCP, `is_complete` means the connection is closed, raise the + // AWS_IO_SOCKET_CLOSED error + if (socket && socket->options.type != AWS_SOCKET_DGRAM && readable_args->is_complete) { + crt_error = AWS_IO_SOCKET_CLOSED; + s_lock_socket_synced_data(nw_socket); + s_set_socket_state(nw_socket, ~CONNECTED_READ); + s_unlock_socket_synced_data(nw_socket); + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: socket is complete, flip read flag", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection); + } + + if (nw_socket->on_readable) { + nw_socket->on_readable(socket, crt_error, nw_socket->on_readable_user_data); + } + s_unlock_base_socket(nw_socket); + } + + s_socket_release_internal_ref(nw_socket); + + aws_mem_release(readable_args->allocator, readable_args); +} + +static void s_handle_incoming_data( + struct nw_socket *nw_socket, + int error_code, + dispatch_data_t data, + bool is_complete) { + + if (s_validate_event_loop(nw_socket->event_loop)) { + struct nw_socket_scheduled_task_args *args = + aws_mem_calloc(nw_socket->allocator, 1, sizeof(struct nw_socket_scheduled_task_args)); + + args->is_complete = is_complete; + args->nw_socket = nw_socket; + args->allocator = nw_socket->allocator; + args->error_code = error_code; + + if (data) { + dispatch_retain(data); + args->data = data; + } + s_socket_acquire_internal_ref(nw_socket); + aws_task_init(&args->task, s_process_incoming_data_task, args, "readableTask"); + + aws_event_loop_schedule_task_now(nw_socket->event_loop, &args->task); + } +} + +static void s_process_connection_result_task(struct aws_task *task, void *arg, enum aws_task_status status) { + (void)status; + (void)task; + + struct nw_socket_scheduled_task_args *task_args = arg; + struct nw_socket *nw_socket = task_args->nw_socket; + + AWS_LOGF_TRACE(AWS_LS_IO_SOCKET, "id=%p: start to process connection result task.", (void *)nw_socket); + + if (status != AWS_TASK_STATUS_CANCELED) { + s_lock_base_socket(nw_socket); + struct aws_socket *socket = nw_socket->base_socket_synced_data.base_socket; + if (socket && nw_socket->on_connection_result_fn) + nw_socket->on_connection_result_fn(socket, task_args->error_code, nw_socket->connect_result_user_data); + s_unlock_base_socket(nw_socket); + } + + s_socket_release_internal_ref(nw_socket); + + aws_mem_release(task_args->allocator, task_args); +} + +static void s_handle_on_connection_result(struct nw_socket *nw_socket, int error_code) { + + if (s_validate_event_loop(nw_socket->event_loop)) { + struct nw_socket_scheduled_task_args *args = + aws_mem_calloc(nw_socket->allocator, 1, sizeof(struct nw_socket_scheduled_task_args)); + + args->nw_socket = s_socket_acquire_internal_ref(nw_socket); + args->allocator = nw_socket->allocator; + args->error_code = error_code; + + aws_task_init(&args->task, s_process_connection_result_task, args, "connectionSuccessTask"); + aws_event_loop_schedule_task_now(nw_socket->event_loop, &args->task); + } +} + +struct connection_state_change_args { + struct aws_task task; + struct aws_allocator *allocator; + struct nw_socket *nw_socket; + nw_connection_t nw_connection; + nw_connection_state_t state; + int error; +}; + +static void s_process_connection_state_changed_task(struct aws_task *task, void *args, enum aws_task_status status) { + (void)status; + (void)task; + + struct connection_state_change_args *connection_args = args; + + struct nw_socket *nw_socket = connection_args->nw_socket; + nw_connection_t nw_connection = connection_args->nw_connection; + nw_connection_state_t state = connection_args->state; + + /* Ideally we should not have a canceled task here, as nw_socket keeps a reference to event loop, therefore the + * event loop should never be destroyed before the nw_socket get destroyed. If we manually cancel the task, we + * should make sure we carefully handled the state change eventually, as the socket relies on this task to release + * and cleanup. + */ + if (status != AWS_TASK_STATUS_CANCELED) { + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: Apple network framework socket connection state changed to %d, nw error code : %d", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection, + connection_args->state, + connection_args->error); + + switch (state) { + case nw_connection_state_cancelled: { + s_lock_socket_synced_data(nw_socket); + s_set_socket_state(nw_socket, CLOSED); + s_unlock_socket_synced_data(nw_socket); + + s_socket_release_internal_ref(nw_socket); + break; + } + case nw_connection_state_ready: { + s_lock_base_socket(nw_socket); + struct aws_socket *socket = nw_socket->base_socket_synced_data.base_socket; + if (socket) { + nw_path_t path = nw_connection_copy_current_path(nw_connection); + nw_endpoint_t local_endpoint = nw_path_copy_effective_local_endpoint(path); + nw_release(path); + const char *hostname = nw_endpoint_get_hostname(local_endpoint); + uint16_t port = nw_endpoint_get_port(local_endpoint); + nw_release(local_endpoint); + + if (hostname != NULL) { + size_t hostname_len = strlen(hostname); + size_t buffer_size = AWS_ARRAY_SIZE(socket->local_endpoint.address); + size_t to_copy = aws_min_size(hostname_len, buffer_size); + memcpy(socket->local_endpoint.address, hostname, to_copy); + socket->local_endpoint.port = port; + } + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: set local endpoint %s:%d", + (void *)socket, + socket->io_handle.data.handle, + socket->local_endpoint.address, + port); + } else { + // This happens when the aws_socket_clean_up() get called before the nw_connection_state_ready get + // returned. We still want to set the socket to write/read state and fire the connection succeed + // callback until we get the "nw_connection_state_cancelled" status. + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: connection succeed, however, the base socket has been cleaned up.", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection); + } + s_lock_socket_synced_data(nw_socket); + s_set_socket_state(nw_socket, CONNECTED_WRITE | CONNECTED_READ); + s_unlock_socket_synced_data(nw_socket); + s_unlock_base_socket(nw_socket); + + nw_socket->connection_setup = true; + // Cancel the connection timeout task + if (nw_socket->timeout_args) { + aws_event_loop_cancel_task(nw_socket->event_loop, &nw_socket->timeout_args->task); + } + aws_ref_count_acquire(&nw_socket->nw_socket_ref_count); + s_handle_on_connection_result(nw_socket, AWS_OP_SUCCESS); + aws_ref_count_release(&nw_socket->nw_socket_ref_count); + break; + } + case nw_connection_state_waiting: + case nw_connection_state_preparing: + case nw_connection_state_failed: + default: + break; + } + + int crt_error_code = connection_args->error; + if (crt_error_code) { + /* any error, including if closed remotely in error */ + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: socket connection got error: %d", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection, + crt_error_code); + + nw_socket->last_error = crt_error_code; + s_lock_socket_synced_data(nw_socket); + s_set_socket_state(nw_socket, ERROR); + s_unlock_socket_synced_data(nw_socket); + + if (!nw_socket->connection_setup) { + s_handle_on_connection_result(nw_socket, crt_error_code); + nw_socket->connection_setup = true; + // Cancel the connection timeout task + if (nw_socket->timeout_args) { + aws_event_loop_cancel_task(nw_socket->event_loop, &nw_socket->timeout_args->task); + } + } else { + s_handle_incoming_data(nw_socket, nw_socket->last_error, NULL, false); + } + } + } + + s_socket_release_internal_ref(nw_socket); + aws_mem_release(connection_args->allocator, connection_args); +} + +static void s_handle_connection_state_changed_fn( + struct nw_socket *nw_socket, + nw_connection_t nw_connection, + nw_connection_state_t state, + nw_error_t error) { + + AWS_LOGF_TRACE(AWS_LS_IO_SOCKET, "id=%p: s_handle_connection_state_changed_fn start...", (void *)nw_socket); + + int crt_error_code = s_convert_nw_error(error); + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: s_handle_connection_state_changed_fn invoked error code %d.", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection, + crt_error_code); + + if (s_validate_event_loop(nw_socket->event_loop)) { + struct connection_state_change_args *args = + aws_mem_calloc(nw_socket->allocator, 1, sizeof(struct connection_state_change_args)); + + args->nw_socket = nw_socket; + args->allocator = nw_socket->allocator; + args->error = crt_error_code; + args->state = state; + args->nw_connection = nw_connection; + + s_socket_acquire_internal_ref(nw_socket); + + aws_task_init(&args->task, s_process_connection_state_changed_task, args, "ConnectionStateChangedTask"); + + aws_event_loop_schedule_task_now(nw_socket->event_loop, &args->task); + + } else if (state == nw_connection_state_cancelled) { + s_socket_release_internal_ref(nw_socket); + } +} + +static void s_process_listener_success_task(struct aws_task *task, void *args, enum aws_task_status status) { + (void)task; + struct nw_listener_connection_args *task_args = args; + struct aws_allocator *allocator = task_args->allocator; + struct nw_socket *listener_nw_socket = task_args->nw_socket; + int error = task_args->error_code; + + AWS_FATAL_ASSERT(listener_nw_socket && listener_nw_socket->mode == NWSM_LISTENER); + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: start to process incoming connection.", + (void *)listener_nw_socket, + (void *)listener_nw_socket->os_handle.nw_listener); + + if (status == AWS_TASK_STATUS_RUN_READY) { + s_lock_base_socket(listener_nw_socket); + struct aws_socket *listener = listener_nw_socket->base_socket_synced_data.base_socket; + AWS_FATAL_ASSERT(listener && listener->accept_result_fn); + struct aws_socket *new_socket = NULL; + + if (error) { + goto incoming_listener_error_cleanup; + } + + new_socket = aws_mem_calloc(allocator, 1, sizeof(struct aws_socket)); + struct aws_socket_options options = listener->options; + error = aws_socket_init(new_socket, allocator, &options); + if (error) { + goto incoming_listener_error_cleanup; + } + + nw_endpoint_t endpoint = nw_connection_copy_endpoint(task_args->new_connection); + const char *hostname = nw_endpoint_get_hostname(endpoint); + uint16_t port = nw_endpoint_get_port(endpoint); + + if (hostname != NULL) { + size_t address_strlen; + if (aws_secure_strlen(hostname, AWS_ADDRESS_MAX_LEN, &address_strlen)) { + nw_release(endpoint); + goto incoming_listener_error_cleanup; + } + + struct aws_byte_buf hostname_buf = aws_byte_buf_from_c_str(hostname); + struct aws_byte_buf address_buf = + aws_byte_buf_from_empty_array(new_socket->remote_endpoint.address, AWS_ADDRESS_MAX_LEN); + aws_byte_buf_write_from_whole_buffer(&address_buf, hostname_buf); + aws_byte_buf_clean_up(&address_buf); + aws_byte_buf_clean_up(&hostname_buf); + + new_socket->remote_endpoint.port = port; + } + nw_release(endpoint); + + new_socket->io_handle.data.handle = task_args->new_connection; + new_socket->io_handle.set_queue = s_client_set_dispatch_queue; + + struct nw_socket *new_nw_socket = new_socket->impl; + new_nw_socket->os_handle.nw_connection = task_args->new_connection; + new_nw_socket->connection_setup = true; + + // Setup socket state to start read/write operations. We didn't lock here as we are in initializing process, no + // other process will touch the socket state. + s_set_socket_state(new_nw_socket, CONNECTED_READ | CONNECTED_WRITE); + + // this internal ref will be released when the connection canceled ( connection state changed to + // nw_connection_state_cancelled) + s_socket_acquire_internal_ref(new_nw_socket); + + nw_connection_set_state_changed_handler( + new_socket->io_handle.data.handle, ^(nw_connection_state_t state, nw_error_t error) { + s_handle_connection_state_changed_fn(new_nw_socket, new_nw_socket->os_handle.nw_connection, state, error); + }); + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: incoming connection has been successfully connected to %s:%d, the incoming " + "handle is %p", + (void *)listener, + listener->io_handle.data.handle, + new_socket->remote_endpoint.address, + new_socket->remote_endpoint.port, + new_socket->io_handle.data.handle); + + goto incoming_listener_finalize; + + incoming_listener_error_cleanup: + if (new_socket) { + aws_socket_clean_up(new_socket); + aws_mem_release(allocator, new_socket); + new_socket = NULL; + } + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: failed to setup new socket for incoming connection with error code %d.", + (void *)listener, + listener->io_handle.data.handle, + error); + nw_release(task_args->new_connection); + + incoming_listener_finalize: + listener->accept_result_fn(listener, error, new_socket, task_args->user_data); + + s_unlock_base_socket(listener_nw_socket); + + } else { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: process incoming listener task canceled .", + (void *)listener_nw_socket, + (void *)listener_nw_socket->os_handle.nw_listener); + // If the task is not scheduled, release the connection. + nw_release(task_args->new_connection); + } + + s_socket_release_internal_ref(listener_nw_socket); + aws_mem_release(task_args->allocator, task_args); +} + +static void s_handle_on_listener_success( + struct nw_socket *nw_socket, + int error_code, + nw_connection_t new_connection, + void *user_data) { + + if (s_validate_event_loop(nw_socket->event_loop)) { + + struct nw_listener_connection_args *args = + aws_mem_calloc(nw_socket->allocator, 1, sizeof(struct nw_listener_connection_args)); + + args->nw_socket = nw_socket; + args->allocator = nw_socket->allocator; + args->error_code = error_code; + args->new_connection = new_connection; + args->user_data = user_data; + + s_socket_acquire_internal_ref(nw_socket); + nw_retain(new_connection); + + aws_task_init(&args->task, s_process_listener_success_task, args, "listenerSuccessTask"); + aws_event_loop_schedule_task_now(nw_socket->event_loop, &args->task); + } +} + +static void s_process_write_task(struct aws_task *task, void *args, enum aws_task_status status) { + (void)task; + struct nw_socket_written_args *task_args = args; + struct aws_allocator *allocator = task_args->allocator; + struct nw_socket *nw_socket = task_args->nw_socket; + + AWS_LOGF_TRACE(AWS_LS_IO_SOCKET, "id=%p: start to process write task.", (void *)nw_socket); + + if (status != AWS_TASK_STATUS_CANCELED) { + s_lock_base_socket(nw_socket); + struct aws_socket *socket = nw_socket->base_socket_synced_data.base_socket; + if (task_args->written_fn) { + task_args->written_fn(socket, task_args->error_code, task_args->bytes_written, task_args->user_data); + } + s_unlock_base_socket(nw_socket); + } + + s_socket_release_internal_ref(nw_socket); + + aws_mem_release(allocator, task_args); +} + +static void s_handle_write_fn( + struct nw_socket *nw_socket, + int error_code, + size_t bytes_written, + void *user_data, + aws_socket_on_write_completed_fn *written_fn) { + AWS_FATAL_ASSERT(s_validate_event_loop(nw_socket->event_loop)); + + struct nw_socket_written_args *args = + aws_mem_calloc(nw_socket->allocator, 1, sizeof(struct nw_socket_written_args)); + + args->nw_socket = nw_socket; + args->allocator = nw_socket->allocator; + args->error_code = error_code; + args->written_fn = written_fn; + args->user_data = user_data; + args->bytes_written = bytes_written; + s_socket_acquire_internal_ref(nw_socket); + + aws_task_init(&args->task, s_process_write_task, args, "writtenTask"); + + aws_event_loop_schedule_task_now(nw_socket->event_loop, &args->task); +} + +static int s_socket_connect_fn( + struct aws_socket *socket, + const struct aws_socket_endpoint *remote_endpoint, + struct aws_event_loop *event_loop, + aws_socket_on_connection_result_fn *on_connection_result, + void *user_data) { + struct nw_socket *nw_socket = socket->impl; + + AWS_FATAL_ASSERT(event_loop); + AWS_FATAL_ASSERT(!socket->event_loop); + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, "id=%p handle=%p: beginning connect.", (void *)socket, socket->io_handle.data.handle); + + // Apple Network Framework uses a connection based abstraction on top of the UDP layer. We should always do an + // "connect" action after aws_socket_init() regardless it's a UDP socket or a TCP socket. + AWS_FATAL_ASSERT(on_connection_result); + s_lock_socket_synced_data(nw_socket); + if (nw_socket->synced_data.state != INIT) { + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_SOCKET_ILLEGAL_OPERATION_FOR_STATE); + } + + /* fill in posix sock addr, and then let Network framework sort it out. */ + size_t address_strlen; + if (aws_secure_strlen(remote_endpoint->address, AWS_ADDRESS_MAX_LEN, &address_strlen)) { + s_unlock_socket_synced_data(nw_socket); + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: failed to parse address %s:%d.", + (void *)socket, + socket->io_handle.data.handle, + remote_endpoint->address, + (int)remote_endpoint->port); + return aws_raise_error(AWS_IO_SOCKET_INVALID_ADDRESS); + } + + struct socket_address address; + AWS_ZERO_STRUCT(address); + int pton_err = 1; + + switch (socket->options.domain) { + case AWS_SOCKET_IPV4: { + pton_err = inet_pton(AF_INET, remote_endpoint->address, &address.sock_addr_types.addr_in.sin_addr); + address.sock_addr_types.addr_in.sin_port = htons((uint16_t)remote_endpoint->port); + address.sock_addr_types.addr_in.sin_family = AF_INET; + address.sock_addr_types.addr_in.sin_len = sizeof(struct sockaddr_in); + break; + } + case AWS_SOCKET_IPV6: { + pton_err = inet_pton(AF_INET6, remote_endpoint->address, &address.sock_addr_types.addr_in6.sin6_addr); + address.sock_addr_types.addr_in6.sin6_port = htons((uint16_t)remote_endpoint->port); + address.sock_addr_types.addr_in6.sin6_family = AF_INET6; + address.sock_addr_types.addr_in6.sin6_len = sizeof(struct sockaddr_in6); + break; + } + case AWS_SOCKET_LOCAL: { + address.sock_addr_types.un_addr.sun_family = AF_UNIX; + strncpy(address.sock_addr_types.un_addr.sun_path, remote_endpoint->address, AWS_ADDRESS_MAX_LEN); + address.sock_addr_types.un_addr.sun_len = sizeof(struct sockaddr_un); + break; + } + default: { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: socket tried to bind to an unknow domain.", + (void *)socket, + socket->io_handle.data.handle); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_SOCKET_UNSUPPORTED_ADDRESS_FAMILY); + } + } + + if (pton_err != 1) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: failed to parse address %s:%d.", + (void *)socket, + socket->io_handle.data.handle, + remote_endpoint->address, + (int)remote_endpoint->port); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(s_convert_pton_error(pton_err)); + } + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: connecting to endpoint %s:%d.", + (void *)socket, + socket->io_handle.data.handle, + remote_endpoint->address, + (int)remote_endpoint->port); + + nw_endpoint_t endpoint = nw_endpoint_create_address(&address.sock_addr_types.addr_base); + + if (!endpoint) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: failed to create remote address %s:%d.", + (void *)socket, + socket->io_handle.data.handle, + remote_endpoint->address, + (int)remote_endpoint->port); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_SOCKET_INVALID_ADDRESS); + } + + socket->io_handle.data.handle = nw_connection_create(endpoint, nw_socket->socket_options_to_params); + nw_release(endpoint); + + if (!socket->io_handle.data.handle) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: connection creation failed, please verify the socket options are setup properly.", + (void *)socket, + socket->io_handle.data.handle); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + socket->remote_endpoint = *remote_endpoint; + nw_socket->os_handle.nw_connection = socket->io_handle.data.handle; + + socket->io_handle.set_queue = s_client_set_dispatch_queue; + aws_event_loop_connect_handle_to_io_completion_port(event_loop, &socket->io_handle); + s_set_event_loop(socket, event_loop); + + nw_socket->on_connection_result_fn = on_connection_result; + nw_socket->connect_result_user_data = user_data; + + nw_socket->timeout_args = aws_mem_calloc(socket->allocator, 1, sizeof(struct nw_socket_timeout_args)); + + nw_socket->timeout_args->nw_socket = nw_socket; + nw_socket->timeout_args->allocator = socket->allocator; + + aws_task_init( + &nw_socket->timeout_args->task, + s_handle_socket_timeout, + nw_socket->timeout_args, + "NWSocketConnectionTimeoutTask"); + + /* schedule a task to run at the connect timeout interval, if this task runs before the connect + * happens, we consider that a timeout. */ + + uint64_t timeout = 0; + aws_event_loop_current_clock_time(event_loop, &timeout); + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: start connection at %llu.", + (void *)socket, + socket->io_handle.data.handle, + (unsigned long long)timeout); + timeout += + aws_timestamp_convert(socket->options.connect_timeout_ms, AWS_TIMESTAMP_MILLIS, AWS_TIMESTAMP_NANOS, NULL); + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: scheduling timeout task for %llu.", + (void *)socket, + socket->io_handle.data.handle, + (unsigned long long)timeout); + nw_socket->timeout_args->task.timestamp = timeout; + // Acquire a nw_socket for the timeout task + s_socket_acquire_internal_ref(nw_socket); + + // The timeout task must schedule before we start the system connection. We will release the timeout args when we + // finished a connection. If we start the system connection first, then it is possible that the connection finished + // before timeout task scheduled, and the timeout args is already released by the time we schedule it. + aws_event_loop_schedule_task_future(event_loop, &nw_socket->timeout_args->task, timeout); + + /* set a handler for socket state changes. This is where we find out if the connection timed out, was successful, + * was disconnected etc .... */ + nw_connection_set_state_changed_handler( + socket->io_handle.data.handle, ^(nw_connection_state_t state, nw_error_t error) { + s_handle_connection_state_changed_fn(nw_socket, nw_socket->os_handle.nw_connection, state, error); + }); + + s_set_socket_state(nw_socket, CONNECTING); + + socket->connect_accept_user_data = user_data; + socket->connection_result_fn = on_connection_result; + + // released when the connection state changed to nw_connection_state_cancelled + s_socket_acquire_internal_ref(nw_socket); + nw_retain(socket->io_handle.data.handle); + nw_connection_start(socket->io_handle.data.handle); + s_unlock_socket_synced_data(nw_socket); + + return AWS_OP_SUCCESS; +} + +static int s_socket_bind_fn(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint) { + struct nw_socket *nw_socket = socket->impl; + + s_lock_socket_synced_data(nw_socket); + if (nw_socket->synced_data.state != INIT) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKET, "id=%p: invalid state for bind operation.", (void *)socket); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_SOCKET_ILLEGAL_OPERATION_FOR_STATE); + } + + socket->local_endpoint = *local_endpoint; + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p: binding to %s:%d.", + (void *)socket, + local_endpoint->address, + (int)local_endpoint->port); + + struct socket_address address; + AWS_ZERO_STRUCT(address); + int pton_err = 1; + switch (socket->options.domain) { + case AWS_SOCKET_IPV4: { + pton_err = inet_pton(AF_INET, local_endpoint->address, &address.sock_addr_types.addr_in.sin_addr); + address.sock_addr_types.addr_in.sin_port = htons((uint16_t)local_endpoint->port); + address.sock_addr_types.addr_in.sin_family = AF_INET; + address.sock_addr_types.addr_in.sin_len = sizeof(struct sockaddr_in); + break; + } + case AWS_SOCKET_IPV6: { + pton_err = inet_pton(AF_INET6, local_endpoint->address, &address.sock_addr_types.addr_in6.sin6_addr); + address.sock_addr_types.addr_in6.sin6_port = htons((uint16_t)local_endpoint->port); + address.sock_addr_types.addr_in6.sin6_family = AF_INET6; + address.sock_addr_types.addr_in6.sin6_len = sizeof(struct sockaddr_in6); + break; + } + case AWS_SOCKET_LOCAL: { + address.sock_addr_types.un_addr.sun_family = AF_UNIX; + address.sock_addr_types.un_addr.sun_len = sizeof(struct sockaddr_un); + + strncpy(address.sock_addr_types.un_addr.sun_path, local_endpoint->address, AWS_ADDRESS_MAX_LEN); + break; + } + default: { + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_SOCKET_UNSUPPORTED_ADDRESS_FAMILY); + } + } + + if (pton_err != 1) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p: failed to parse address %s:%d.", + (void *)socket, + local_endpoint->address, + (int)local_endpoint->port); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(s_convert_pton_error(pton_err)); + } + + nw_endpoint_t endpoint = nw_endpoint_create_address(&address.sock_addr_types.addr_base); + + if (!endpoint) { + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_SOCKET_INVALID_ADDRESS); + } + + nw_parameters_set_local_endpoint(nw_socket->socket_options_to_params, endpoint); + nw_release(endpoint); + + // Apple network framework requires connection besides bind. + s_set_socket_state(nw_socket, BOUND); + s_unlock_socket_synced_data(nw_socket); + + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKET, "id=%p: successfully bound", (void *)socket); + + return AWS_OP_SUCCESS; +} + +static void s_listener_set_dispatch_queue(struct aws_io_handle *handle, void *queue) { + nw_listener_set_queue(handle->data.handle, queue); +} + +static int s_socket_listen_fn(struct aws_socket *socket, int backlog_size) { + (void)backlog_size; + + struct nw_socket *nw_socket = socket->impl; + + s_lock_socket_synced_data(nw_socket); + if (nw_socket->synced_data.state != BOUND) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, "id=%p: invalid state for listen operation. You must call bind first.", (void *)socket); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_SOCKET_ILLEGAL_OPERATION_FOR_STATE); + } + + socket->io_handle.data.handle = nw_listener_create(nw_socket->socket_options_to_params); + + if (!socket->io_handle.data.handle) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p: listener creation failed, please verify the socket options are setup properly.", + (void *)socket); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + socket->io_handle.set_queue = s_listener_set_dispatch_queue; + nw_socket->os_handle.nw_listener = socket->io_handle.data.handle; + nw_retain(socket->io_handle.data.handle); + nw_socket->mode = NWSM_LISTENER; + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: nw_socket successfully listening", + (void *)socket, + socket->io_handle.data.handle); + + s_set_socket_state(nw_socket, LISTENING); + s_unlock_socket_synced_data(nw_socket); + return AWS_OP_SUCCESS; +} + +struct listener_state_changed_args { + struct aws_task task; + struct aws_allocator *allocator; + struct nw_socket *nw_socket; + nw_listener_state_t state; + int error; +}; + +static void s_process_listener_state_changed_task(struct aws_task *task, void *args, enum aws_task_status status) { + (void)status; + (void)task; + + struct listener_state_changed_args *listener_state_changed_args = args; + + struct nw_socket *nw_socket = listener_state_changed_args->nw_socket; + nw_listener_t nw_listener = nw_socket->os_handle.nw_listener; + nw_listener_state_t state = listener_state_changed_args->state; + int crt_error_code = listener_state_changed_args->error; + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: start to process listener state change task.", + (void *)nw_socket, + (void *)nw_listener); + + /* Ideally we should not have a task with AWS_TASK_STATUS_CANCELED here, as the event loop should never be destroyed + * before the nw_socket get destroyed. If we manually cancel the task, we should make sure we carefully handled the + * state change eventually, as the socket relies on this task to release and cleanup. + */ + if (status != AWS_TASK_STATUS_CANCELED) { + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: listener state changed to %d ", + (void *)nw_socket, + (void *)nw_listener, + state); + + switch (state) { + case nw_listener_state_failed: { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: listener failed with error %d", + (void *)nw_socket, + (void *)nw_listener, + crt_error_code); + + s_lock_base_socket(nw_socket); + struct aws_socket *aws_socket = nw_socket->base_socket_synced_data.base_socket; + s_lock_socket_synced_data(nw_socket); + s_set_socket_state(nw_socket, ERROR); + s_unlock_socket_synced_data(nw_socket); + if (nw_socket->on_accept_started_fn) { + nw_socket->on_accept_started_fn( + aws_socket, crt_error_code, nw_socket->listen_accept_started_user_data); + } + s_unlock_base_socket(nw_socket); + break; + } + case nw_listener_state_ready: { + s_lock_base_socket(nw_socket); + struct aws_socket *aws_socket = nw_socket->base_socket_synced_data.base_socket; + if (aws_socket) { + AWS_FATAL_ASSERT(nw_socket->mode == NWSM_LISTENER); + aws_socket->local_endpoint.port = nw_listener_get_port(nw_socket->os_handle.nw_listener); + if (nw_socket->on_accept_started_fn) { + nw_socket->on_accept_started_fn( + aws_socket, AWS_OP_SUCCESS, nw_socket->listen_accept_started_user_data); + } + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: listener on port %d ready ", + (void *)nw_socket, + (void *)nw_listener, + aws_socket->local_endpoint.port); + } + + s_unlock_base_socket(nw_socket); + break; + } + case nw_listener_state_cancelled: { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, "id=%p handle=%p: listener cancelled.", (void *)nw_socket, (void *)nw_listener); + s_lock_socket_synced_data(nw_socket); + s_set_socket_state(nw_socket, CLOSED); + s_unlock_socket_synced_data(nw_socket); + s_socket_release_internal_ref(nw_socket); + break; + } + default: + break; + } + } + + // Release the internal ref for the task + s_socket_release_internal_ref(nw_socket); + aws_mem_release(listener_state_changed_args->allocator, listener_state_changed_args); +} + +static void s_handle_listener_state_changed_fn( + struct nw_socket *nw_socket, + nw_listener_state_t state, + nw_error_t error) { + + AWS_LOGF_TRACE(AWS_LS_IO_SOCKET, "id=%p: s_handle_listener_state_changed_fn start...", (void *)nw_socket); + + int crt_error_code = s_convert_nw_error(error); + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: s_handle_listener_state_changed_fn invoked error code %d.", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection, + crt_error_code); + + if (s_validate_event_loop(nw_socket->event_loop)) { + struct listener_state_changed_args *args = + aws_mem_calloc(nw_socket->allocator, 1, sizeof(struct listener_state_changed_args)); + + args->nw_socket = nw_socket; + args->allocator = nw_socket->allocator; + args->error = crt_error_code; + args->state = state; + + s_socket_acquire_internal_ref(nw_socket); + aws_task_init(&args->task, s_process_listener_state_changed_task, args, "ListenerStateChangedTask"); + aws_event_loop_schedule_task_now(nw_socket->event_loop, &args->task); + } else { + AWS_FATAL_ASSERT(false && "The nw_socket should be always attached to a valid event loop."); + } +} + +static int s_socket_start_accept_fn( + struct aws_socket *socket, + struct aws_event_loop *accept_loop, + struct aws_socket_listener_options options) { + AWS_FATAL_ASSERT(options.on_accept_result); + AWS_FATAL_ASSERT(accept_loop); + + struct nw_socket *nw_socket = socket->impl; + s_lock_socket_synced_data(nw_socket); + if (nw_socket->synced_data.state != LISTENING) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: invalid state for start_accept operation. You must call listen first.", + (void *)socket, + socket->io_handle.data.handle); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_SOCKET_ILLEGAL_OPERATION_FOR_STATE); + } + + if (socket->event_loop) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: is already assigned to event-loop %p.", + (void *)socket, + socket->io_handle.data.handle, + (void *)socket->event_loop); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_EVENT_LOOP_ALREADY_ASSIGNED); + } + + aws_event_loop_connect_handle_to_io_completion_port(accept_loop, &socket->io_handle); + socket->accept_result_fn = options.on_accept_result; + socket->connect_accept_user_data = options.on_accept_result_user_data; + + nw_socket->on_accept_started_fn = options.on_accept_start; + nw_socket->listen_accept_started_user_data = options.on_accept_start_user_data; + + s_set_event_loop(socket, accept_loop); + + nw_listener_set_state_changed_handler( + socket->io_handle.data.handle, ^(nw_listener_state_t state, nw_error_t error) { + s_handle_listener_state_changed_fn(nw_socket, state, error); + }); + + nw_listener_set_new_connection_handler(socket->io_handle.data.handle, ^(nw_connection_t connection) { + s_handle_on_listener_success(nw_socket, AWS_OP_SUCCESS, connection, socket->connect_accept_user_data); + }); + // this ref should be released in nw_listener_set_state_changed_handler where get state == + // nw_listener_state_cancelled + s_socket_acquire_internal_ref(nw_socket); + nw_listener_start(socket->io_handle.data.handle); + s_unlock_socket_synced_data(nw_socket); + return AWS_OP_SUCCESS; +} + +static int s_socket_stop_accept_fn(struct aws_socket *socket) { + struct nw_socket *nw_socket = socket->impl; + s_lock_socket_synced_data(nw_socket); + if (nw_socket->synced_data.state != LISTENING) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: is not in a listening state, can't stop_accept.", + (void *)socket, + socket->io_handle.data.handle); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_SOCKET_ILLEGAL_OPERATION_FOR_STATE); + } + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: stopping accepting new connections", + (void *)socket, + socket->io_handle.data.handle); + + nw_listener_cancel(socket->io_handle.data.handle); + + s_set_socket_state(nw_socket, STOPPED); + s_unlock_socket_synced_data(nw_socket); + + return AWS_OP_SUCCESS; +} + +// Close should always be run on event loop +static int s_socket_close_fn(struct aws_socket *socket) { + + struct nw_socket *nw_socket = socket->impl; + s_lock_socket_synced_data(nw_socket); + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: socket is closing with current state %d", + (void *)socket, + socket->io_handle.data.handle, + socket->state); + + if (nw_socket->synced_data.state < CLOSING) { + // We would like to keep CONNECTED_READ so that we could continue processing any received data until the we got + // the system callback indicates that the system connection has been closed in the receiving direction. + s_set_socket_state(nw_socket, CLOSING | CONNECTED_READ); + s_socket_release_write_ref(nw_socket); + } + s_unlock_socket_synced_data(nw_socket); + return AWS_OP_SUCCESS; +} + +static int s_socket_shutdown_dir_fn(struct aws_socket *socket, enum aws_channel_direction dir) { + (void)dir; + AWS_FATAL_ASSERT(true); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, "id=%p: shutdown by direction is not support for Apple network framework.", (void *)socket); + return aws_raise_error(AWS_IO_SOCKET_INVALID_OPERATION_FOR_TYPE); +} + +static int s_socket_set_options_fn(struct aws_socket *socket, const struct aws_socket_options *options) { + if (socket->options.domain != options->domain || socket->options.type != options->type) { + return aws_raise_error(AWS_IO_SOCKET_INVALID_OPTIONS); + } + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: setting socket options to: keep-alive %d, keep idle %d, keep-alive interval %d, " + "keep-alive " + "probe " + "count %d.", + (void *)socket, + socket->io_handle.data.handle, + (int)options->keepalive, + (int)options->keep_alive_timeout_sec, + (int)options->keep_alive_interval_sec, + (int)options->keep_alive_max_failed_probes); + + socket->options = *options; + + struct nw_socket *nw_socket = socket->impl; + + /* If nw_parameters_t has been previously set, they need to be released prior to assigning a new one */ + if (nw_socket->socket_options_to_params) { + nw_release(nw_socket->socket_options_to_params); + nw_socket->socket_options_to_params = NULL; + } + + return s_setup_socket_params(nw_socket, options); +} + +static int s_socket_assign_to_event_loop_fn(struct aws_socket *socket, struct aws_event_loop *event_loop) { + if (!socket->event_loop) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: assigning to event loop %p", + (void *)socket, + socket->io_handle.data.handle, + (void *)event_loop); + + if (aws_event_loop_connect_handle_to_io_completion_port(event_loop, &socket->io_handle)) { + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: assigning event loop %p failed", + (void *)socket, + socket->io_handle.data.handle, + (void *)event_loop); + return AWS_OP_ERR; + } + + s_set_event_loop(socket, event_loop); + nw_connection_start(socket->io_handle.data.handle); + return AWS_OP_SUCCESS; + } + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: the socket is already assigned with an event loop %p", + (void *)socket, + socket->io_handle.data.handle, + (void *)event_loop); + return aws_raise_error(AWS_IO_EVENT_LOOP_ALREADY_ASSIGNED); +} + +static void s_handle_nw_connection_receive_completion_fn( + dispatch_data_t data, + nw_content_context_t context, + bool is_complete, + nw_error_t error, + struct nw_socket *nw_socket) { + s_lock_socket_synced_data(nw_socket); + nw_socket->synced_data.read_scheduled = false; + s_unlock_socket_synced_data(nw_socket); + + bool complete = is_complete; + int crt_error_code = s_convert_nw_error(error); + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: s_handle_nw_connection_receive_completion_fn invoked error code %d.", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection, + crt_error_code); + + if (!crt_error_code) { + /* For protocols such as TCP, `is_complete` will be marked when the entire stream has be closed in the + * reading direction. For protocols such as UDP, this will be marked when the end of a datagram has + * been reached. */ + + complete = is_complete && nw_content_context_get_is_final(context); + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: queued read buffer of size %d", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection, + data ? (int)dispatch_data_get_size(data) : 0); + } + + // The callback should be fired before schedule next read, so that if the socket is closed, we could + // prevent schedule next read earlier. + s_handle_incoming_data(nw_socket, crt_error_code, data, complete); + + // keep reading from the system socket + s_schedule_next_read(nw_socket); + + s_socket_release_internal_ref(nw_socket); +} + +/* s_schedule_next_read() will setup the nw_connection_receive_completion_t and start a read request to the system + * socket. The handler will get invoked when the system socket has data to read. + * The function is initially fired on the following conditions, and recursively call itself on handler invocation: + * 1. on function call `aws_socket_read()` + * 2. on function call `aws_socket_subscribe_to_readable_events` + */ +static int s_schedule_next_read(struct nw_socket *nw_socket) { + s_lock_socket_synced_data(nw_socket); + + // Once a read operation is scheduled, we should not schedule another one until the current one is + // completed. + if (nw_socket->synced_data.read_scheduled) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: there is already read queued, do not queue further read", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection); + s_unlock_socket_synced_data(nw_socket); + return AWS_OP_SUCCESS; + } + + if (nw_socket->synced_data.state & CLOSING || !(nw_socket->synced_data.state & CONNECTED_READ)) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: cannot read to because socket is not connected", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_SOCKET_NOT_CONNECTED); + } + + nw_socket->synced_data.read_scheduled = true; + + // Acquire nw_socket as we called nw_connection_receive, and the ref will be released when the handler is + // called. + s_socket_acquire_internal_ref(nw_socket); + + /* read and let me know when you've done it. */ + nw_connection_receive( + nw_socket->os_handle.nw_connection, + 1, + UINT32_MAX, + ^(dispatch_data_t data, nw_content_context_t context, bool is_complete, nw_error_t error) { + s_handle_nw_connection_receive_completion_fn(data, context, is_complete, error, nw_socket); + }); + + s_unlock_socket_synced_data(nw_socket); + return AWS_OP_SUCCESS; +} + +static int s_socket_subscribe_to_readable_events_fn( + struct aws_socket *socket, + aws_socket_on_readable_fn *on_readable, + void *user_data) { + struct nw_socket *nw_socket = socket->impl; + + if (nw_socket->mode == NWSM_LISTENER) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: Apple Network Framework does not support read/write on a listener. Please use the " + "incoming socket to track the read/write operation.", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_listener); + return aws_raise_error(AWS_IO_SOCKET_INVALID_OPERATION_FOR_TYPE); + } + + socket->readable_user_data = user_data; + socket->readable_fn = on_readable; + + nw_socket->on_readable = on_readable; + nw_socket->on_readable_user_data = user_data; + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: socket_subscribe_to_readable_events: start to schedule read request.", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection); + + return s_schedule_next_read(nw_socket); +} + +// WARNING: This function should handle the locks carefully. aws_socket_read()&aws_socket_write() should always called +// on event loop thread. +static int s_socket_read_fn(struct aws_socket *socket, struct aws_byte_buf *read_buffer, size_t *amount_read) { + struct nw_socket *nw_socket = socket->impl; + + AWS_FATAL_ASSERT(amount_read); + + if (!aws_event_loop_thread_is_callers_thread(socket->event_loop)) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: cannot read from a different thread than event loop %p", + (void *)socket, + socket->io_handle.data.handle, + (void *)socket->event_loop); + return aws_raise_error(AWS_ERROR_IO_EVENT_LOOP_THREAD_ONLY); + } + + __block size_t max_to_read = read_buffer->capacity - read_buffer->len; + + /* As the function is always called on event loop, we didn't lock protect the read_queue. */ + if (aws_linked_list_empty(&nw_socket->read_queue)) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: read queue is empty, scheduling another read", + (void *)socket, + socket->io_handle.data.handle); + s_lock_socket_synced_data(nw_socket); + if (!(nw_socket->synced_data.state & CONNECTED_READ)) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: socket is not connected to read.", + (void *)socket, + socket->io_handle.data.handle); + s_unlock_socket_synced_data(nw_socket); + + return aws_raise_error(AWS_IO_SOCKET_CLOSED); + } + + *amount_read = 0; + s_unlock_socket_synced_data(nw_socket); + s_schedule_next_read(nw_socket); + return aws_raise_error(AWS_IO_READ_WOULD_BLOCK); + } + + /* loop over the read queue, take the data and copy it over, and do so til we're either out of data + * and need to schedule another read, or we've read entirely into the requested buffer. */ + while (!aws_linked_list_empty(&nw_socket->read_queue) && max_to_read) { + struct aws_linked_list_node *node = aws_linked_list_front(&nw_socket->read_queue); + struct read_queue_node *read_node = AWS_CONTAINER_OF(node, struct read_queue_node, node); + + bool buffer_processed = dispatch_data_apply( + read_node->received_data, + (dispatch_data_applier_t) ^ (dispatch_data_t region, size_t offset, const void *buffer, size_t size) { + (void)region; + (void)offset; + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: Starting read dispatch data region offset: %lu, buffer %p, with size %lu.", + (void *)socket, + socket->io_handle.data.handle, + offset, + buffer, + size); + + if (read_node->resume_region && offset < read_node->resume_region) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: Skipped dispatch data region region : %lu, looking for region: %lu", + (void *)socket, + socket->io_handle.data.handle, + offset, + read_node->resume_region); + return true; + } + size_t to_copy = aws_min_size(max_to_read, size - read_node->region_offset); + aws_byte_buf_write(read_buffer, (const uint8_t *)buffer + read_node->region_offset, to_copy); + max_to_read -= to_copy; + *amount_read += to_copy; + read_node->region_offset += to_copy; + if (read_node->region_offset == size) { + read_node->region_offset = 0; + return true; + } + read_node->resume_region = offset; + return false; + }); + + if (buffer_processed) { + aws_linked_list_remove(node); + s_read_queue_node_destroy(read_node); + } + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: read of %d", + (void *)socket, + socket->io_handle.data.handle, + (int)*amount_read); + } + + return AWS_OP_SUCCESS; +} + +static void s_handle_nw_connection_send_completion_fn( + nw_error_t error, + dispatch_data_t data, + struct nw_socket *nw_socket, + aws_socket_on_write_completed_fn *written_fn, + void *user_data) { + + int crt_error_code = s_convert_nw_error(error); + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: s_handle_nw_connection_send_completion_fn invoked error code %d.", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection, + crt_error_code); + + if (crt_error_code) { + nw_socket->last_error = crt_error_code; + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: error during write %d", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection, + crt_error_code); + } + + size_t written_size = dispatch_data_get_size(data); + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: send written size %d", + (void *)nw_socket, + (void *)nw_socket->os_handle.nw_connection, + (int)written_size); + s_handle_write_fn(nw_socket, crt_error_code, data ? written_size : 0, user_data, written_fn); + s_socket_release_write_ref(nw_socket); + s_socket_release_internal_ref(nw_socket); +} + +// WARNING: This function should be careful with locks. aws_socket_read()&aws_socket_write() should always called on +// event loop thread. +static int s_socket_write_fn( + struct aws_socket *socket, + const struct aws_byte_cursor *cursor, + aws_socket_on_write_completed_fn *written_fn, + void *user_data) { + AWS_FATAL_ASSERT(written_fn); + if (!aws_event_loop_thread_is_callers_thread(socket->event_loop)) { + return aws_raise_error(AWS_ERROR_IO_EVENT_LOOP_THREAD_ONLY); + } + + struct nw_socket *nw_socket = socket->impl; + s_lock_socket_synced_data(nw_socket); + if (!(nw_socket->synced_data.state & CONNECTED_WRITE)) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: cannot write to because it is not connected", + (void *)socket, + socket->io_handle.data.handle); + s_unlock_socket_synced_data(nw_socket); + return aws_raise_error(AWS_IO_SOCKET_NOT_CONNECTED); + } + + dispatch_data_t data = dispatch_data_create(cursor->ptr, cursor->len, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT); + if (!data) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p handle=%p: failed to process write data.", + (void *)socket, + socket->io_handle.data.handle); + return AWS_OP_ERR; + } + s_socket_acquire_internal_ref(nw_socket); + s_socket_acquire_write_ref(nw_socket); + + nw_connection_send( + socket->io_handle.data.handle, data, _nw_content_context_default_message, true, ^(nw_error_t error) { + s_handle_nw_connection_send_completion_fn(error, data, nw_socket, written_fn, user_data); + }); + + s_unlock_socket_synced_data(nw_socket); + + return AWS_OP_SUCCESS; +} + +static int s_socket_get_error_fn(struct aws_socket *socket) { + struct nw_socket *nw_socket = socket->impl; + + return nw_socket->last_error; +} + +static bool s_socket_is_open_fn(struct aws_socket *socket) { + struct nw_socket *nw_socket = socket->impl; + s_lock_socket_synced_data(nw_socket); + bool is_open = nw_socket->synced_data.state < CLOSING; + s_unlock_socket_synced_data(nw_socket); + return is_open; +} + +static int s_set_close_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data) { + struct nw_socket *nw_socket = socket->impl; + nw_socket->close_user_data = user_data; + nw_socket->on_socket_close_complete_fn = fn; + return 0; +} + +static int s_set_cleanup_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data) { + struct nw_socket *nw_socket = socket->impl; + nw_socket->cleanup_user_data = user_data; + nw_socket->on_socket_cleanup_complete_fn = fn; + return 0; +} diff --git a/source/darwin/secure_transport_tls_channel_handler.c b/source/darwin/secure_transport_tls_channel_handler.c index f58248623..e0db53fef 100644 --- a/source/darwin/secure_transport_tls_channel_handler.c +++ b/source/darwin/secure_transport_tls_channel_handler.c @@ -24,6 +24,8 @@ #include #include +#include "./dispatch_queue_event_loop_private.h" + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-variable" #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -710,6 +712,8 @@ static int s_process_read_message( /* continue the while loop */ continue; default: + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, "id=%p: read message processed with OSStatus %d.", (void *)handler, status); /* unexpected error happened */ aws_raise_error(AWS_IO_TLS_ERROR_READ_FAILURE); shutdown_error_code = AWS_IO_TLS_ERROR_READ_FAILURE; @@ -853,7 +857,7 @@ struct secure_transport_ctx { CFArrayRef ca_cert; enum aws_tls_versions minimum_version; struct aws_string *alpn_list; - bool veriify_peer; + bool verify_peer; }; static struct aws_channel_handler *s_tls_handler_new( @@ -941,9 +945,9 @@ static struct aws_channel_handler *s_tls_handler_new( } OSStatus status = noErr; - secure_transport_handler->verify_peer = secure_transport_ctx->veriify_peer; + secure_transport_handler->verify_peer = secure_transport_ctx->verify_peer; - if (!secure_transport_ctx->veriify_peer && protocol_side == kSSLClientSide) { + if (!secure_transport_ctx->verify_peer && protocol_side == kSSLClientSide) { AWS_LOGF_WARN( AWS_LS_IO_TLS, "id=%p: x.509 validation has been disabled. " @@ -959,9 +963,9 @@ static struct aws_channel_handler *s_tls_handler_new( secure_transport_handler->ca_certs = NULL; if (secure_transport_ctx->ca_cert) { secure_transport_handler->ca_certs = secure_transport_ctx->ca_cert; - if (protocol_side == kSSLServerSide && secure_transport_ctx->veriify_peer) { + if (protocol_side == kSSLServerSide && secure_transport_ctx->verify_peer) { SSLSetSessionOption(secure_transport_handler->ctx, kSSLSessionOptionBreakOnClientAuth, true); - } else if (secure_transport_ctx->veriify_peer) { + } else if (secure_transport_ctx->verify_peer) { SSLSetSessionOption(secure_transport_handler->ctx, kSSLSessionOptionBreakOnServerAuth, true); } } @@ -1070,7 +1074,7 @@ static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const stru } } - secure_transport_ctx->veriify_peer = options->verify_peer; + secure_transport_ctx->verify_peer = options->verify_peer; secure_transport_ctx->ca_cert = NULL; secure_transport_ctx->certs = NULL; secure_transport_ctx->ctx.alloc = alloc; diff --git a/source/posix/socket.c b/source/posix/socket.c index 266ad2de2..54b8bf312 100644 --- a/source/posix/socket.c +++ b/source/posix/socket.c @@ -187,6 +187,12 @@ struct posix_socket { bool currently_subscribed; bool continue_accept; bool *close_happened; + + aws_socket_on_shutdown_complete_fn *on_close_complete; + void *close_user_data; + + aws_socket_on_shutdown_complete_fn *on_cleanup_complete; + void *cleanup_user_data; }; static void s_socket_clean_up(struct aws_socket *socket); @@ -201,8 +207,7 @@ static int s_socket_listen(struct aws_socket *socket, int backlog_size); static int s_socket_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data); + struct aws_socket_listener_options options); static int s_socket_stop_accept(struct aws_socket *socket); static int s_socket_set_options(struct aws_socket *socket, const struct aws_socket_options *options); static int s_socket_close(struct aws_socket *socket); @@ -220,6 +225,8 @@ static int s_socket_write( void *user_data); static int s_socket_get_error(struct aws_socket *socket); static bool s_socket_is_open(struct aws_socket *socket); +static int s_set_close_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data); +static int s_set_cleanup_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data); struct aws_socket_vtable s_posix_socket_vtable = { .socket_cleanup_fn = s_socket_clean_up, @@ -237,8 +244,24 @@ struct aws_socket_vtable s_posix_socket_vtable = { .socket_write_fn = s_socket_write, .socket_get_error_fn = s_socket_get_error, .socket_is_open_fn = s_socket_is_open, + .socket_set_close_callback = s_set_close_callback, + .socket_set_cleanup_callback = s_set_cleanup_callback, }; +static int s_set_close_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data) { + struct posix_socket *socket_impl = socket->impl; + socket_impl->close_user_data = user_data; + socket_impl->on_close_complete = fn; + return 0; +} + +static int s_set_cleanup_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data) { + struct posix_socket *socket_impl = socket->impl; + socket_impl->cleanup_user_data = user_data; + socket_impl->on_cleanup_complete = fn; + return 0; +} + static void s_socket_destroy_impl(void *user_data) { struct posix_socket *socket_impl = user_data; aws_mem_release(socket_impl->allocator, socket_impl); @@ -315,6 +338,8 @@ static void s_socket_clean_up(struct aws_socket *socket) { aws_socket_close(socket); } struct posix_socket *socket_impl = socket->impl; + aws_socket_on_shutdown_complete_fn *on_cleanup_complete = socket_impl->on_cleanup_complete; + void *cleanup_user_data = socket_impl->cleanup_user_data; if (aws_ref_count_release(&socket_impl->internal_refcount) != 0) { AWS_LOGF_DEBUG( @@ -326,6 +351,10 @@ static void s_socket_clean_up(struct aws_socket *socket) { AWS_ZERO_STRUCT(*socket); socket->io_handle.data.fd = -1; + + if (on_cleanup_complete) { + on_cleanup_complete(cleanup_user_data); + } } /* Update socket->local_endpoint based on the results of getsockname() */ @@ -613,17 +642,6 @@ static inline int s_convert_pton_error(int pton_code, int errno_value) { return s_determine_socket_error(errno_value); } -struct socket_address { - union sock_addr_types { - struct sockaddr_in addr_in; - struct sockaddr_in6 addr_in6; - struct sockaddr_un un_addr; -#ifdef USE_VSOCK - struct sockaddr_vm vm_addr; -#endif - } sock_addr_types; -}; - #ifdef USE_VSOCK /** Convert a string to a VSOCK CID. Respects the calling convetion of inet_pton: * 0 on error, 1 on success. */ @@ -1117,12 +1135,45 @@ static void s_socket_accept_event( socket->io_handle.data.fd); } +static void s_process_invoke_on_accept_start(struct aws_task *task, void *args, enum aws_task_status status) { + (void)task; + struct on_start_accept_result_args *on_accept_args = args; + if (status == AWS_TASK_STATUS_RUN_READY) { + struct aws_socket *socket = on_accept_args->socket; + + if (on_accept_args->on_accept_start) { + // socket should not be cleaned up until on_accept_result callback is invoked. + AWS_FATAL_ASSERT(socket); + on_accept_args->on_accept_start(socket, on_accept_args->error, on_accept_args->on_accept_start_user_data); + } + } + aws_mem_release(on_accept_args->allocator, args); +} + +static void s_invoke_on_accept_start( + struct aws_allocator *allocator, + struct aws_event_loop *loop, + struct aws_socket *socket, + int error, + aws_socket_on_accept_started_fn *on_accept_start, + void *on_accept_start_user_data) { + struct on_start_accept_result_args *args = aws_mem_calloc(allocator, 1, sizeof(struct on_start_accept_result_args)); + + args->allocator = allocator; + args->socket = socket; + args->error = error; + args->on_accept_start = on_accept_start; + args->on_accept_start_user_data = on_accept_start_user_data; + + aws_task_init(&args->task, s_process_invoke_on_accept_start, args, "SocketOnAcceptStartResultTask"); + aws_event_loop_schedule_task_now(loop, &args->task); +} + static int s_socket_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data) { - AWS_ASSERT(on_accept_result); + struct aws_socket_listener_options options) { + AWS_ASSERT(options.on_accept_result); AWS_ASSERT(accept_loop); if (socket->event_loop) { @@ -1144,8 +1195,8 @@ static int s_socket_start_accept( return aws_raise_error(AWS_IO_SOCKET_ILLEGAL_OPERATION_FOR_STATE); } - socket->accept_result_fn = on_accept_result; - socket->connect_accept_user_data = user_data; + socket->accept_result_fn = options.on_accept_result; + socket->connect_accept_user_data = options.on_accept_result_user_data; socket->event_loop = accept_loop; struct posix_socket *socket_impl = socket->impl; socket_impl->continue_accept = true; @@ -1166,6 +1217,13 @@ static int s_socket_start_accept( return AWS_OP_ERR; } + s_invoke_on_accept_start( + socket->allocator, + accept_loop, + socket, + AWS_OP_SUCCESS, + options.on_accept_start, + options.on_accept_start_user_data); return AWS_OP_SUCCESS; } @@ -1305,7 +1363,8 @@ static int s_socket_set_options(struct aws_socket *socket, const struct aws_sock if (aws_secure_strlen(options->network_interface_name, AWS_NETWORK_INTERFACE_NAME_MAX, &network_interface_length)) { AWS_LOGF_ERROR( AWS_LS_IO_SOCKET, - "id=%p fd=%d: network_interface_name max length must be %d length and NULL terminated", + "id=%p fd=%d: network_interface_name max length must be less or equal than %d bytes including NULL " + "terminated", (void *)socket, socket->io_handle.data.fd, AWS_NETWORK_INTERFACE_NAME_MAX); @@ -1530,6 +1589,9 @@ static int s_socket_close(struct aws_socket *socket) { aws_condition_variable_wait_pred(&args.condition_variable, &args.mutex, s_close_predicate, &args); aws_mutex_unlock(&args.mutex); AWS_LOGF_INFO(AWS_LS_IO_SOCKET, "id=%p fd=%d: close task completed.", (void *)socket, fd_for_logging); + if (socket_impl->on_close_complete) { + socket_impl->on_close_complete(socket_impl->close_user_data); + } if (args.ret_code) { return aws_raise_error(args.ret_code); } @@ -1589,6 +1651,9 @@ static int s_socket_close(struct aws_socket *socket) { } } + if (socket_impl->on_close_complete) { + socket_impl->on_close_complete(socket_impl->close_user_data); + } return AWS_OP_SUCCESS; } diff --git a/source/socket.c b/source/socket.c index c8ab7a1f0..7d942d739 100644 --- a/source/socket.c +++ b/source/socket.c @@ -37,10 +37,9 @@ int aws_socket_listen(struct aws_socket *socket, int backlog_size) { int aws_socket_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data) { + struct aws_socket_listener_options options) { AWS_PRECONDITION(socket->vtable && socket->vtable->socket_start_accept_fn); - return socket->vtable->socket_start_accept_fn(socket, accept_loop, on_accept_result, user_data); + return socket->vtable->socket_start_accept_fn(socket, accept_loop, options); } int aws_socket_stop_accept(struct aws_socket *socket) { @@ -53,6 +52,22 @@ int aws_socket_close(struct aws_socket *socket) { return socket->vtable->socket_close_fn(socket); } +int aws_socket_set_close_complete_callback( + struct aws_socket *socket, + aws_socket_on_shutdown_complete_fn fn, + void *user_data) { + AWS_PRECONDITION(socket->vtable && socket->vtable->socket_set_close_callback); + return socket->vtable->socket_set_close_callback(socket, fn, user_data); +} + +int aws_socket_set_cleanup_complete_callback( + struct aws_socket *socket, + aws_socket_on_shutdown_complete_fn fn, + void *user_data) { + AWS_PRECONDITION(socket->vtable && socket->vtable->socket_set_cleanup_callback); + return socket->vtable->socket_set_cleanup_callback(socket, fn, user_data); +} + int aws_socket_shutdown_dir(struct aws_socket *socket, enum aws_channel_direction dir) { AWS_PRECONDITION(socket->vtable && socket->vtable->socket_shutdown_dir_fn); return socket->vtable->socket_shutdown_dir_fn(socket, dir); @@ -109,7 +124,7 @@ bool aws_socket_is_open(struct aws_socket *socket) { * Return the default socket implementation type. If the return value is `AWS_SOCKET_IMPL_PLATFORM_DEFAULT`, the * function failed to retrieve the default type value. */ -static enum aws_socket_impl_type aws_socket_get_default_impl_type(void) { +enum aws_socket_impl_type aws_socket_get_default_impl_type(void) { // override default socket #ifdef AWS_USE_APPLE_NETWORK_FRAMEWORK return AWS_SOCKET_IMPL_APPLE_NETWORK_FRAMEWORK; @@ -153,8 +168,6 @@ int aws_socket_init(struct aws_socket *socket, struct aws_allocator *alloc, cons case AWS_SOCKET_IMPL_WINSOCK: return aws_socket_init_winsock(socket, alloc, options); case AWS_SOCKET_IMPL_APPLE_NETWORK_FRAMEWORK: - // Apple Network Framework is not implemented yet. We should not use it yet. - AWS_ASSERT(false && "Invalid socket implementation on platform."); return aws_socket_init_apple_nw_socket(socket, alloc, options); default: AWS_ASSERT(false && "Invalid socket implementation on platform."); @@ -248,6 +261,7 @@ int aws_socket_init_winsock( } #endif +#ifndef AWS_ENABLE_DISPATCH_QUEUE int aws_socket_init_apple_nw_socket( struct aws_socket *socket, struct aws_allocator *alloc, @@ -258,3 +272,4 @@ int aws_socket_init_apple_nw_socket( AWS_LOGF_DEBUG(AWS_LS_IO_SOCKET, "Apple Network Framework is not supported on the platform."); return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); } +#endif diff --git a/source/socket_channel_handler.c b/source/socket_channel_handler.c index e8c9c5499..76d25bee1 100644 --- a/source/socket_channel_handler.c +++ b/source/socket_channel_handler.c @@ -291,6 +291,39 @@ static void s_close_task(struct aws_channel_task *task, void *arg, aws_task_stat socket_handler->slot, AWS_CHANNEL_DIR_WRITE, socket_handler->shutdown_err_code, false); } +struct channel_shutdown_close_args { + struct aws_channel_handler *handler; + int error_code; + struct aws_channel *channel; + struct aws_channel_slot *slot; + enum aws_channel_direction dir; + bool free_scarce_resource_immediately; + int test_flag; +}; + +static void s_shutdown_complete_fn(void *user_data) { + struct channel_shutdown_close_args *close_args = user_data; + + /* Schedule a task to complete the shutdown, in case a do_read task is currently pending. + * It's OK to delay the shutdown, even when free_scarce_resources_immediately is true, + * because the socket has been closed: mitigating the risk that the socket is still being abused by + * a hostile peer. */ + struct socket_handler *socket_handler = close_args->handler->impl; + aws_channel_task_init( + &socket_handler->shutdown_task_storage, s_close_task, close_args->handler, "socket_handler_close"); + socket_handler->shutdown_err_code = close_args->error_code; + aws_channel_schedule_task_now(close_args->channel, &socket_handler->shutdown_task_storage); + aws_mem_release(close_args->handler->alloc, close_args); +} + +static void s_shutdown_read_dir_complete_fn(void *user_data) { + struct channel_shutdown_close_args *close_args = user_data; + + aws_channel_slot_on_handler_shutdown_complete( + close_args->slot, close_args->dir, close_args->error_code, close_args->free_scarce_resource_immediately); + aws_mem_release(close_args->handler->alloc, close_args); +} + static int s_socket_shutdown( struct aws_channel_handler *handler, struct aws_channel_slot *slot, @@ -307,9 +340,21 @@ static int s_socket_shutdown( (void *)handler, error_code); if (free_scarce_resource_immediately && aws_socket_is_open(socket_handler->socket)) { + struct channel_shutdown_close_args *close_args = + aws_mem_calloc(handler->alloc, 1, sizeof(struct channel_shutdown_close_args)); + + close_args->error_code = error_code; + close_args->handler = handler; + close_args->channel = slot->channel; + close_args->slot = slot; + close_args->free_scarce_resource_immediately = free_scarce_resource_immediately; + close_args->dir = dir; + + aws_socket_set_close_complete_callback(socket_handler->socket, s_shutdown_read_dir_complete_fn, close_args); if (aws_socket_close(socket_handler->socket)) { return AWS_OP_ERR; } + return AWS_OP_SUCCESS; } return aws_channel_slot_on_handler_shutdown_complete(slot, dir, error_code, free_scarce_resource_immediately); @@ -321,16 +366,28 @@ static int s_socket_shutdown( (void *)handler, error_code); if (aws_socket_is_open(socket_handler->socket)) { + struct channel_shutdown_close_args *close_args = + aws_mem_calloc(handler->alloc, 1, sizeof(struct channel_shutdown_close_args)); + + close_args->error_code = error_code; + close_args->handler = handler; + close_args->channel = slot->channel; + close_args->slot = slot; + close_args->free_scarce_resource_immediately = free_scarce_resource_immediately; + close_args->dir = dir; + + aws_socket_set_close_complete_callback(socket_handler->socket, s_shutdown_complete_fn, close_args); aws_socket_close(socket_handler->socket); + } else { // If socket is already closed, fire the close task directly. + /* Schedule a task to complete the shutdown, in case a do_read task is currently pending. + * It's OK to delay the shutdown, even when free_scarce_resources_immediately is true, + * because the socket has been closed: mitigating the risk that the socket is still being abused by + * a hostile peer. */ + aws_channel_task_init(&socket_handler->shutdown_task_storage, s_close_task, handler, "socket_handler_close"); + socket_handler->shutdown_err_code = error_code; + aws_channel_schedule_task_now(slot->channel, &socket_handler->shutdown_task_storage); } - /* Schedule a task to complete the shutdown, in case a do_read task is currently pending. - * It's OK to delay the shutdown, even when free_scarce_resources_immediately is true, - * because the socket has been closed: mitigating the risk that the socket is still being abused by - * a hostile peer. */ - aws_channel_task_init(&socket_handler->shutdown_task_storage, s_close_task, handler, "socket_handler_close"); - socket_handler->shutdown_err_code = error_code; - aws_channel_schedule_task_now(slot->channel, &socket_handler->shutdown_task_storage); return AWS_OP_SUCCESS; } diff --git a/source/windows/iocp/socket.c b/source/windows/iocp/socket.c index d672719c8..1b2ec25f2 100644 --- a/source/windows/iocp/socket.c +++ b/source/windows/iocp/socket.c @@ -70,8 +70,7 @@ struct winsock_vtable { int (*start_accept)( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data); + struct aws_socket_listener_options options); int (*stop_accept)(struct aws_socket *socket); int (*bind)(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint); int (*listen)(struct aws_socket *socket, int backlog_size); @@ -116,19 +115,16 @@ static int s_local_connect( static int s_tcp_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data); + struct aws_socket_listener_options options); static int s_local_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data); + struct aws_socket_listener_options options); static int s_stream_stop_accept(struct aws_socket *socket); static int s_dgram_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data); + struct aws_socket_listener_options options); static int s_dgram_stop_accept(struct aws_socket *socket); static int s_tcp_listen(struct aws_socket *socket, int backlog_size); @@ -157,8 +153,7 @@ static int s_socket_listen(struct aws_socket *socket, int backlog_size); static int s_socket_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data); + struct aws_socket_listener_options options); static int s_socket_stop_accept(struct aws_socket *socket); static int s_socket_set_options(struct aws_socket *socket, const struct aws_socket_options *options); static int s_socket_close(struct aws_socket *socket); @@ -176,6 +171,8 @@ static int s_socket_write( void *user_data); static int s_socket_get_error(struct aws_socket *socket); static bool s_socket_is_open(struct aws_socket *socket); +static int s_set_close_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data); +static int s_set_cleanup_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data); static int s_stream_subscribe_to_read( struct aws_socket *socket, @@ -287,6 +284,8 @@ struct aws_socket_vtable s_winsock_vtable = { .socket_write_fn = s_socket_write, .socket_get_error_fn = s_socket_get_error, .socket_is_open_fn = s_socket_is_open, + .socket_set_close_callback = s_set_close_callback, + .socket_set_cleanup_callback = s_set_cleanup_callback, }; /* When socket is connected, any of the CONNECT_*** flags might be set. @@ -355,8 +354,26 @@ struct iocp_socket { struct socket_connect_args *connect_args; struct aws_linked_list pending_io_operations; bool stop_accept; + aws_socket_on_shutdown_complete_fn *on_close_complete; + void *close_user_data; + aws_socket_on_shutdown_complete_fn *on_cleanup_complete; + void *cleanup_user_data; }; +static int s_set_close_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data) { + struct iocp_socket *socket_impl = socket->impl; + socket_impl->close_user_data = user_data; + socket_impl->on_close_complete = fn; + return 0; +} + +static int s_set_cleanup_callback(struct aws_socket *socket, aws_socket_on_shutdown_complete_fn fn, void *user_data) { + struct iocp_socket *socket_impl = socket->impl; + socket_impl->cleanup_user_data = user_data; + socket_impl->on_cleanup_complete = fn; + return 0; +} + static int s_create_socket(struct aws_socket *sock, const struct aws_socket_options *options) { SOCKET handle = socket(s_convert_domain(options->domain), s_convert_type(options->type), 0); if (handle == INVALID_SOCKET) { @@ -480,9 +497,16 @@ static void s_socket_clean_up(struct aws_socket *socket) { aws_mem_release(socket->allocator, socket_impl->read_io_data); } + aws_socket_on_shutdown_complete_fn *on_cleanup_complete = socket_impl->on_cleanup_complete; + void *cleanup_user_data = socket_impl->cleanup_user_data; + aws_mem_release(socket->allocator, socket->impl); AWS_ZERO_STRUCT(*socket); socket->io_handle.data.handle = INVALID_HANDLE_VALUE; + + if (on_cleanup_complete) { + on_cleanup_complete(cleanup_user_data); + } } static int s_socket_connect( @@ -592,10 +616,9 @@ static int s_socket_listen(struct aws_socket *socket, int backlog_size) { static int s_socket_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data) { + struct aws_socket_listener_options options) { struct iocp_socket *socket_impl = socket->impl; - return socket_impl->winsock_vtable->start_accept(socket, accept_loop, on_accept_result, user_data); + return socket_impl->winsock_vtable->start_accept(socket, accept_loop, options); } static int s_socket_stop_accept(struct aws_socket *socket) { @@ -605,7 +628,11 @@ static int s_socket_stop_accept(struct aws_socket *socket) { static int s_socket_close(struct aws_socket *socket) { struct iocp_socket *socket_impl = socket->impl; - return socket_impl->winsock_vtable->close(socket); + int result = socket_impl->winsock_vtable->close(socket); + if (socket_impl->on_close_complete) { + socket_impl->on_close_complete(socket_impl->close_user_data); + } + return result; } static int s_socket_shutdown_dir(struct aws_socket *socket, enum aws_channel_direction dir) { @@ -2015,13 +2042,46 @@ static void s_tcp_accept_event( } } +static void s_process_invoke_on_accept_start(struct aws_task *task, void *args, enum aws_task_status status) { + (void)task; + struct on_start_accept_result_args *on_accept_args = args; + if (status == AWS_TASK_STATUS_RUN_READY) { + struct aws_socket *socket = on_accept_args->socket; + + if (on_accept_args->on_accept_start) { + // socket should not be cleaned up until on_accept_result callback is invoked. + AWS_FATAL_ASSERT(socket); + on_accept_args->on_accept_start(socket, on_accept_args->error, on_accept_args->on_accept_start_user_data); + } + } + aws_mem_release(on_accept_args->allocator, args); +} + +static void s_invoke_on_accept_start( + struct aws_allocator *allocator, + struct aws_event_loop *loop, + struct aws_socket *socket, + int error, + aws_socket_on_accept_started_fn *on_accept_start, + void *on_accept_start_user_data) { + struct on_start_accept_result_args *args = aws_mem_calloc(allocator, 1, sizeof(struct on_start_accept_result_args)); + + args->allocator = allocator; + args->socket = socket; + args->error = error; + args->on_accept_start = on_accept_start; + args->on_accept_start_user_data = on_accept_start_user_data; + + aws_task_init(&args->task, s_process_invoke_on_accept_start, args, "SocketOnAcceptStartResultTask"); + aws_event_loop_schedule_task_now(loop, &args->task); +} + static int s_tcp_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data) { + struct aws_socket_listener_options options) { AWS_ASSERT(accept_loop); - AWS_ASSERT(on_accept_result); + AWS_ASSERT(options.on_accept_result); if (AWS_UNLIKELY(socket->state != LISTENING)) { AWS_LOGF_ERROR( @@ -2055,14 +2115,21 @@ static int s_tcp_start_accept( socket_impl->read_io_data->socket = socket; } - socket->accept_result_fn = on_accept_result; - socket->connect_accept_user_data = user_data; + socket->accept_result_fn = options.on_accept_result; + socket->connect_accept_user_data = options.on_accept_result_user_data; socket_impl->stop_accept = false; struct aws_event_loop *el_to_use = !socket->event_loop ? accept_loop : NULL; int err = s_socket_setup_accept(socket, el_to_use); if (!err || aws_last_error() == AWS_IO_READ_WOULD_BLOCK) { + s_invoke_on_accept_start( + socket->allocator, + accept_loop, + socket, + AWS_OP_SUCCESS, + options.on_accept_start, + options.on_accept_start_user_data); return AWS_OP_SUCCESS; } @@ -2179,10 +2246,9 @@ static void s_named_pipe_is_ridiculous_task(struct aws_task *task, void *args, e static int s_local_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data) { + struct aws_socket_listener_options options) { AWS_ASSERT(accept_loop); - AWS_ASSERT(on_accept_result); + AWS_ASSERT(options.on_accept_result); if (AWS_UNLIKELY(socket->state != LISTENING)) { AWS_LOGF_ERROR( @@ -2221,8 +2287,8 @@ static int s_local_start_accept( socket_impl->read_io_data->socket = socket; } - socket->accept_result_fn = on_accept_result; - socket->connect_accept_user_data = user_data; + socket->accept_result_fn = options.on_accept_result; + socket->connect_accept_user_data = options.on_accept_result_user_data; socket_impl->stop_accept = false; aws_overlapped_init(&socket_impl->read_io_data->signal, s_incoming_pipe_connection_event, socket); socket_impl->read_io_data->in_use = true; @@ -2264,18 +2330,24 @@ static int s_local_start_accept( } } + s_invoke_on_accept_start( + socket->allocator, + accept_loop, + socket, + AWS_OP_SUCCESS, + options.on_accept_start, + options.on_accept_start_user_data); + return AWS_OP_SUCCESS; } static int s_dgram_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, - aws_socket_on_accept_result_fn *on_accept_result, - void *user_data) { + struct aws_socket_listener_options options) { (void)socket; (void)accept_loop; - (void)on_accept_result; - (void)user_data; + (void)options; return aws_raise_error(AWS_IO_SOCKET_ILLEGAL_OPERATION_FOR_STATE); } @@ -2380,7 +2452,8 @@ static int s_socket_set_options(struct aws_socket *socket, const struct aws_sock if (aws_secure_strlen(options->network_interface_name, AWS_NETWORK_INTERFACE_NAME_MAX, &network_interface_length)) { AWS_LOGF_ERROR( AWS_LS_IO_SOCKET, - "id=%p fd=%d: network_interface_name max length must be %d length and NULL terminated", + "id=%p fd=%d: network_interface_name max length must be less or equal than %d bytes including NULL " + "terminated", (void *)socket, socket->io_handle.data.fd, AWS_NETWORK_INTERFACE_NAME_MAX); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0619703de..13f7a8c79 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -70,7 +70,6 @@ add_net_test_case(test_socket_with_bind_to_interface) add_net_test_case(test_socket_with_bind_to_invalid_interface) add_net_test_case(test_is_network_interface_name_valid) add_net_test_case(connect_timeout) -add_net_test_case(connect_timeout_cancelation) if(USE_VSOCK) @@ -83,7 +82,7 @@ add_test_case(incoming_tcp_sock_errors) add_net_test_case(bind_on_zero_port_tcp_ipv4) add_net_test_case(bind_on_zero_port_udp_ipv4) add_test_case(incoming_udp_sock_errors) -add_net_test_case(cleanup_before_connect_or_timeout_doesnt_explode) + add_test_case(cleanup_in_accept_doesnt_explode) add_test_case(cleanup_in_write_cb_doesnt_explode) add_test_case(sock_write_cb_is_async) @@ -97,6 +96,10 @@ add_test_case(wrong_thread_read_write_fails) # Apple Network Framework would not validate the binding endpoint until we start the # listen. The test does not apply here. add_test_case(incoming_duplicate_tcp_bind_errors) +# nw_socket does not allow clean up event loop before socket shutdown, thus the following tests triggered +# by event loop shutdown would not apply to Apple Network Framework +add_net_test_case(connect_timeout_cancelation) +add_net_test_case(cleanup_before_connect_or_timeout_doesnt_explode) endif() @@ -143,6 +146,7 @@ add_test_case(pem_sanitize_comments_around_pem_object_removed) add_test_case(pem_sanitize_empty_file_rejected) add_test_case(pem_sanitize_wrong_format_rejected) +add_test_case(socket_data_over_multiple_frames) add_test_case(socket_handler_echo_and_backpressure) add_test_case(socket_handler_close) # These tests fail on Windows due to some bug in our server code where, if the socket is closed diff --git a/tests/event_loop_test.c b/tests/event_loop_test.c index 43dbc0da3..3d20676ac 100644 --- a/tests/event_loop_test.c +++ b/tests/event_loop_test.c @@ -58,7 +58,7 @@ static void s_dispatch_queue_sleep(void) { * to run to clean up memory allocated to the paired scheduled iteration entry. We wait for two seconds to allow the * Apple dispatch queue to run its delayed blocks and clean up for memory release purposes. */ -#if defined(AWS_USE_APPLE_DISPATCH_QUEUE) +#if defined(AWS_USE_APPLE_DISPATCH_QUEUE) || defined(AWS_USE_APPLE_NETWORK_FRAMEWORK) aws_thread_current_sleep(2000000000); #endif } diff --git a/tests/read_write_test_handler.c b/tests/read_write_test_handler.c index 959158c96..9f249754f 100644 --- a/tests/read_write_test_handler.c +++ b/tests/read_write_test_handler.c @@ -199,17 +199,26 @@ static void s_rw_handler_write_now( struct aws_byte_buf *buffer, aws_channel_on_message_write_completed_fn *on_completion, void *user_data) { + size_t remaining = buffer->len; + struct aws_byte_cursor write_cursor = aws_byte_cursor_from_buf(buffer); - struct aws_io_message *msg = - aws_channel_acquire_message_from_pool(slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, buffer->len); + while (remaining > 0) { + size_t chunk_size = remaining; + struct aws_io_message *msg = + aws_channel_acquire_message_from_pool(slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, chunk_size); + + chunk_size = aws_min_size(chunk_size, msg->message_data.capacity - msg->message_data.len); - msg->on_completion = on_completion; - msg->user_data = user_data; + msg->on_completion = on_completion; + msg->user_data = user_data; - struct aws_byte_cursor write_buffer = aws_byte_cursor_from_buf(buffer); - AWS_FATAL_ASSERT(aws_byte_buf_append(&msg->message_data, &write_buffer) == AWS_OP_SUCCESS); + struct aws_byte_cursor chunk_cursor = aws_byte_cursor_advance(&write_cursor, chunk_size); + AWS_FATAL_ASSERT(aws_byte_buf_append(&msg->message_data, &chunk_cursor) == AWS_OP_SUCCESS); - AWS_FATAL_ASSERT(aws_channel_slot_send_message(slot, msg, AWS_CHANNEL_DIR_WRITE) == AWS_OP_SUCCESS); + AWS_FATAL_ASSERT(aws_channel_slot_send_message(slot, msg, AWS_CHANNEL_DIR_WRITE) == AWS_OP_SUCCESS); + + remaining -= chunk_size; + } } static void s_rw_handler_write_task(struct aws_channel_task *task, void *arg, enum aws_task_status task_status) { diff --git a/tests/socket_handler_test.c b/tests/socket_handler_test.c index 1f301bfee..be125ec7b 100644 --- a/tests/socket_handler_test.c +++ b/tests/socket_handler_test.c @@ -18,6 +18,8 @@ #include "statistics_handler_test.h" #include +#include "../include/aws/io/private/socket_impl.h" + #ifdef _MSC_VER # pragma warning(disable : 4996) /* allow strncpy() */ #endif @@ -38,6 +40,7 @@ struct socket_test_args { bool error_invoked; bool creation_callback_invoked; bool listener_destroyed; + bool listener_connected; }; /* common structure for test */ @@ -122,6 +125,12 @@ static bool s_listener_destroy_predicate(void *user_data) { return finished; } +static bool s_listener_connected_predicate(void *user_data) { + struct socket_test_args *setup_test_args = (struct socket_test_args *)user_data; + bool finished = setup_test_args->listener_connected; + return finished; +} + static void s_socket_handler_test_client_setup_callback( struct aws_client_bootstrap *bootstrap, int error_code, @@ -299,6 +308,21 @@ static void s_socket_handler_test_server_listener_destroy_callback( aws_condition_variable_notify_one(setup_test_args->condition_variable); } +static void s_socket_handler_test_server_listener_setup_callback( + struct aws_server_bootstrap *bootstrap, + int error_code, + void *user_data) { + + (void)bootstrap; + + struct socket_test_args *setup_test_args = (struct socket_test_args *)user_data; + aws_mutex_lock(setup_test_args->mutex); + setup_test_args->listener_connected = true; + setup_test_args->error_code = error_code; + aws_condition_variable_notify_one(setup_test_args->condition_variable); + aws_mutex_unlock(setup_test_args->mutex); +} + static int s_rw_args_init( struct socket_test_rw_args *args, struct socket_common_tester *s_c_tester, @@ -362,10 +386,20 @@ static int s_local_server_tester_init( .incoming_callback = s_socket_handler_test_server_setup_callback, .shutdown_callback = s_socket_handler_test_server_shutdown_callback, .destroy_callback = s_socket_handler_test_server_listener_destroy_callback, + .setup_callback = s_socket_handler_test_server_listener_setup_callback, .user_data = args, }; + tester->listener = aws_server_bootstrap_new_socket_listener(&bootstrap_options); ASSERT_NOT_NULL(tester->listener); + // if server setup properly, waiting for setup callback + + ASSERT_SUCCESS(aws_mutex_lock(args->mutex)); + /* wait for listener to connected */ + ASSERT_SUCCESS( + aws_condition_variable_wait_pred(args->condition_variable, args->mutex, s_listener_connected_predicate, args)); + ASSERT_TRUE(args->error_code == AWS_OP_SUCCESS); + ASSERT_SUCCESS(aws_mutex_unlock(args->mutex)); /* find out which port the socket is bound to */ ASSERT_SUCCESS(aws_socket_get_bound_address(tester->listener, &tester->endpoint)); @@ -694,11 +728,136 @@ static int s_socket_echo_and_backpressure_test(struct aws_allocator *allocator, aws_client_bootstrap_release(client_bootstrap); ASSERT_SUCCESS(s_socket_common_tester_clean_up(&c_tester)); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(socket_handler_echo_and_backpressure, s_socket_echo_and_backpressure_test) +static int s_socket_data_over_multiple_frames_test(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + s_socket_common_tester_init(allocator, &c_tester); + + // Create a large message that will be split over multiple frames + const size_t total_bytes_to_send_from_server = g_aws_channel_max_fragment_size * 1024; + struct aws_byte_buf msg_from_server; + ASSERT_SUCCESS(aws_byte_buf_init(&msg_from_server, allocator, total_bytes_to_send_from_server)); + + srand(0); + // Fill the buffer with random printable ASCII characters + for (size_t i = 0; i < total_bytes_to_send_from_server; ++i) { + char random_char = 32 + (rand() % 95); // Printable ASCII characters range from 32 to 126 + ASSERT_TRUE(aws_byte_buf_write_u8(&msg_from_server, random_char)); + } + + struct aws_byte_buf client_received_message; + ASSERT_SUCCESS(aws_byte_buf_init(&client_received_message, allocator, total_bytes_to_send_from_server)); + + struct socket_test_rw_args server_rw_args; + ASSERT_SUCCESS(s_rw_args_init(&server_rw_args, &c_tester, aws_byte_buf_from_empty_array(NULL, 0), 0)); + + struct socket_test_rw_args client_rw_args; + ASSERT_SUCCESS(s_rw_args_init(&client_rw_args, &c_tester, client_received_message, 0)); + + struct aws_channel_handler *client_rw_handler = + rw_handler_new(allocator, s_socket_test_handle_read, s_socket_test_handle_write, true, 10000, &client_rw_args); + ASSERT_NOT_NULL(client_rw_handler); + + struct aws_channel_handler *server_rw_handler = + rw_handler_new(allocator, s_socket_test_handle_read, s_socket_test_handle_write, true, 10000, &server_rw_args); + ASSERT_NOT_NULL(server_rw_handler); + + struct socket_test_args server_args; + ASSERT_SUCCESS(s_socket_test_args_init(&server_args, &c_tester, server_rw_handler)); + + struct socket_test_args client_args; + ASSERT_SUCCESS(s_socket_test_args_init(&client_args, &c_tester, client_rw_handler)); + + struct local_server_tester local_server_tester; + ASSERT_SUCCESS( + s_local_server_tester_init(allocator, &local_server_tester, &server_args, &c_tester, AWS_SOCKET_LOCAL, true)); + + struct aws_client_bootstrap_options client_bootstrap_options = { + .event_loop_group = c_tester.el_group, + .host_resolver = c_tester.resolver, + }; + struct aws_client_bootstrap *client_bootstrap = aws_client_bootstrap_new(allocator, &client_bootstrap_options); + ASSERT_NOT_NULL(client_bootstrap); + + struct aws_socket_channel_bootstrap_options client_channel_options; + AWS_ZERO_STRUCT(client_channel_options); + client_channel_options.bootstrap = client_bootstrap; + client_channel_options.host_name = local_server_tester.endpoint.address; + client_channel_options.port = local_server_tester.endpoint.port; + client_channel_options.socket_options = &local_server_tester.socket_options; + client_channel_options.setup_callback = s_socket_handler_test_client_setup_callback; + client_channel_options.shutdown_callback = s_socket_handler_test_client_shutdown_callback; + client_channel_options.user_data = &client_args; + client_channel_options.enable_read_back_pressure = true; + + ASSERT_SUCCESS(aws_client_bootstrap_new_socket_channel(&client_channel_options)); + + ASSERT_SUCCESS(aws_mutex_lock(&c_tester.mutex)); + + /* wait for both ends to setup */ + ASSERT_SUCCESS(aws_condition_variable_wait_for_pred( + &c_tester.condition_variable, &c_tester.mutex, TIMEOUT, s_channel_setup_predicate, &server_args)); + ASSERT_SUCCESS(aws_condition_variable_wait_for_pred( + &c_tester.condition_variable, &c_tester.mutex, TIMEOUT, s_channel_setup_predicate, &client_args)); + + /* send msg from server to client, and wait for some bytes to be received */ + rw_handler_write(server_args.rw_handler, server_args.rw_slot, &msg_from_server); + ASSERT_SUCCESS(aws_condition_variable_wait_for_pred( + &c_tester.condition_variable, &c_tester.mutex, TIMEOUT, s_socket_test_read_predicate, &client_rw_args)); + + /* confirm that the initial read window was respected */ + ASSERT_SUCCESS(client_rw_args.amount_read == 1000); + + client_rw_args.invocation_happened = false; + client_rw_args.expected_read = total_bytes_to_send_from_server; + + /* increment the read window on the client side and confirm it receives the remainder of the message */ + rw_handler_trigger_increment_read_window( + client_args.rw_handler, client_args.rw_slot, total_bytes_to_send_from_server); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &c_tester.condition_variable, &c_tester.mutex, s_socket_test_full_read_predicate, &client_rw_args)); + + ASSERT_INT_EQUALS(total_bytes_to_send_from_server, client_rw_args.amount_read); + + ASSERT_BIN_ARRAYS_EQUALS( + msg_from_server.buffer, + msg_from_server.len, + client_rw_args.received_message.buffer, + client_rw_args.received_message.len); + + /* shut down both sides */ + ASSERT_SUCCESS(aws_channel_shutdown(server_args.channel, AWS_OP_SUCCESS)); + ASSERT_SUCCESS(aws_channel_shutdown(client_args.channel, AWS_OP_SUCCESS)); + + ASSERT_SUCCESS(aws_condition_variable_wait_for_pred( + &c_tester.condition_variable, &c_tester.mutex, TIMEOUT, s_channel_shutdown_predicate, &server_args)); + ASSERT_SUCCESS(aws_condition_variable_wait_for_pred( + &c_tester.condition_variable, &c_tester.mutex, TIMEOUT, s_channel_shutdown_predicate, &client_args)); + aws_server_bootstrap_destroy_socket_listener(local_server_tester.server_bootstrap, local_server_tester.listener); + ASSERT_SUCCESS(aws_condition_variable_wait_for_pred( + &c_tester.condition_variable, &c_tester.mutex, TIMEOUT, s_listener_destroy_predicate, &server_args)); + + aws_mutex_unlock(&c_tester.mutex); + + /* clean up */ + ASSERT_SUCCESS(s_local_server_tester_clean_up(&local_server_tester)); + + aws_client_bootstrap_release(client_bootstrap); + ASSERT_SUCCESS(s_socket_common_tester_clean_up(&c_tester)); + aws_byte_buf_clean_up(&msg_from_server); + aws_byte_buf_clean_up(&client_received_message); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(socket_data_over_multiple_frames, s_socket_data_over_multiple_frames_test) + static int s_socket_close_test(struct aws_allocator *allocator, void *ctx) { (void)ctx; diff --git a/tests/socket_test.c b/tests/socket_test.c index e6dcdfdd7..50175396d 100644 --- a/tests/socket_test.c +++ b/tests/socket_test.c @@ -29,8 +29,24 @@ struct local_listener_args { struct aws_condition_variable *condition_variable; bool incoming_invoked; bool error_invoked; + bool shutdown_complete; }; +static void s_local_listener_shutdown_complete(void *user_data) { + struct local_listener_args *listener_args = (struct local_listener_args *)user_data; + + aws_mutex_lock(listener_args->mutex); + listener_args->shutdown_complete = true; + aws_mutex_unlock(listener_args->mutex); + aws_condition_variable_notify_one(listener_args->condition_variable); +} + +static bool s_local_listener_shutdown_completed_predicate(void *arg) { + struct local_listener_args *listener_args = arg; + + return listener_args->shutdown_complete; +} + static bool s_incoming_predicate(void *arg) { struct local_listener_args *listener_args = (struct local_listener_args *)arg; @@ -60,6 +76,7 @@ struct local_outgoing_args { bool connect_invoked; bool error_invoked; int last_error; + bool shutdown_complete; struct aws_mutex *mutex; struct aws_condition_variable *condition_variable; }; @@ -88,6 +105,21 @@ static void s_local_outgoing_connection(struct aws_socket *socket, int error_cod aws_condition_variable_notify_one(outgoing_args->condition_variable); } +static void s_local_outgoing_connection_shutdown_complete(void *user_data) { + struct local_outgoing_args *outgoing_args = (struct local_outgoing_args *)user_data; + + aws_mutex_lock(outgoing_args->mutex); + outgoing_args->shutdown_complete = true; + aws_mutex_unlock(outgoing_args->mutex); + aws_condition_variable_notify_one(outgoing_args->condition_variable); +} + +static bool s_outgoing_shutdown_completed_predicate(void *arg) { + struct local_outgoing_args *io_args = arg; + + return io_args->shutdown_complete; +} + struct socket_io_args { struct aws_socket *socket; struct aws_byte_cursor *to_write; @@ -97,18 +129,25 @@ struct socket_io_args { size_t amount_read; int error_code; bool close_completed; + bool shutdown_complete; struct aws_mutex *mutex; struct aws_condition_variable condition_variable; }; +static bool s_shutdown_completed_predicate(void *arg) { + struct socket_io_args *io_args = arg; + + return io_args->shutdown_complete; +} + static void s_on_written(struct aws_socket *socket, int error_code, size_t amount_written, void *user_data) { (void)socket; struct socket_io_args *write_args = user_data; aws_mutex_lock(write_args->mutex); write_args->error_code = error_code; write_args->amount_written = amount_written; - aws_mutex_unlock(write_args->mutex); aws_condition_variable_notify_one(&write_args->condition_variable); + aws_mutex_unlock(write_args->mutex); } static bool s_write_completed_predicate(void *arg) { @@ -130,17 +169,25 @@ static void s_read_task(struct aws_task *task, void *args, enum aws_task_status (void)status; struct socket_io_args *io_args = args; + aws_mutex_lock(io_args->mutex); size_t read = 0; + while (read < io_args->to_read->len) { size_t data_len = 0; + if (aws_socket_read(io_args->socket, io_args->read_data, &data_len)) { if (AWS_IO_READ_WOULD_BLOCK == aws_last_error()) { - continue; + /* we can't just loop here, since the socket may rely on the event-loop for actually getting + * the data, so schedule a task to force a context switch and give the socket a chance to catch up. */ + aws_mutex_unlock(io_args->mutex); + aws_event_loop_schedule_task_now(io_args->socket->event_loop, task); + return; } break; } + read += data_len; } io_args->amount_read = read; @@ -178,6 +225,35 @@ static void s_socket_close_task(struct aws_task *task, void *args, enum aws_task aws_condition_variable_notify_one(&io_args->condition_variable); } +static void s_socket_shutdown_complete_fn(void *user_data) { + struct socket_io_args *close_args = user_data; + aws_mutex_lock(close_args->mutex); + close_args->shutdown_complete = true; + aws_mutex_unlock(close_args->mutex); + aws_condition_variable_notify_one(&close_args->condition_variable); +} +struct error_test_args { + int error_code; + struct aws_mutex mutex; + struct aws_condition_variable condition_variable; + bool shutdown_invoked; +}; + +static bool s_socket_error_shutdown_predicate(void *args) { + struct error_test_args *test_args = (struct error_test_args *)args; + + return test_args->shutdown_invoked; +} + +static void s_socket_error_shutdown_complete(void *user_data) { + struct error_test_args *test_args = (struct error_test_args *)user_data; + + aws_mutex_lock(&test_args->mutex); + test_args->shutdown_invoked = true; + aws_mutex_unlock(&test_args->mutex); + aws_condition_variable_notify_one(&test_args->condition_variable); +} + /* we have tests that need to check the error handling path, but it's damn near impossible to predictably make sockets fail, the best idea we have is to do something the OS won't allow for the access permissions (like attempt to listen @@ -201,8 +277,23 @@ static bool s_test_running_as_root(struct aws_allocator *alloc) { err = aws_socket_bind(&socket, &endpoint); err |= aws_socket_listen(&socket, 1024); + + struct error_test_args args = { + .error_code = 0, + .mutex = AWS_MUTEX_INIT, + .condition_variable = AWS_CONDITION_VARIABLE_INIT, + .shutdown_invoked = false, + }; + + aws_socket_set_cleanup_complete_callback(&socket, s_socket_error_shutdown_complete, &args); + bool is_root = !err; aws_socket_clean_up(&socket); + ASSERT_SUCCESS(aws_mutex_lock(&args.mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &args.condition_variable, &args.mutex, s_socket_error_shutdown_predicate, &args)); + ASSERT_SUCCESS(aws_mutex_unlock(&args.mutex)); + return is_root; } @@ -211,10 +302,16 @@ static int s_test_socket_ex( struct aws_socket_options *options, struct aws_socket_endpoint *local, struct aws_socket_endpoint *endpoint) { - struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + + aws_io_library_init(allocator); + + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); - ASSERT_SUCCESS(aws_event_loop_run(event_loop)); struct aws_mutex mutex = AWS_MUTEX_INIT; struct aws_condition_variable condition_variable = AWS_CONDITION_VARIABLE_INIT; @@ -227,6 +324,30 @@ static int s_test_socket_ex( .error_invoked = false, }; + /* now test the read and write across the connection. */ + const char read_data[] = "I'm a little teapot"; + char write_data[sizeof(read_data)] = {0}; + + struct aws_byte_buf read_buffer = aws_byte_buf_from_array((const uint8_t *)read_data, sizeof(read_data)); + struct aws_byte_buf write_buffer = aws_byte_buf_from_array((const uint8_t *)write_data, sizeof(write_data)); + write_buffer.len = 0; + + struct aws_byte_cursor read_cursor = aws_byte_cursor_from_buf(&read_buffer); + + struct socket_io_args io_args = { + .socket = NULL, + .to_write = &read_cursor, + .to_read = &read_buffer, + .read_data = &write_buffer, + .mutex = &mutex, + .amount_read = 0, + .amount_written = 0, + .error_code = 0, + .condition_variable = AWS_CONDITION_VARIABLE_INIT, + .close_completed = false, + .shutdown_complete = false, + }; + struct aws_socket listener; ASSERT_SUCCESS(aws_socket_init(&listener, allocator, options)); @@ -237,22 +358,29 @@ static int s_test_socket_ex( ASSERT_INT_EQUALS(endpoint->port, bound_endpoint.port); ASSERT_STR_EQUALS(endpoint->address, bound_endpoint.address); - if (options->type == AWS_SOCKET_STREAM) { + // The Apple Network Framework always require a "start listener/start connection" + // for setup a server socket + if (options->type == AWS_SOCKET_STREAM || + aws_socket_get_default_impl_type() == AWS_SOCKET_IMPL_APPLE_NETWORK_FRAMEWORK) { ASSERT_SUCCESS(aws_socket_listen(&listener, 1024)); - ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, s_local_listener_incoming, &listener_args)); + struct aws_socket_listener_options listener_options = { + .on_accept_result = s_local_listener_incoming, .on_accept_result_user_data = &listener_args}; + ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, listener_options)); } struct local_outgoing_args outgoing_args = { .mutex = &mutex, .condition_variable = &condition_variable, .connect_invoked = false, .error_invoked = false}; struct aws_socket outgoing; + ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, options)); if (local && (strcmp(local->address, endpoint->address) != 0 || local->port != endpoint->port)) { ASSERT_SUCCESS(aws_socket_bind(&outgoing, local)); } ASSERT_SUCCESS(aws_socket_connect(&outgoing, endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); - if (listener.options.type == AWS_SOCKET_STREAM) { + if (listener.options.type == AWS_SOCKET_STREAM || + aws_socket_get_default_impl_type() == AWS_SOCKET_IMPL_APPLE_NETWORK_FRAMEWORK) { ASSERT_SUCCESS(aws_mutex_lock(&mutex)); ASSERT_SUCCESS( aws_condition_variable_wait_pred(&condition_variable, &mutex, s_incoming_predicate, &listener_args)); @@ -265,7 +393,8 @@ static int s_test_socket_ex( struct aws_socket *server_sock = &listener; - if (options->type == AWS_SOCKET_STREAM) { + if (options->type == AWS_SOCKET_STREAM || + aws_socket_get_default_impl_type() == AWS_SOCKET_IMPL_APPLE_NETWORK_FRAMEWORK) { ASSERT_TRUE(listener_args.incoming_invoked); ASSERT_FALSE(listener_args.error_invoked); server_sock = listener_args.incoming; @@ -276,31 +405,10 @@ static int s_test_socket_ex( } ASSERT_SUCCESS(aws_socket_assign_to_event_loop(server_sock, event_loop)); - aws_socket_subscribe_to_readable_events(server_sock, s_on_readable, NULL); - aws_socket_subscribe_to_readable_events(&outgoing, s_on_readable, NULL); + ASSERT_SUCCESS(aws_socket_subscribe_to_readable_events(server_sock, s_on_readable, NULL)); + ASSERT_SUCCESS(aws_socket_subscribe_to_readable_events(&outgoing, s_on_readable, NULL)); - /* now test the read and write across the connection. */ - const char read_data[] = "I'm a little teapot"; - char write_data[sizeof(read_data)] = {0}; - - struct aws_byte_buf read_buffer = aws_byte_buf_from_array((const uint8_t *)read_data, sizeof(read_data)); - struct aws_byte_buf write_buffer = aws_byte_buf_from_array((const uint8_t *)write_data, sizeof(write_data)); - write_buffer.len = 0; - - struct aws_byte_cursor read_cursor = aws_byte_cursor_from_buf(&read_buffer); - - struct socket_io_args io_args = { - .socket = &outgoing, - .to_write = &read_cursor, - .to_read = &read_buffer, - .read_data = &write_buffer, - .mutex = &mutex, - .amount_read = 0, - .amount_written = 0, - .error_code = 0, - .condition_variable = AWS_CONDITION_VARIABLE_INIT, - .close_completed = false, - }; + io_args.socket = &outgoing; struct aws_task write_task = { .fn = s_write_task, @@ -357,34 +465,222 @@ static int s_test_socket_ex( if (listener_args.incoming) { io_args.socket = listener_args.incoming; io_args.close_completed = false; + io_args.shutdown_complete = false; + aws_socket_set_cleanup_complete_callback(listener_args.incoming, s_socket_shutdown_complete_fn, &io_args); + aws_event_loop_schedule_task_now(event_loop, &close_task); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_close_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + + aws_socket_clean_up(listener_args.incoming); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_shutdown_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + aws_mem_release(allocator, listener_args.incoming); + } + + io_args.socket = &outgoing; + io_args.close_completed = false; + io_args.shutdown_complete = false; + aws_socket_set_cleanup_complete_callback(&outgoing, s_socket_shutdown_complete_fn, &io_args); + aws_event_loop_schedule_task_now(event_loop, &close_task); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_close_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + + aws_socket_clean_up(&outgoing); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_shutdown_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + + io_args.socket = &listener; + io_args.close_completed = false; + io_args.shutdown_complete = false; + aws_socket_set_cleanup_complete_callback(&listener, s_socket_shutdown_complete_fn, &io_args); + aws_event_loop_schedule_task_now(event_loop, &close_task); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_close_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + + aws_socket_clean_up(&listener); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_shutdown_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); + + return 0; +} + +static int s_test_socket_udp_apple_network_framework( + struct aws_allocator *allocator, + struct aws_socket_options *options, + struct aws_socket_endpoint *endpoint) { + + aws_io_library_init(allocator); + + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); + + ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); + + struct aws_mutex mutex = AWS_MUTEX_INIT; + struct aws_condition_variable condition_variable = AWS_CONDITION_VARIABLE_INIT; + + struct local_listener_args listener_args = { + .mutex = &mutex, + .condition_variable = &condition_variable, + .incoming = NULL, + .incoming_invoked = false, + .error_invoked = false, + }; + + struct aws_socket listener; + ASSERT_SUCCESS(aws_socket_init(&listener, allocator, options)); + + ASSERT_SUCCESS(aws_socket_bind(&listener, endpoint)); + + struct aws_socket_endpoint bound_endpoint; + ASSERT_SUCCESS(aws_socket_get_bound_address(&listener, &bound_endpoint)); + ASSERT_INT_EQUALS(endpoint->port, bound_endpoint.port); + ASSERT_STR_EQUALS(endpoint->address, bound_endpoint.address); + + ASSERT_SUCCESS(aws_socket_listen(&listener, 1024)); + struct aws_socket_listener_options listener_options = { + .on_accept_result = s_local_listener_incoming, .on_accept_result_user_data = &listener_args}; + ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, listener_options)); + + struct local_outgoing_args outgoing_args = { + .mutex = &mutex, .condition_variable = &condition_variable, .connect_invoked = false, .error_invoked = false}; + + struct aws_socket outgoing; + ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, options)); + ASSERT_SUCCESS(aws_socket_connect(&outgoing, endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); + + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &condition_variable, &mutex, s_connection_completed_predicate, &outgoing_args)); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + + ASSERT_SUCCESS(aws_socket_subscribe_to_readable_events(&outgoing, s_on_readable, NULL)); + + /* now test the read and write across the connection. */ + const char read_data[] = "I'm a little teapot"; + char write_data[sizeof(read_data)] = {0}; + + struct aws_byte_buf read_buffer = aws_byte_buf_from_array((const uint8_t *)read_data, sizeof(read_data)); + struct aws_byte_buf write_buffer = aws_byte_buf_from_array((const uint8_t *)write_data, sizeof(write_data)); + write_buffer.len = 0; + + struct aws_byte_cursor read_cursor = aws_byte_cursor_from_buf(&read_buffer); + + struct socket_io_args io_args = { + .socket = &outgoing, + .to_write = &read_cursor, + .to_read = &read_buffer, + .read_data = &write_buffer, + .mutex = &mutex, + .amount_read = 0, + .amount_written = 0, + .error_code = 0, + .condition_variable = AWS_CONDITION_VARIABLE_INIT, + .close_completed = false, + }; + + struct aws_task write_task = { + .fn = s_write_task, + .arg = &io_args, + }; + + aws_event_loop_schedule_task_now(event_loop, &write_task); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_write_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + ASSERT_INT_EQUALS(AWS_OP_SUCCESS, io_args.error_code); + + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred(&condition_variable, &mutex, s_incoming_predicate, &listener_args)); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + + ASSERT_TRUE(listener_args.incoming_invoked); + ASSERT_FALSE(listener_args.error_invoked); + struct aws_socket *server_sock = listener_args.incoming; + ASSERT_TRUE(outgoing_args.connect_invoked); + ASSERT_FALSE(outgoing_args.error_invoked); + ASSERT_INT_EQUALS(options->domain, listener_args.incoming->options.domain); + ASSERT_INT_EQUALS(options->type, listener_args.incoming->options.type); + ASSERT_SUCCESS(aws_socket_assign_to_event_loop(server_sock, event_loop)); + + aws_socket_subscribe_to_readable_events(server_sock, s_on_readable, NULL); + + io_args.socket = server_sock; + struct aws_task read_task = { + .fn = s_read_task, + .arg = &io_args, + }; + + aws_event_loop_schedule_task_now(event_loop, &read_task); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_read_task_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + ASSERT_INT_EQUALS(AWS_OP_SUCCESS, io_args.error_code); + ASSERT_BIN_ARRAYS_EQUALS(read_buffer.buffer, read_buffer.len, write_buffer.buffer, write_buffer.len); + + struct aws_task close_task = { + .fn = s_socket_close_task, + .arg = &io_args, + }; + + if (listener_args.incoming) { + io_args.socket = listener_args.incoming; + io_args.close_completed = false; + io_args.shutdown_complete = false; + aws_socket_set_cleanup_complete_callback(listener_args.incoming, s_socket_shutdown_complete_fn, &io_args); aws_event_loop_schedule_task_now(event_loop, &close_task); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); - aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_close_completed_predicate, &io_args); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_close_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + + aws_socket_clean_up(listener_args.incoming); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_shutdown_completed_predicate, &io_args); ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); - aws_socket_clean_up(listener_args.incoming); aws_mem_release(allocator, listener_args.incoming); } io_args.socket = &outgoing; io_args.close_completed = false; + io_args.shutdown_complete = false; + aws_socket_set_cleanup_complete_callback(&outgoing, s_socket_shutdown_complete_fn, &io_args); aws_event_loop_schedule_task_now(event_loop, &close_task); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_close_completed_predicate, &io_args); ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); aws_socket_clean_up(&outgoing); - + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_shutdown_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); io_args.socket = &listener; io_args.close_completed = false; + io_args.shutdown_complete = false; + aws_socket_set_cleanup_complete_callback(&listener, s_socket_shutdown_complete_fn, &io_args); aws_event_loop_schedule_task_now(event_loop, &close_task); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_close_completed_predicate, &io_args); ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); aws_socket_clean_up(&listener); - - aws_event_loop_destroy(event_loop); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_shutdown_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); return 0; } @@ -442,7 +738,11 @@ static int s_test_socket( struct aws_socket_options *options, struct aws_socket_endpoint *endpoint) { - return s_test_socket_ex(allocator, options, NULL, endpoint); + if (aws_socket_get_default_impl_type() == AWS_SOCKET_IMPL_APPLE_NETWORK_FRAMEWORK && + options->type == AWS_SOCKET_DGRAM) + return s_test_socket_udp_apple_network_framework(allocator, options, endpoint); + else + return s_test_socket_ex(allocator, options, NULL, endpoint); } static int s_test_local_socket_communication(struct aws_allocator *allocator, void *ctx) { @@ -453,6 +753,9 @@ static int s_test_local_socket_communication(struct aws_allocator *allocator, vo options.connect_timeout_ms = 3000; options.type = AWS_SOCKET_STREAM; options.domain = AWS_SOCKET_LOCAL; + + uint64_t timestamp = 0; + ASSERT_SUCCESS(aws_sys_clock_get_ticks(×tamp)); struct aws_socket_endpoint endpoint; AWS_ZERO_STRUCT(endpoint); aws_socket_endpoint_init_local_address_for_test(&endpoint); @@ -498,7 +801,8 @@ static int s_test_socket_with_bind_to_interface(struct aws_allocator *allocator, #endif struct aws_socket_endpoint endpoint = {.address = "127.0.0.1", .port = 8128}; if (s_test_socket(allocator, &options, &endpoint)) { -#if !defined(AWS_OS_APPLE) && !defined(AWS_OS_LINUX) +#if !defined(AWS_OS_LINUX) + // On Apple, nw_socket currently not support network_interface_name if (aws_last_error() == AWS_ERROR_PLATFORM_NOT_SUPPORTED) { return AWS_OP_SKIP; } @@ -536,7 +840,7 @@ static int s_test_socket_with_bind_to_invalid_interface(struct aws_allocator *al options.domain = AWS_SOCKET_IPV4; strncpy(options.network_interface_name, "invalid", AWS_NETWORK_INTERFACE_NAME_MAX); struct aws_socket outgoing; -#if defined(AWS_OS_APPLE) || defined(AWS_OS_LINUX) +#if (defined(AWS_OS_APPLE) && !defined(AWS_USE_APPLE_NETWORK_FRAMEWORK)) || defined(AWS_OS_LINUX) ASSERT_ERROR(AWS_IO_SOCKET_INVALID_OPTIONS, aws_socket_init(&outgoing, allocator, &options)); #else ASSERT_ERROR(AWS_ERROR_PLATFORM_NOT_SUPPORTED, aws_socket_init(&outgoing, allocator, &options)); @@ -730,10 +1034,12 @@ static int s_test_connect_timeout(struct aws_allocator *allocator, void *ctx) { .condition_variable = &condition_variable, .connect_invoked = false, .error_invoked = false, + .shutdown_complete = false, }; struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); + aws_socket_set_cleanup_complete_callback(&outgoing, s_local_outgoing_connection_shutdown_complete, &outgoing_args); ASSERT_SUCCESS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); aws_mutex_lock(&mutex); ASSERT_SUCCESS(aws_condition_variable_wait_pred( @@ -741,7 +1047,12 @@ static int s_test_connect_timeout(struct aws_allocator *allocator, void *ctx) { aws_mutex_unlock(&mutex); ASSERT_INT_EQUALS(AWS_IO_SOCKET_TIMEOUT, outgoing_args.last_error); + aws_socket_set_cleanup_complete_callback(&outgoing, s_local_outgoing_connection_shutdown_complete, &outgoing_args); aws_socket_clean_up(&outgoing); + aws_mutex_lock(&mutex); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &condition_variable, &mutex, s_outgoing_shutdown_completed_predicate, &outgoing_args)); + aws_mutex_unlock(&mutex); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); @@ -751,7 +1062,7 @@ static int s_test_connect_timeout(struct aws_allocator *allocator, void *ctx) { AWS_TEST_CASE(connect_timeout, s_test_connect_timeout) -static int s_test_connect_timeout_cancelation(struct aws_allocator *allocator, void *ctx) { +static int s_test_connect_timeout_cancellation(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); @@ -768,7 +1079,7 @@ static int s_test_connect_timeout_cancelation(struct aws_allocator *allocator, v struct aws_socket_options options; AWS_ZERO_STRUCT(options); - options.connect_timeout_ms = 1000; + options.connect_timeout_ms = 10000; options.type = AWS_SOCKET_STREAM; options.domain = AWS_SOCKET_IPV4; @@ -826,31 +1137,32 @@ static int s_test_connect_timeout_cancelation(struct aws_allocator *allocator, v .condition_variable = &condition_variable, .connect_invoked = false, .error_invoked = false, + .shutdown_complete = false, }; struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); ASSERT_SUCCESS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); - aws_event_loop_group_release(el_group); + aws_socket_set_cleanup_complete_callback(&outgoing, s_local_outgoing_connection_shutdown_complete, &outgoing_args); - aws_thread_join_all_managed(); + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); ASSERT_INT_EQUALS(AWS_IO_EVENT_LOOP_SHUTDOWN, outgoing_args.last_error); + aws_socket_clean_up(&outgoing); + ASSERT_SUCCESS(aws_mutex_lock(outgoing_args.mutex)); + aws_condition_variable_wait_pred( + outgoing_args.condition_variable, outgoing_args.mutex, s_outgoing_shutdown_completed_predicate, &outgoing_args); + ASSERT_SUCCESS(aws_mutex_unlock(outgoing_args.mutex)); aws_io_library_clean_up(); return 0; } -AWS_TEST_CASE(connect_timeout_cancelation, s_test_connect_timeout_cancelation) - -struct error_test_args { - int error_code; - struct aws_mutex mutex; - struct aws_condition_variable condition_variable; -}; +AWS_TEST_CASE(connect_timeout_cancelation, s_test_connect_timeout_cancellation) static void s_null_sock_connection(struct aws_socket *socket, int error_code, void *user_data) { (void)socket; @@ -865,13 +1177,23 @@ static void s_null_sock_connection(struct aws_socket *socket, int error_code, vo aws_mutex_unlock(&error_args->mutex); } +static bool s_outgoing_local_error_predicate(void *args) { + struct error_test_args *test_args = (struct error_test_args *)args; + + return test_args->error_code != 0; +} + static int s_test_outgoing_local_sock_errors(struct aws_allocator *allocator, void *ctx) { (void)ctx; + aws_io_library_init(allocator); - struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); - ASSERT_SUCCESS(aws_event_loop_run(event_loop)); struct aws_socket_options options; AWS_ZERO_STRUCT(options); @@ -885,17 +1207,34 @@ static int s_test_outgoing_local_sock_errors(struct aws_allocator *allocator, vo .error_code = 0, .mutex = AWS_MUTEX_INIT, .condition_variable = AWS_CONDITION_VARIABLE_INIT, + .shutdown_invoked = false, }; struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); - ASSERT_FAILS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_null_sock_connection, &args)); - ASSERT_TRUE( - aws_last_error() == AWS_IO_SOCKET_CONNECTION_REFUSED || aws_last_error() == AWS_ERROR_FILE_INVALID_PATH); + aws_socket_set_cleanup_complete_callback(&outgoing, s_socket_error_shutdown_complete, &args); + int socket_connect_result = aws_socket_connect(&outgoing, &endpoint, event_loop, s_null_sock_connection, &args); + // As Apple network framework has an async API design, we would not get the error back on connect + if (aws_socket_get_default_impl_type() != AWS_SOCKET_IMPL_APPLE_NETWORK_FRAMEWORK) { + ASSERT_FAILS(socket_connect_result); + ASSERT_TRUE( + aws_last_error() == AWS_IO_SOCKET_CONNECTION_REFUSED || aws_last_error() == AWS_ERROR_FILE_INVALID_PATH); + } else { + ASSERT_SUCCESS(aws_mutex_lock(&args.mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &args.condition_variable, &args.mutex, s_outgoing_local_error_predicate, &args)); + ASSERT_SUCCESS(aws_mutex_unlock(&args.mutex)); + ASSERT_TRUE( + args.error_code == AWS_IO_SOCKET_CONNECTION_REFUSED || args.error_code == AWS_ERROR_FILE_INVALID_PATH); + } aws_socket_clean_up(&outgoing); - aws_event_loop_destroy(event_loop); + ASSERT_SUCCESS(aws_mutex_lock(&args.mutex)); + aws_condition_variable_wait_pred(&args.condition_variable, &args.mutex, s_socket_error_shutdown_predicate, &args); + ASSERT_SUCCESS(aws_mutex_unlock(&args.mutex)); + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); return 0; } @@ -910,10 +1249,15 @@ static bool s_outgoing_tcp_error_predicate(void *args) { static int s_test_outgoing_tcp_sock_error(struct aws_allocator *allocator, void *ctx) { (void)ctx; - struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + aws_io_library_init(allocator); + + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); - ASSERT_SUCCESS(aws_event_loop_run(event_loop)); struct aws_socket_options options; AWS_ZERO_STRUCT(options); @@ -930,10 +1274,12 @@ static int s_test_outgoing_tcp_sock_error(struct aws_allocator *allocator, void .error_code = 0, .mutex = AWS_MUTEX_INIT, .condition_variable = AWS_CONDITION_VARIABLE_INIT, + .shutdown_invoked = false, }; struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); + aws_socket_set_cleanup_complete_callback(&outgoing, s_socket_error_shutdown_complete, &args); int result = aws_socket_connect(&outgoing, &endpoint, event_loop, s_null_sock_connection, &args); #ifdef __FreeBSD__ /** @@ -958,7 +1304,13 @@ static int s_test_outgoing_tcp_sock_error(struct aws_allocator *allocator, void goto cleanup; /* to avoid unused label warning on systems other than FreeBSD */ cleanup: aws_socket_clean_up(&outgoing); - aws_event_loop_destroy(event_loop); + ASSERT_SUCCESS(aws_mutex_lock(&args.mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &args.condition_variable, &args.mutex, s_socket_error_shutdown_predicate, &args)); + ASSERT_SUCCESS(aws_mutex_unlock(&args.mutex)); + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); + return result; } AWS_TEST_CASE(outgoing_tcp_sock_error, s_test_outgoing_tcp_sock_error) @@ -966,10 +1318,15 @@ AWS_TEST_CASE(outgoing_tcp_sock_error, s_test_outgoing_tcp_sock_error) static int s_test_incoming_tcp_sock_errors(struct aws_allocator *allocator, void *ctx) { (void)ctx; if (!s_test_running_as_root(allocator)) { - struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + aws_io_library_init(allocator); + + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); - ASSERT_SUCCESS(aws_event_loop_run(event_loop)); struct aws_socket_options options; AWS_ZERO_STRUCT(options); @@ -982,12 +1339,27 @@ static int s_test_incoming_tcp_sock_errors(struct aws_allocator *allocator, void .port = 80, }; + struct error_test_args args = { + .error_code = 0, + .mutex = AWS_MUTEX_INIT, + .condition_variable = AWS_CONDITION_VARIABLE_INIT, + .shutdown_invoked = false, + }; + struct aws_socket incoming; ASSERT_SUCCESS(aws_socket_init(&incoming, allocator, &options)); ASSERT_ERROR(AWS_ERROR_NO_PERMISSION, aws_socket_bind(&incoming, &endpoint)); + aws_socket_set_cleanup_complete_callback(&incoming, s_socket_error_shutdown_complete, &args); + aws_socket_clean_up(&incoming); - aws_event_loop_destroy(event_loop); + ASSERT_SUCCESS(aws_mutex_lock(&args.mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &args.condition_variable, &args.mutex, s_socket_error_shutdown_predicate, &args)); + ASSERT_SUCCESS(aws_mutex_unlock(&args.mutex)); + + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); } return 0; } @@ -996,10 +1368,15 @@ AWS_TEST_CASE(incoming_tcp_sock_errors, s_test_incoming_tcp_sock_errors) static int s_test_incoming_duplicate_tcp_bind_errors(struct aws_allocator *allocator, void *ctx) { (void)ctx; - struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + + aws_io_library_init(allocator); + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); - ASSERT_SUCCESS(aws_event_loop_run(event_loop)); struct aws_socket_options options; AWS_ZERO_STRUCT(options); @@ -1024,12 +1401,80 @@ static int s_test_incoming_duplicate_tcp_bind_errors(struct aws_allocator *alloc aws_socket_clean_up(&duplicate_bind); aws_socket_close(&incoming); aws_socket_clean_up(&incoming); - aws_event_loop_destroy(event_loop); + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); return 0; } AWS_TEST_CASE(incoming_duplicate_tcp_bind_errors, s_test_incoming_duplicate_tcp_bind_errors) +struct nw_socket_bind_args { + struct aws_socket *incoming; + struct aws_socket *listener; + struct aws_mutex *mutex; + struct aws_condition_variable *condition_variable; + bool start_listening; + bool incoming_invoked; + bool error_invoked; + bool shutdown_complete; +}; + +static void s_bind_args_shutdown_complete(void *user_data) { + struct nw_socket_bind_args *bind_args = (struct nw_socket_bind_args *)user_data; + + aws_mutex_lock(bind_args->mutex); + bind_args->shutdown_complete = true; + aws_mutex_unlock(bind_args->mutex); + aws_condition_variable_notify_one(bind_args->condition_variable); +} + +static bool s_bind_args_shutdown_completed_predicate(void *arg) { + struct nw_socket_bind_args *bind_args = arg; + + return bind_args->shutdown_complete; +} + +static bool s_bind_args_start_listening_predicate(void *arg) { + struct nw_socket_bind_args *bind_args = arg; + + return bind_args->start_listening; +} + +static void s_local_listener_incoming_destroy_listener_bind( + struct aws_socket *socket, + int error_code, + struct aws_socket *new_socket, + void *user_data) { + (void)socket; + struct nw_socket_bind_args *listener_args = (struct nw_socket_bind_args *)user_data; + aws_mutex_lock(listener_args->mutex); + + if (!error_code) { + listener_args->incoming = new_socket; + listener_args->incoming_invoked = true; + } else { + listener_args->error_invoked = true; + } + if (new_socket) + aws_socket_clean_up(new_socket); + aws_condition_variable_notify_one(listener_args->condition_variable); + aws_mutex_unlock(listener_args->mutex); +} + +static void s_local_listener_start_accept(struct aws_socket *socket, int error_code, void *user_data) { + (void)socket; + struct nw_socket_bind_args *listener_args = (struct nw_socket_bind_args *)user_data; + aws_mutex_lock(listener_args->mutex); + + if (!error_code) { + listener_args->start_listening = true; + } else { + listener_args->error_invoked = true; + } + aws_condition_variable_notify_one(listener_args->condition_variable); + aws_mutex_unlock(listener_args->mutex); +} + /* Ensure that binding to port 0 results in OS assigning a port */ static int s_test_bind_on_zero_port( struct aws_allocator *allocator, @@ -1037,10 +1482,15 @@ static int s_test_bind_on_zero_port( enum aws_socket_domain sock_domain, const char *address) { - struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + aws_io_library_init(allocator); + + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); - ASSERT_SUCCESS(aws_event_loop_run(event_loop)); struct aws_socket_options options; AWS_ZERO_STRUCT(options); @@ -1064,10 +1514,45 @@ static int s_test_bind_on_zero_port( ASSERT_SUCCESS(aws_socket_get_bound_address(&incoming, &local_address1)); - if (sock_type != AWS_SOCKET_DGRAM) { + struct aws_mutex mutex = AWS_MUTEX_INIT; + struct aws_condition_variable condition_variable = AWS_CONDITION_VARIABLE_INIT; + struct nw_socket_bind_args listener_args = { + .incoming = NULL, + .listener = &incoming, + .incoming_invoked = false, + .error_invoked = false, + .mutex = &mutex, + .condition_variable = &condition_variable, + }; + + if (aws_socket_get_default_impl_type() == AWS_SOCKET_IMPL_APPLE_NETWORK_FRAMEWORK) { + ASSERT_SUCCESS(aws_socket_listen(&incoming, 1024)); - } + struct aws_socket_listener_options listener_options = { + .on_accept_result = s_local_listener_incoming_destroy_listener_bind, + .on_accept_result_user_data = &listener_args, + .on_accept_start = s_local_listener_start_accept, + .on_accept_start_user_data = &listener_args}; + + ASSERT_SUCCESS(aws_socket_start_accept(&incoming, event_loop, listener_options)); + + // Apple Dispatch Queue requires a listener to be ready before it can get the assigned port. We wait until the + // port is back. + ASSERT_SUCCESS(aws_mutex_lock(listener_args.mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + listener_args.condition_variable, + listener_args.mutex, + s_bind_args_start_listening_predicate, + &listener_args)); + ASSERT_SUCCESS(aws_mutex_unlock(listener_args.mutex)); + + ASSERT_SUCCESS(aws_socket_get_bound_address(&incoming, &local_address1)); + } else { + if (sock_type != AWS_SOCKET_DGRAM) { + ASSERT_SUCCESS(aws_socket_listen(&incoming, 1024)); + } + } ASSERT_TRUE(local_address1.port > 0); ASSERT_STR_EQUALS(address, local_address1.address); @@ -1077,9 +1562,19 @@ static int s_test_bind_on_zero_port( ASSERT_INT_EQUALS(local_address1.port, local_address2.port); ASSERT_STR_EQUALS(local_address1.address, local_address2.address); + aws_socket_set_cleanup_complete_callback(&incoming, s_bind_args_shutdown_complete, &listener_args); aws_socket_close(&incoming); aws_socket_clean_up(&incoming); - aws_event_loop_destroy(event_loop); + + ASSERT_SUCCESS(aws_mutex_lock(listener_args.mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + listener_args.condition_variable, + listener_args.mutex, + s_bind_args_shutdown_completed_predicate, + &listener_args)); + ASSERT_SUCCESS(aws_mutex_unlock(listener_args.mutex)); + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); return 0; } @@ -1099,10 +1594,15 @@ static int s_test_incoming_udp_sock_errors(struct aws_allocator *allocator, void (void)ctx; if (!s_test_running_as_root(allocator)) { - struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + aws_io_library_init(allocator); + + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); - ASSERT_SUCCESS(aws_event_loop_run(event_loop)); struct aws_socket_options options; AWS_ZERO_STRUCT(options); @@ -1123,7 +1623,8 @@ static int s_test_incoming_udp_sock_errors(struct aws_allocator *allocator, void ASSERT_TRUE(AWS_IO_SOCKET_INVALID_ADDRESS == error || AWS_ERROR_NO_PERMISSION == error); aws_socket_clean_up(&incoming); - aws_event_loop_destroy(event_loop); + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); } return 0; } @@ -1138,10 +1639,15 @@ static void s_on_null_readable_notification(struct aws_socket *socket, int error static int s_test_wrong_thread_read_write_fails(struct aws_allocator *allocator, void *ctx) { (void)ctx; - struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + aws_io_library_init(allocator); + + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); - ASSERT_SUCCESS(aws_event_loop_run(event_loop)); struct aws_socket_options options; AWS_ZERO_STRUCT(options); @@ -1182,7 +1688,8 @@ static int s_test_wrong_thread_read_write_fails(struct aws_allocator *allocator, aws_mutex_unlock(&mutex); aws_socket_clean_up(&socket); - aws_event_loop_destroy(event_loop); + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); return 0; } @@ -1260,6 +1767,13 @@ static int s_cleanup_before_connect_or_timeout_doesnt_explode(struct aws_allocat .condition_variable = &condition_variable, .connect_invoked = false, .error_invoked = false, + .shutdown_complete = false, + }; + + struct error_test_args shutdown_args = { + .mutex = AWS_MUTEX_INIT, + .condition_variable = AWS_CONDITION_VARIABLE_INIT, + .shutdown_invoked = false, }; struct aws_socket outgoing; @@ -1270,7 +1784,10 @@ static int s_cleanup_before_connect_or_timeout_doesnt_explode(struct aws_allocat }; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); + ASSERT_SUCCESS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); + aws_socket_set_cleanup_complete_callback(&outgoing, s_socket_error_shutdown_complete, &shutdown_args); + aws_event_loop_schedule_task_now(event_loop, &destroy_task); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); ASSERT_ERROR( @@ -1287,6 +1804,11 @@ static int s_cleanup_before_connect_or_timeout_doesnt_explode(struct aws_allocat aws_io_library_clean_up(); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &condition_variable, &mutex, s_socket_error_shutdown_predicate, &shutdown_args)); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + return 0; } @@ -1307,7 +1829,10 @@ static void s_local_listener_incoming_destroy_listener( } else { listener_args->error_invoked = true; } - aws_socket_clean_up(socket); + + if (socket) { + aws_socket_clean_up(socket); + } aws_condition_variable_notify_one(listener_args->condition_variable); aws_mutex_unlock(listener_args->mutex); } @@ -1315,10 +1840,15 @@ static void s_local_listener_incoming_destroy_listener( static int s_cleanup_in_accept_doesnt_explode(struct aws_allocator *allocator, void *ctx) { (void)ctx; - struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + aws_io_library_init(allocator); + + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); - ASSERT_SUCCESS(aws_event_loop_run(event_loop)); struct aws_mutex mutex = AWS_MUTEX_INIT; struct aws_condition_variable condition_variable = AWS_CONDITION_VARIABLE_INIT; @@ -1329,6 +1859,7 @@ static int s_cleanup_in_accept_doesnt_explode(struct aws_allocator *allocator, v .incoming = NULL, .incoming_invoked = false, .error_invoked = false, + .shutdown_complete = false, }; struct aws_socket_options options; @@ -1348,8 +1879,13 @@ static int s_cleanup_in_accept_doesnt_explode(struct aws_allocator *allocator, v ASSERT_SUCCESS(aws_socket_bind(&listener, &endpoint)); ASSERT_SUCCESS(aws_socket_listen(&listener, 1024)); - ASSERT_SUCCESS( - aws_socket_start_accept(&listener, event_loop, s_local_listener_incoming_destroy_listener, &listener_args)); +#ifdef AWS_USE_APPLE_NETWORK_FRAMEWORK + aws_socket_set_cleanup_complete_callback(&listener, s_local_listener_shutdown_complete, &listener_args); +#endif + struct aws_socket_listener_options listener_options = { + .on_accept_result = s_local_listener_incoming_destroy_listener, .on_accept_result_user_data = &listener_args}; + + ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, listener_options)); struct local_outgoing_args outgoing_args = { .mutex = &mutex, .condition_variable = &condition_variable, .connect_invoked = false, .error_invoked = false}; @@ -1382,6 +1918,7 @@ static int s_cleanup_in_accept_doesnt_explode(struct aws_allocator *allocator, v .error_code = 0, .condition_variable = AWS_CONDITION_VARIABLE_INIT, .close_completed = false, + .shutdown_complete = false, }; struct aws_task close_task = { @@ -1392,24 +1929,41 @@ static int s_cleanup_in_accept_doesnt_explode(struct aws_allocator *allocator, v if (listener_args.incoming) { io_args.socket = listener_args.incoming; io_args.close_completed = false; + +#ifdef AWS_USE_APPLE_NETWORK_FRAMEWORK + aws_socket_set_cleanup_complete_callback(io_args.socket, s_socket_shutdown_complete_fn, &io_args); + io_args.shutdown_complete = false; +#endif + aws_socket_assign_to_event_loop(io_args.socket, event_loop); aws_event_loop_schedule_task_now(event_loop, &close_task); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_close_completed_predicate, &io_args); ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); - aws_socket_clean_up(listener_args.incoming); + aws_socket_clean_up(io_args.socket); +#ifdef AWS_USE_APPLE_NETWORK_FRAMEWORK + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_shutdown_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); +#endif aws_mem_release(allocator, listener_args.incoming); } io_args.socket = &outgoing; + aws_socket_set_cleanup_complete_callback(io_args.socket, s_socket_shutdown_complete_fn, &io_args); io_args.close_completed = false; + io_args.shutdown_complete = false; aws_event_loop_schedule_task_now(event_loop, &close_task); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_close_completed_predicate, &io_args); ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); - aws_socket_clean_up(&outgoing); - aws_event_loop_destroy(event_loop); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_shutdown_completed_predicate, &io_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); return 0; } @@ -1421,7 +1975,9 @@ static void s_on_written_destroy(struct aws_socket *socket, int error_code, size aws_mutex_lock(write_args->mutex); write_args->error_code = error_code; write_args->amount_written = amount_written; - aws_socket_clean_up(socket); + if (socket) { + aws_socket_clean_up(socket); + } aws_condition_variable_notify_one(&write_args->condition_variable); aws_mutex_unlock(write_args->mutex); } @@ -1443,10 +1999,15 @@ static void s_write_task_destroy(struct aws_task *task, void *args, enum aws_tas static int s_cleanup_in_write_cb_doesnt_explode(struct aws_allocator *allocator, void *ctx) { (void)ctx; - struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + aws_io_library_init(allocator); + + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); - ASSERT_SUCCESS(aws_event_loop_run(event_loop)); struct aws_mutex mutex = AWS_MUTEX_INIT; struct aws_condition_variable condition_variable = AWS_CONDITION_VARIABLE_INIT; @@ -1457,6 +2018,7 @@ static int s_cleanup_in_write_cb_doesnt_explode(struct aws_allocator *allocator, .incoming = NULL, .incoming_invoked = false, .error_invoked = false, + .shutdown_complete = false, }; struct aws_socket_options options; @@ -1475,7 +2037,9 @@ static int s_cleanup_in_write_cb_doesnt_explode(struct aws_allocator *allocator, ASSERT_SUCCESS(aws_socket_bind(&listener, &endpoint)); ASSERT_SUCCESS(aws_socket_listen(&listener, 1024)); - ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, s_local_listener_incoming, &listener_args)); + struct aws_socket_listener_options listener_options = { + .on_accept_result = s_local_listener_incoming, .on_accept_result_user_data = &listener_args}; + ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, listener_options)); struct local_outgoing_args outgoing_args = { .mutex = &mutex, .condition_variable = &condition_variable, .connect_invoked = false, .error_invoked = false}; @@ -1523,8 +2087,13 @@ static int s_cleanup_in_write_cb_doesnt_explode(struct aws_allocator *allocator, .error_code = 0, .condition_variable = AWS_CONDITION_VARIABLE_INIT, .close_completed = false, + .shutdown_complete = false, }; +#ifdef AWS_USE_APPLE_NETWORK_FRAMEWORK + aws_socket_set_cleanup_complete_callback(io_args.socket, s_socket_shutdown_complete_fn, &io_args); +#endif + struct aws_task write_task = { .fn = s_write_task_destroy, .arg = &io_args, @@ -1543,15 +2112,33 @@ static int s_cleanup_in_write_cb_doesnt_explode(struct aws_allocator *allocator, io_args.error_code = 0; io_args.amount_written = 0; io_args.socket = server_sock; + io_args.shutdown_complete = false; +#ifdef AWS_USE_APPLE_NETWORK_FRAMEWORK + aws_socket_set_cleanup_complete_callback(io_args.socket, s_socket_shutdown_complete_fn, &io_args); +#endif aws_event_loop_schedule_task_now(event_loop, &write_task); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_write_completed_predicate, &io_args); +#ifdef AWS_USE_APPLE_NETWORK_FRAMEWORK + aws_condition_variable_wait_pred(&io_args.condition_variable, &mutex, s_shutdown_completed_predicate, &io_args); +#endif ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); ASSERT_INT_EQUALS(AWS_OP_SUCCESS, io_args.error_code); - aws_mem_release(allocator, server_sock); + +#ifdef AWS_USE_APPLE_NETWORK_FRAMEWORK + aws_socket_set_cleanup_complete_callback(&listener, s_local_listener_shutdown_complete, &listener_args); +#endif aws_socket_clean_up(&listener); - aws_event_loop_destroy(event_loop); +#ifdef AWS_USE_APPLE_NETWORK_FRAMEWORK + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + aws_condition_variable_wait_pred( + listener_args.condition_variable, &mutex, s_local_listener_shutdown_completed_predicate, &listener_args); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); +#endif + + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); return 0; } @@ -1566,13 +2153,14 @@ enum async_role { ASYNC_ROLE_COUNT }; -static struct { +static struct async_test_args { struct aws_allocator *allocator; struct aws_event_loop *event_loop; struct aws_socket *write_socket; struct aws_socket *read_socket; bool currently_writing; enum async_role next_expected_callback; + int read_error; struct aws_mutex *mutex; struct aws_condition_variable *condition_variable; @@ -1598,7 +2186,12 @@ static void s_async_read_task(struct aws_task *task, void *args, enum aws_task_s buf.len = 0; if (aws_socket_read(g_async_tester.read_socket, &buf, &amount_read)) { /* reschedule task to try reading more later */ - if (AWS_IO_READ_WOULD_BLOCK == aws_last_error()) { + /* + * For Apple Network Framework (dispatch queue), the read error would not directly returned from + * aws_socket_read, but from the callback, therefore, we validate the g_async_tester.read_error + * returned from the callback + */ + if (!g_async_tester.read_error && AWS_IO_READ_WOULD_BLOCK == aws_last_error()) { aws_event_loop_schedule_task_now(g_async_tester.event_loop, task); break; } @@ -1684,6 +2277,15 @@ static void s_async_write_task(struct aws_task *task, void *args, enum aws_task_ g_async_tester.currently_writing = false; } +static void s_on_readable_return(struct aws_socket *socket, int error_code, void *user_data) { + (void)socket; + (void)error_code; + struct async_test_args *async_tester = user_data; + if (error_code) { + async_tester->read_error = error_code; + } +} + /** * aws_socket_write()'s completion callback MUST fire asynchronously. * Otherwise, we can get multiple write() calls in the same callstack, which @@ -1693,10 +2295,15 @@ static int s_sock_write_cb_is_async(struct aws_allocator *allocator, void *ctx) (void)ctx; /* set up server (read) and client (write) sockets */ - struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + aws_io_library_init(allocator); + + struct aws_event_loop_group_options elg_options = { + .loop_count = 1, + }; + struct aws_event_loop_group *el_group = aws_event_loop_group_new(allocator, &elg_options); + struct aws_event_loop *event_loop = aws_event_loop_group_get_next_loop(el_group); ASSERT_NOT_NULL(event_loop, "Event loop creation failed with error: %s", aws_error_debug_str(aws_last_error())); - ASSERT_SUCCESS(aws_event_loop_run(event_loop)); struct aws_mutex mutex = AWS_MUTEX_INIT; struct aws_condition_variable condition_variable = AWS_CONDITION_VARIABLE_INIT; @@ -1707,6 +2314,7 @@ static int s_sock_write_cb_is_async(struct aws_allocator *allocator, void *ctx) .incoming = NULL, .incoming_invoked = false, .error_invoked = false, + .shutdown_complete = false, }; struct aws_socket_options options; @@ -1726,7 +2334,9 @@ static int s_sock_write_cb_is_async(struct aws_allocator *allocator, void *ctx) ASSERT_SUCCESS(aws_socket_bind(&listener, &endpoint)); ASSERT_SUCCESS(aws_socket_listen(&listener, 1024)); - ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, s_local_listener_incoming, &listener_args)); + struct aws_socket_listener_options listener_options = { + .on_accept_result = s_local_listener_incoming, .on_accept_result_user_data = &listener_args}; + ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, listener_options)); struct local_outgoing_args outgoing_args = { .mutex = &mutex, .condition_variable = &condition_variable, .connect_invoked = false, .error_invoked = false}; @@ -1750,7 +2360,7 @@ static int s_sock_write_cb_is_async(struct aws_allocator *allocator, void *ctx) ASSERT_INT_EQUALS(options.type, listener_args.incoming->options.type); ASSERT_SUCCESS(aws_socket_assign_to_event_loop(server_sock, event_loop)); - aws_socket_subscribe_to_readable_events(server_sock, s_on_readable, NULL); + aws_socket_subscribe_to_readable_events(server_sock, s_on_readable_return, &g_async_tester); aws_socket_subscribe_to_readable_events(&outgoing, s_on_readable, NULL); /* set up g_async_tester */ @@ -1775,9 +2385,17 @@ static int s_sock_write_cb_is_async(struct aws_allocator *allocator, void *ctx) aws_condition_variable_wait_pred(&condition_variable, &mutex, s_async_tasks_complete_pred, NULL); aws_mutex_unlock(&mutex); + aws_socket_set_cleanup_complete_callback(&listener, s_local_listener_shutdown_complete, &listener_args); /* cleanup */ aws_socket_clean_up(&listener); - aws_event_loop_destroy(event_loop); + ASSERT_SUCCESS(aws_mutex_lock(&mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &condition_variable, &mutex, s_local_listener_shutdown_completed_predicate, &listener_args)); + ASSERT_SUCCESS(aws_mutex_unlock(&mutex)); + + aws_event_loop_group_release(el_group); + aws_io_library_clean_up(); + return 0; } AWS_TEST_CASE(sock_write_cb_is_async, s_sock_write_cb_is_async) @@ -1829,7 +2447,9 @@ static int s_local_socket_pipe_connected_race(struct aws_allocator *allocator, v ASSERT_SUCCESS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); - ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, s_local_listener_incoming, &listener_args)); + struct aws_socket_listener_options listener_options = { + .on_accept_result = s_local_listener_incoming, .on_accept_result_user_data = &listener_args}; + ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, listener_options)); aws_mutex_lock(&mutex); ASSERT_SUCCESS(aws_condition_variable_wait_pred(&condition_variable, &mutex, s_incoming_predicate, &listener_args)); ASSERT_SUCCESS(aws_condition_variable_wait_pred(