Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Diff: c-utility/src/uws_client.c
- Revision:
- 0:f7f1f0d76dd6
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/c-utility/src/uws_client.c Thu Aug 23 06:52:14 2018 +0000 @@ -0,0 +1,2112 @@ + // Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include <ctype.h> +#include <limits.h> +#include "azure_c_shared_utility/gballoc.h" +#include "azure_c_shared_utility/uws_client.h" +#include "azure_c_shared_utility/optimize_size.h" +#include "azure_c_shared_utility/xlogging.h" +#include "azure_c_shared_utility/xio.h" +#include "azure_c_shared_utility/singlylinkedlist.h" +#include "azure_c_shared_utility/socketio.h" +#include "azure_c_shared_utility/platform.h" +#include "azure_c_shared_utility/tlsio.h" +#include "azure_c_shared_utility/crt_abstractions.h" +#include "azure_c_shared_utility/buffer_.h" +#include "azure_c_shared_utility/uws_frame_encoder.h" +#include "azure_c_shared_utility/crt_abstractions.h" +#include "azure_c_shared_utility/utf8_checker.h" +#include "azure_c_shared_utility/gb_rand.h" +#include "azure_c_shared_utility/base64.h" +#include "azure_c_shared_utility/optionhandler.h" + +static const char* UWS_CLIENT_OPTIONS = "uWSClientOptions"; + +/* Requirements not needed as they are optional: +Codes_SRS_UWS_CLIENT_01_254: [ If an endpoint receives a Ping frame and has not yet sent Pong frame(s) in response to previous Ping frame(s), the endpoint MAY elect to send a Pong frame for only the most recently processed Ping frame. ] +Codes_SRS_UWS_CLIENT_01_255: [ A Pong frame MAY be sent unsolicited. ] +Codes_SRS_UWS_CLIENT_01_256: [ A response to an unsolicited Pong frame is not expected. ] +*/ + +/* Requirements satisfied by the underlying TLS/socket stack +Codes_SRS_UWS_CLIENT_01_362: [ To achieve reasonable levels of protection, clients should use only Strong TLS algorithms. ] +Codes_SRS_UWS_CLIENT_01_289: [ An endpoint SHOULD use a method that cleanly closes the TCP connection, as well as the TLS session, if applicable, discarding any trailing bytes that may have been received. ] +Codes_SRS_UWS_CLIENT_01_078: [ Otherwise, all further communication on this channel MUST run through the encrypted tunnel [RFC5246]. ] +Codes_SRS_UWS_CLIENT_01_141: [ masking is done whether or not the WebSocket Protocol is running over TLS. ] +*/ + +/* Requirements satisfied by the way the APIs are designed: +Codes_SRS_UWS_CLIENT_01_211: [One implication of this is that in absence of extensions, senders and receivers must not depend on the presence of specific frame boundaries.] +*/ + +typedef enum UWS_STATE_TAG +{ + UWS_STATE_CLOSED, + UWS_STATE_OPENING_UNDERLYING_IO, + UWS_STATE_WAITING_FOR_UPGRADE_RESPONSE, + UWS_STATE_OPEN, + UWS_STATE_CLOSING_WAITING_FOR_CLOSE, + UWS_STATE_CLOSING_SENDING_CLOSE, + UWS_STATE_CLOSING_UNDERLYING_IO, + UWS_STATE_ERROR +} UWS_STATE; + +typedef struct WS_INSTANCE_PROTOCOL_TAG +{ + char* protocol; +} WS_INSTANCE_PROTOCOL; + +typedef struct WS_PENDING_SEND_TAG +{ + ON_WS_SEND_FRAME_COMPLETE on_ws_send_frame_complete; + void* context; + UWS_CLIENT_HANDLE uws_client; +} WS_PENDING_SEND; + +typedef struct UWS_CLIENT_INSTANCE_TAG +{ + SINGLYLINKEDLIST_HANDLE pending_sends; + XIO_HANDLE underlying_io; + char* hostname; + char* resource_name; + WS_INSTANCE_PROTOCOL* protocols; + size_t protocol_count; + int port; + UWS_STATE uws_state; + ON_WS_OPEN_COMPLETE on_ws_open_complete; + void* on_ws_open_complete_context; + ON_WS_FRAME_RECEIVED on_ws_frame_received; + void* on_ws_frame_received_context; + ON_WS_PEER_CLOSED on_ws_peer_closed; + void* on_ws_peer_closed_context; + ON_WS_ERROR on_ws_error; + void* on_ws_error_context; + ON_WS_CLOSE_COMPLETE on_ws_close_complete; + void* on_ws_close_complete_context; + unsigned char* stream_buffer; + size_t stream_buffer_count; + unsigned char* fragment_buffer; + size_t fragment_buffer_count; + unsigned char fragmented_frame_type; +} UWS_CLIENT_INSTANCE; + +/* Codes_SRS_UWS_CLIENT_01_360: [ Connection confidentiality and integrity is provided by running the WebSocket Protocol over TLS (wss URIs). ]*/ +/* Codes_SRS_UWS_CLIENT_01_361: [ WebSocket implementations MUST support TLS and SHOULD employ it when communicating with their peers. ]*/ +/* Codes_SRS_UWS_CLIENT_01_063: [ A client will need to supply a /host/, /port/, /resource name/, and a /secure/ flag, which are the components of a WebSocket URI as discussed in Section 3, along with a list of /protocols/ and /extensions/ to be used. ]*/ +UWS_CLIENT_HANDLE uws_client_create(const char* hostname, unsigned int port, const char* resource_name, bool use_ssl, const WS_PROTOCOL* protocols, size_t protocol_count) +{ + UWS_CLIENT_HANDLE result; + + /* Codes_SRS_UWS_CLIENT_01_002: [ If any of the arguments `hostname` and `resource_name` is NULL then `uws_client_create` shall return NULL. ]*/ + if ((hostname == NULL) || + (resource_name == NULL) || + /* Codes_SRS_UWS_CLIENT_01_411: [ If `protocol_count` is non zero and `protocols` is NULL then `uws_client_create` shall fail and return NULL. ]*/ + ((protocols == NULL) && (protocol_count > 0))) + { + LogError("Invalid arguments: hostname = %p, resource_name = %p, protocols = %p, protocol_count = %lu", hostname, resource_name, protocols, protocol_count); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_412: [ If the `protocol` member of any of the items in the `protocols` argument is NULL, then `uws_client_create` shall fail and return NULL. ]*/ + size_t i; + for (i = 0; i < protocol_count; i++) + { + if (protocols[i].protocol == NULL) + { + break; + } + } + + if (i < protocol_count) + { + LogError("Protocol index %lu has NULL name", i); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_001: [`uws_client_create` shall create an instance of uws and return a non-NULL handle to it.]*/ + result = (UWS_CLIENT_HANDLE)malloc(sizeof(UWS_CLIENT_INSTANCE)); + if (result == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_003: [ If allocating memory for the new uws instance fails then `uws_client_create` shall return NULL. ]*/ + LogError("Could not allocate uWS instance"); + } + else + { + /* Codes_SRS_UWS_CLIENT_01_004: [ The argument `hostname` shall be copied for later use. ]*/ + if (mallocAndStrcpy_s(&result->hostname, hostname) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_392: [ If allocating memory for the copy of the `hostname` argument fails, then `uws_client_create` shall return NULL. ]*/ + LogError("Could not copy hostname."); + free(result); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_404: [ The argument `resource_name` shall be copied for later use. ]*/ + if (mallocAndStrcpy_s(&result->resource_name, resource_name) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_405: [ If allocating memory for the copy of the `resource` argument fails, then `uws_client_create` shall return NULL. ]*/ + LogError("Could not copy resource."); + free(result->hostname); + free(result); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_017: [ `uws_client_create` shall create a pending send IO list that is to be used to queue send packets by calling `singlylinkedlist_create`. ]*/ + result->pending_sends = singlylinkedlist_create(); + if (result->pending_sends == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_018: [ If `singlylinkedlist_create` fails then `uws_client_create` shall fail and return NULL. ]*/ + LogError("Could not allocate pending send frames list"); + free(result->resource_name); + free(result->hostname); + free(result); + result = NULL; + } + else + { + if (use_ssl == true) + { + TLSIO_CONFIG tlsio_config; + + /* Codes_SRS_UWS_CLIENT_01_006: [ If `use_ssl` is true then `uws_client_create` shall obtain the interface used to create a tlsio instance by calling `platform_get_default_tlsio`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_076: [ If /secure/ is true, the client MUST perform a TLS handshake over the connection after opening the connection and before sending the handshake data [RFC2818]. ]*/ + const IO_INTERFACE_DESCRIPTION* tlsio_interface = platform_get_default_tlsio(); + if (tlsio_interface == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_007: [ If obtaining the underlying IO interface fails, then `uws_client_create` shall fail and return NULL. ]*/ + LogError("NULL TLSIO interface description"); + result->underlying_io = NULL; + } + else + { + SOCKETIO_CONFIG socketio_config; + + /* Codes_SRS_UWS_CLIENT_01_013: [ The create arguments for the tls IO (when `use_ssl` is 1) shall have: ]*/ + /* Codes_SRS_UWS_CLIENT_01_014: [ - `hostname` set to the `hostname` argument passed to `uws_client_create`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_015: [ - `port` set to the `port` argument passed to `uws_client_create`. ]*/ + socketio_config.hostname = hostname; + socketio_config.port = port; + socketio_config.accepted_socket = NULL; + + tlsio_config.hostname = hostname; + tlsio_config.port = port; + tlsio_config.underlying_io_interface = socketio_get_interface_description(); + tlsio_config.underlying_io_parameters = &socketio_config; + + result->underlying_io = xio_create(tlsio_interface, &tlsio_config); + if (result->underlying_io == NULL) + { + LogError("Cannot create underlying TLS IO."); + } + } + } + else + { + SOCKETIO_CONFIG socketio_config; + /* Codes_SRS_UWS_CLIENT_01_005: [ If `use_ssl` is false then `uws_client_create` shall obtain the interface used to create a socketio instance by calling `socketio_get_interface_description`. ]*/ + const IO_INTERFACE_DESCRIPTION* socketio_interface = socketio_get_interface_description(); + if (socketio_interface == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_007: [ If obtaining the underlying IO interface fails, then `uws_client_create` shall fail and return NULL. ]*/ + LogError("NULL socketio interface description"); + result->underlying_io = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_010: [ The create arguments for the socket IO (when `use_ssl` is 0) shall have: ]*/ + /* Codes_SRS_UWS_CLIENT_01_011: [ - `hostname` set to the `hostname` argument passed to `uws_client_create`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_012: [ - `port` set to the `port` argument passed to `uws_client_create`. ]*/ + socketio_config.hostname = hostname; + socketio_config.port = port; + socketio_config.accepted_socket = NULL; + + /* Codes_SRS_UWS_CLIENT_01_008: [ The obtained interface shall be used to create the IO used as underlying IO by the newly created uws instance. ]*/ + /* Codes_SRS_UWS_CLIENT_01_009: [ The underlying IO shall be created by calling `xio_create`. ]*/ + result->underlying_io = xio_create(socketio_interface, &socketio_config); + if (result->underlying_io == NULL) + { + LogError("Cannot create underlying socket IO."); + } + } + } + + if (result->underlying_io == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_016: [ If `xio_create` fails, then `uws_client_create` shall fail and return NULL. ]*/ + singlylinkedlist_destroy(result->pending_sends); + free(result->resource_name); + free(result->hostname); + free(result); + result = NULL; + } + else + { + result->uws_state = UWS_STATE_CLOSED; + /* Codes_SRS_UWS_CLIENT_01_403: [ The argument `port` shall be copied for later use. ]*/ + result->port = port; + + result->on_ws_open_complete = NULL; + result->on_ws_open_complete_context = NULL; + result->on_ws_frame_received = NULL; + result->on_ws_frame_received_context = NULL; + result->on_ws_error = NULL; + result->on_ws_error_context = NULL; + result->on_ws_close_complete = NULL; + result->on_ws_close_complete_context = NULL; + result->stream_buffer = NULL; + result->stream_buffer_count = 0; + result->fragment_buffer = NULL; + result->fragment_buffer_count = 0; + result->fragmented_frame_type = WS_FRAME_TYPE_UNKNOWN; + + result->protocol_count = protocol_count; + + /* Codes_SRS_UWS_CLIENT_01_410: [ The `protocols` argument shall be allowed to be NULL, in which case no protocol is to be specified by the client in the upgrade request. ]*/ + if (protocols == NULL) + { + result->protocols = NULL; + } + else + { + result->protocols = (WS_INSTANCE_PROTOCOL*)malloc(sizeof(WS_INSTANCE_PROTOCOL) * protocol_count); + if (result->protocols == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_414: [ If allocating memory for the copied protocol information fails then `uws_client_create` shall fail and return NULL. ]*/ + LogError("Cannot allocate memory for the protocols array."); + xio_destroy(result->underlying_io); + singlylinkedlist_destroy(result->pending_sends); + free(result->resource_name); + free(result->hostname); + free(result); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_413: [ The protocol information indicated by `protocols` and `protocol_count` shall be copied for later use (for constructing the upgrade request). ]*/ + for (i = 0; i < protocol_count; i++) + { + if (mallocAndStrcpy_s(&result->protocols[i].protocol, protocols[i].protocol) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_414: [ If allocating memory for the copied protocol information fails then `uws_client_create` shall fail and return NULL. ]*/ + LogError("Cannot allocate memory for the protocol index %u.", (unsigned int)i); + break; + } + } + + if (i < protocol_count) + { + size_t j; + + for (j = 0; j < i; j++) + { + free(result->protocols[j].protocol); + } + + free(result->protocols); + xio_destroy(result->underlying_io); + singlylinkedlist_destroy(result->pending_sends); + free(result->resource_name); + free(result->hostname); + free(result); + result = NULL; + } + else + { + result->protocol_count = protocol_count; + } + } + } + } + } + } + } + } + } + } + + return result; +} + +UWS_CLIENT_HANDLE uws_client_create_with_io(const IO_INTERFACE_DESCRIPTION* io_interface, void* io_create_parameters, const char* hostname, unsigned int port, const char* resource_name, const WS_PROTOCOL* protocols, size_t protocol_count) +{ + UWS_CLIENT_HANDLE result; + + /* Codes_SRS_UWS_CLIENT_01_516: [ If any of the arguments `io_interface`, `hostname` and `resource_name` is NULL then `uws_client_create_with_io` shall return NULL. ]*/ + if ((hostname == NULL) || + (io_interface == NULL) || + (resource_name == NULL) || + /* Codes_SRS_UWS_CLIENT_01_525: [ If `protocol_count` is non zero and `protocols` is NULL then `uws_client_create_with_io` shall fail and return NULL. ]*/ + ((protocols == NULL) && (protocol_count > 0))) + { + LogError("Invalid arguments: io_interface = %p, resource_name = %p, protocols = %p, protocol_count = %lu", io_interface, resource_name, protocols, protocol_count); + result = NULL; + } + else + { + size_t i; + for (i = 0; i < protocol_count; i++) + { + if (protocols[i].protocol == NULL) + { + break; + } + } + + if (i < protocol_count) + { + /* Codes_SRS_UWS_CLIENT_01_526: [ If the `protocol` member of any of the items in the `protocols` argument is NULL, then `uws_client_create_with_io` shall fail and return NULL. ]*/ + LogError("Protocol index %lu has NULL name", i); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_515: [ `uws_client_create_with_io` shall create an instance of uws and return a non-NULL handle to it. ]*/ + result = (UWS_CLIENT_HANDLE)malloc(sizeof(UWS_CLIENT_INSTANCE)); + if (result == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_517: [ If allocating memory for the new uws instance fails then `uws_client_create_with_io` shall return NULL. ]*/ + LogError("Could not allocate uWS instance"); + } + else + { + /* Codes_SRS_UWS_CLIENT_01_518: [ The argument `hostname` shall be copied for later use. ]*/ + if (mallocAndStrcpy_s(&result->hostname, hostname) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_519: [ If allocating memory for the copy of the `hostname` argument fails, then `uws_client_create` shall return NULL. ]*/ + LogError("Could not copy hostname."); + free(result); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_523: [ The argument `resource_name` shall be copied for later use. ]*/ + if (mallocAndStrcpy_s(&result->resource_name, resource_name) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_529: [ If allocating memory for the copy of the `resource_name` argument fails, then `uws_client_create_with_io` shall return NULL. ]*/ + LogError("Could not copy resource."); + free(result->hostname); + free(result); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_530: [ `uws_client_create_with_io` shall create a pending send IO list that is to be used to queue send packets by calling `singlylinkedlist_create`. ]*/ + result->pending_sends = singlylinkedlist_create(); + if (result->pending_sends == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_531: [ If `singlylinkedlist_create` fails then `uws_client_create_with_io` shall fail and return NULL. ]*/ + LogError("Could not allocate pending send frames list"); + free(result->resource_name); + free(result->hostname); + free(result); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_521: [ The underlying IO shall be created by calling `xio_create`, while passing as arguments the `io_interface` and `io_create_parameters` argument values. ]*/ + result->underlying_io = xio_create(io_interface, io_create_parameters); + if (result->underlying_io == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_522: [ If `xio_create` fails, then `uws_client_create_with_io` shall fail and return NULL. ]*/ + LogError("Cannot create underlying IO."); + singlylinkedlist_destroy(result->pending_sends); + free(result->resource_name); + free(result->hostname); + free(result); + result = NULL; + } + else + { + result->uws_state = UWS_STATE_CLOSED; + + /* Codes_SRS_UWS_CLIENT_01_520: [ The argument `port` shall be copied for later use. ]*/ + result->port = port; + + result->on_ws_open_complete = NULL; + result->on_ws_open_complete_context = NULL; + result->on_ws_frame_received = NULL; + result->on_ws_frame_received_context = NULL; + result->on_ws_error = NULL; + result->on_ws_error_context = NULL; + result->on_ws_close_complete = NULL; + result->on_ws_close_complete_context = NULL; + result->stream_buffer = NULL; + result->stream_buffer_count = 0; + result->fragment_buffer = NULL; + result->fragment_buffer_count = 0; + result->fragmented_frame_type = WS_FRAME_TYPE_UNKNOWN; + + result->protocol_count = protocol_count; + + /* Codes_SRS_UWS_CLIENT_01_524: [ The `protocols` argument shall be allowed to be NULL, in which case no protocol is to be specified by the client in the upgrade request. ]*/ + if (protocols == NULL) + { + result->protocols = NULL; + } + else + { + result->protocols = (WS_INSTANCE_PROTOCOL*)malloc(sizeof(WS_INSTANCE_PROTOCOL) * protocol_count); + if (result->protocols == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_414: [ If allocating memory for the copied protocol information fails then `uws_client_create` shall fail and return NULL. ]*/ + LogError("Cannot allocate memory for the protocols array."); + xio_destroy(result->underlying_io); + singlylinkedlist_destroy(result->pending_sends); + free(result->resource_name); + free(result->hostname); + free(result); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_527: [ The protocol information indicated by `protocols` and `protocol_count` shall be copied for later use (for constructing the upgrade request). ]*/ + for (i = 0; i < protocol_count; i++) + { + if (mallocAndStrcpy_s(&result->protocols[i].protocol, protocols[i].protocol) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_528: [ If allocating memory for the copied protocol information fails then `uws_client_create_with_io` shall fail and return NULL. ]*/ + LogError("Cannot allocate memory for the protocol index %u.", (unsigned int)i); + break; + } + } + + if (i < protocol_count) + { + size_t j; + + for (j = 0; j < i; j++) + { + free(result->protocols[j].protocol); + } + + free(result->protocols); + xio_destroy(result->underlying_io); + singlylinkedlist_destroy(result->pending_sends); + free(result->resource_name); + free(result->hostname); + free(result); + result = NULL; + } + else + { + result->protocol_count = protocol_count; + } + } + } + } + } + } + } + } + } + } + + return result; +} + +void uws_client_destroy(UWS_CLIENT_HANDLE uws_client) +{ + /* Codes_SRS_UWS_CLIENT_01_020: [ If `uws_client` is NULL, `uws_client_destroy` shall do nothing. ]*/ + if (uws_client == NULL) + { + LogError("NULL uws handle"); + } + else + { + free(uws_client->stream_buffer); + free(uws_client->fragment_buffer); + + /* Codes_SRS_UWS_CLIENT_01_021: [ `uws_client_destroy` shall perform a close action if the uws instance has already been open. ]*/ + switch (uws_client->uws_state) + { + default: + break; + + case UWS_STATE_OPEN: + case UWS_STATE_ERROR: + uws_client_close_async(uws_client, NULL, NULL); + break; + } + + if (uws_client->protocol_count > 0) + { + size_t i; + + /* Codes_SRS_UWS_CLIENT_01_437: [ `uws_client_destroy` shall free the protocols array allocated in `uws_client_create`. ]*/ + for (i = 0; i < uws_client->protocol_count; i++) + { + free(uws_client->protocols[i].protocol); + } + + free(uws_client->protocols); + } + + /* Codes_SRS_UWS_CLIENT_01_019: [ `uws_client_destroy` shall free all resources associated with the uws instance. ]*/ + /* Codes_SRS_UWS_CLIENT_01_023: [ `uws_client_destroy` shall ensure the underlying IO created in `uws_client_open_async` is destroyed by calling `xio_destroy`. ]*/ + if (uws_client->underlying_io != NULL) + { + xio_destroy(uws_client->underlying_io); + uws_client->underlying_io = NULL; + } + + /* Codes_SRS_UWS_CLIENT_01_024: [ `uws_client_destroy` shall free the list used to track the pending sends by calling `singlylinkedlist_destroy`. ]*/ + singlylinkedlist_destroy(uws_client->pending_sends); + free(uws_client->resource_name); + free(uws_client->hostname); + free(uws_client); + } +} + +static void indicate_ws_open_complete_error(UWS_CLIENT_INSTANCE* uws_client, WS_OPEN_RESULT ws_open_result) +{ + /* Codes_SRS_UWS_CLIENT_01_409: [ After any error is indicated by `on_ws_open_complete`, a subsequent `uws_client_open_async` shall be possible. ]*/ + uws_client->uws_state = UWS_STATE_CLOSED; + uws_client->on_ws_open_complete(uws_client->on_ws_open_complete_context, ws_open_result); +} + +static void indicate_ws_open_complete_error_and_close(UWS_CLIENT_INSTANCE* uws_client, WS_OPEN_RESULT ws_open_result) +{ + (void)xio_close(uws_client->underlying_io, NULL, NULL); + indicate_ws_open_complete_error(uws_client, ws_open_result); +} + +static void indicate_ws_error(UWS_CLIENT_INSTANCE* uws_client, WS_ERROR error_code) +{ + uws_client->uws_state = UWS_STATE_ERROR; + uws_client->on_ws_error(uws_client->on_ws_error_context, error_code); +} + +static void indicate_ws_close_complete(UWS_CLIENT_INSTANCE* uws_client) +{ + uws_client->uws_state = UWS_STATE_CLOSED; + + /* Codes_SRS_UWS_CLIENT_01_496: [ If the close was initiated by the peer no `on_ws_close_complete` shall be called. ]*/ + if (uws_client->on_ws_close_complete != NULL) + { + /* Codes_SRS_UWS_CLIENT_01_491: [ When calling `on_ws_close_complete` callback, the `on_ws_close_complete_context` argument shall be passed to it. ]*/ + uws_client->on_ws_close_complete(uws_client->on_ws_close_complete_context); + } +} + +// This callback usage needs to be either verified and commented or integrated into +// the state machine. +static void unchecked_on_send_complete(void* context, IO_SEND_RESULT send_result) +{ + (void)context; + (void)send_result; +} + +static int send_close_frame(UWS_CLIENT_INSTANCE* uws_client, unsigned int close_error_code) +{ + unsigned char* close_frame; + unsigned char close_frame_payload[2]; + size_t close_frame_length; + int result; + BUFFER_HANDLE close_frame_buffer; + + close_frame_payload[0] = (unsigned char)(close_error_code >> 8); + close_frame_payload[1] = (unsigned char)(close_error_code & 0xFF); + + /* Codes_SRS_UWS_CLIENT_01_140: [ To avoid confusing network intermediaries (such as intercepting proxies) and for security reasons that are further discussed in Section 10.3, a client MUST mask all frames that it sends to the server (see Section 5.3 for further details). ]*/ + close_frame_buffer = uws_frame_encoder_encode(WS_CLOSE_FRAME, close_frame_payload, sizeof(close_frame_payload), true, true, 0); + if (close_frame_buffer == NULL) + { + LogError("Encoding of CLOSE failed."); + result = __FAILURE__; + } + else + { + close_frame = BUFFER_u_char(close_frame_buffer); + close_frame_length = BUFFER_length(close_frame_buffer); + + /* Codes_SRS_UWS_CLIENT_01_471: [ The callback `on_underlying_io_close_sent` shall be passed as argument to `xio_send`. ]*/ + if (xio_send(uws_client->underlying_io, close_frame, close_frame_length, unchecked_on_send_complete, NULL) != 0) + { + LogError("Sending CLOSE frame failed."); + result = __FAILURE__; + } + else + { + result = 0; + } + + BUFFER_delete(close_frame_buffer); + } + + return result; +} + +static void indicate_ws_error_and_close(UWS_CLIENT_INSTANCE* uws_client, WS_ERROR error_code, unsigned int close_error_code) +{ + uws_client->uws_state = UWS_STATE_ERROR; + + (void)send_close_frame(uws_client, close_error_code); + + uws_client->on_ws_error(uws_client->on_ws_error_context, error_code); +} + +static void on_underlying_io_open_complete(void* context, IO_OPEN_RESULT open_result) +{ + UWS_CLIENT_HANDLE uws_client = (UWS_CLIENT_HANDLE)context; + /* Codes_SRS_UWS_CLIENT_01_401: [ If `on_underlying_io_open_complete` is called with a NULL context, `on_underlying_io_open_complete` shall do nothing. ]*/ + if (uws_client == NULL) + { + LogError("NULL context"); + } + else + { + switch (uws_client->uws_state) + { + default: + case UWS_STATE_WAITING_FOR_UPGRADE_RESPONSE: + /* Codes_SRS_UWS_CLIENT_01_407: [ When `on_underlying_io_open_complete` is called when the uws instance has send the upgrade request but it is waiting for the response, an error shall be reported to the user by calling the `on_ws_open_complete` with `WS_OPEN_ERROR_MULTIPLE_UNDERLYING_IO_OPEN_EVENTS`. ]*/ + LogError("underlying on_io_open_complete was called again after upgrade request was sent."); + indicate_ws_open_complete_error_and_close(uws_client, WS_OPEN_ERROR_MULTIPLE_UNDERLYING_IO_OPEN_EVENTS); + break; + case UWS_STATE_OPENING_UNDERLYING_IO: + switch (open_result) + { + default: + case IO_OPEN_ERROR: + /* Codes_SRS_UWS_CLIENT_01_369: [ When `on_underlying_io_open_complete` is called with `IO_OPEN_ERROR` while uws is OPENING (`uws_client_open_async` was called), uws shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_UNDERLYING_IO_OPEN_FAILED`. ]*/ + indicate_ws_open_complete_error(uws_client, WS_OPEN_ERROR_UNDERLYING_IO_OPEN_FAILED); + break; + + case IO_OPEN_CANCELLED: + /* Codes_SRS_UWS_CLIENT_01_402: [ When `on_underlying_io_open_complete` is called with `IO_OPEN_CANCELLED` while uws is OPENING (`uws_client_open_async` was called), uws shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_UNDERLYING_IO_OPEN_CANCELLED`. ]*/ + indicate_ws_open_complete_error(uws_client, WS_OPEN_ERROR_UNDERLYING_IO_OPEN_CANCELLED); + break; + + case IO_OPEN_OK: + { + int upgrade_request_length; + char* upgrade_request; + size_t i; + unsigned char nonce[16]; + STRING_HANDLE base64_nonce; + + /* Codes_SRS_UWS_CLIENT_01_089: [ The value of this header field MUST be a nonce consisting of a randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]). ]*/ + /* Codes_SRS_UWS_CLIENT_01_090: [ The nonce MUST be selected randomly for each connection. ]*/ + for (i = 0; i < sizeof(nonce); i++) + { + nonce[i] = (unsigned char)gb_rand(); + } + + /* Codes_SRS_UWS_CLIENT_01_497: [ The nonce needed for the upgrade request shall be Base64 encoded with `Base64_Encode_Bytes`. ]*/ + base64_nonce = Base64_Encode_Bytes(nonce, sizeof(nonce)); + if (base64_nonce == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_498: [ If Base64 encoding the nonce for the upgrade request fails, then the uws client shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_BASE64_ENCODE_FAILED`. ]*/ + LogError("Cannot construct the WebSocket upgrade request"); + indicate_ws_open_complete_error(uws_client, WS_OPEN_ERROR_BASE64_ENCODE_FAILED); + } + else + { + /* Codes_SRS_UWS_CLIENT_01_371: [ When `on_underlying_io_open_complete` is called with `IO_OPEN_OK` while uws is OPENING (`uws_client_open_async` was called), uws shall prepare the WebSockets upgrade request. ]*/ + /* Codes_SRS_UWS_CLIENT_01_081: [ The handshake consists of an HTTP Upgrade request, along with a list of required and optional header fields. ]*/ + /* Codes_SRS_UWS_CLIENT_01_082: [ The handshake MUST be a valid HTTP request as specified by [RFC2616]. ]*/ + /* Codes_SRS_UWS_CLIENT_01_083: [ The method of the request MUST be GET, and the HTTP version MUST be at least 1.1. ]*/ + /* Codes_SRS_UWS_CLIENT_01_084: [ The "Request-URI" part of the request MUST match the /resource name/ defined in Section 3 (a relative URI) or be an absolute http/https URI that, when parsed, has a /resource name/, /host/, and /port/ that match the corresponding ws/wss URI. ]*/ + /* Codes_SRS_UWS_CLIENT_01_085: [ The request MUST contain a |Host| header field whose value contains /host/ plus optionally ":" followed by /port/ (when not using the default port). ]*/ + /* Codes_SRS_UWS_CLIENT_01_086: [ The request MUST contain an |Upgrade| header field whose value MUST include the "websocket" keyword. ]*/ + /* Codes_SRS_UWS_CLIENT_01_087: [ The request MUST contain a |Connection| header field whose value MUST include the "Upgrade" token. ]*/ + /* Codes_SRS_UWS_CLIENT_01_088: [ The request MUST include a header field with the name |Sec-WebSocket-Key|. ]*/ + /* Codes_SRS_UWS_CLIENT_01_094: [ The request MUST include a header field with the name |Sec-WebSocket-Version|. ]*/ + /* Codes_SRS_UWS_CLIENT_01_095: [ The value of this header field MUST be 13. ]*/ + /* Codes_SRS_UWS_CLIENT_01_096: [ The request MAY include a header field with the name |Sec-WebSocket-Protocol|. ]*/ + /* Codes_SRS_UWS_CLIENT_01_100: [ The request MAY include a header field with the name |Sec-WebSocket-Extensions|. ]*/ + const char upgrade_request_format[] = "GET %s HTTP/1.1\r\n" + "Host: %s:%d\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: %s\r\n" + "Sec-WebSocket-Protocol: %s\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"; + const char* base64_nonce_chars = STRING_c_str(base64_nonce); + + upgrade_request_length = (int)(strlen(upgrade_request_format) + strlen(uws_client->resource_name)+strlen(uws_client->hostname) + strlen(base64_nonce_chars) + strlen(uws_client->protocols[0].protocol)+5); + if (upgrade_request_length < 0) + { + /* Codes_SRS_UWS_CLIENT_01_408: [ If constructing of the WebSocket upgrade request fails, uws shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_CONSTRUCTING_UPGRADE_REQUEST`. ]*/ + LogError("Cannot construct the WebSocket upgrade request"); + indicate_ws_open_complete_error_and_close(uws_client, WS_OPEN_ERROR_CONSTRUCTING_UPGRADE_REQUEST); + } + else + { + upgrade_request = (char*)malloc(upgrade_request_length + 1); + if (upgrade_request == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_406: [ If not enough memory can be allocated to construct the WebSocket upgrade request, uws shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_NOT_ENOUGH_MEMORY`. ]*/ + LogError("Cannot allocate memory for the WebSocket upgrade request"); + indicate_ws_open_complete_error_and_close(uws_client, WS_OPEN_ERROR_NOT_ENOUGH_MEMORY); + } + else + { + + upgrade_request_length = sprintf(upgrade_request, upgrade_request_format, + uws_client->resource_name, + uws_client->hostname, + uws_client->port, + base64_nonce_chars, + uws_client->protocols[0].protocol); + + /* No need to have any send complete here, as we are monitoring the received bytes */ + /* Codes_SRS_UWS_CLIENT_01_372: [ Once prepared the WebSocket upgrade request shall be sent by calling `xio_send`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_080: [ Once a connection to the server has been established (including a connection via a proxy or over a TLS-encrypted tunnel), the client MUST send an opening handshake to the server. ]*/ + if (xio_send(uws_client->underlying_io, upgrade_request, upgrade_request_length, unchecked_on_send_complete, NULL) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_373: [ If `xio_send` fails then uws shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_CANNOT_SEND_UPGRADE_REQUEST`. ]*/ + LogError("Cannot send upgrade request"); + indicate_ws_open_complete_error_and_close(uws_client, WS_OPEN_ERROR_CANNOT_SEND_UPGRADE_REQUEST); + } + else + { + /* Codes_SRS_UWS_CLIENT_01_102: [ Once the client's opening handshake has been sent, the client MUST wait for a response from the server before sending any further data. ]*/ + uws_client->uws_state = UWS_STATE_WAITING_FOR_UPGRADE_RESPONSE; + } + + free(upgrade_request); + } + } + + STRING_delete(base64_nonce); + } + + break; + } + } + } + } +} + +static void consume_stream_buffer_bytes(UWS_CLIENT_INSTANCE* uws_client, size_t consumed_bytes) +{ + if (consumed_bytes < uws_client->stream_buffer_count) + { + (void)memmove(uws_client->stream_buffer, uws_client->stream_buffer + consumed_bytes, uws_client->stream_buffer_count - consumed_bytes); + } + + uws_client->stream_buffer_count -= consumed_bytes; +} + +static void on_underlying_io_close_complete(void* context) +{ + if (context == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_477: [ When `on_underlying_io_close_complete` is called with a NULL context, it shall do nothing. ]*/ + LogError("NULL context for on_underlying_io_close_complete"); + } + else + { + UWS_CLIENT_HANDLE uws_client = (UWS_CLIENT_HANDLE)context; + if (uws_client->uws_state == UWS_STATE_CLOSING_UNDERLYING_IO) + { + /* Codes_SRS_UWS_CLIENT_01_475: [ When `on_underlying_io_close_complete` is called while closing the underlying IO a subsequent `uws_client_open_async` shall succeed. ]*/ + indicate_ws_close_complete(uws_client); + uws_client->uws_state = UWS_STATE_CLOSED; + } + } +} + +static void on_underlying_io_close_sent(void* context, IO_SEND_RESULT io_send_result) +{ + if (context == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_489: [ When `on_underlying_io_close_sent` is called with NULL context, it shall do nothing. ] */ + LogError("NULL context in "); + } + else + { + UWS_CLIENT_INSTANCE* uws_client = (UWS_CLIENT_HANDLE)context; + + switch (io_send_result) + { + case IO_SEND_OK: + case IO_SEND_CANCELLED: + if (uws_client->uws_state == UWS_STATE_CLOSING_SENDING_CLOSE) + { + uws_client->uws_state = UWS_STATE_CLOSING_UNDERLYING_IO; + + /* Codes_SRS_UWS_CLIENT_01_490: [ When `on_underlying_io_close_sent` is called while the uws client is CLOSING, `on_underlying_io_close_sent` shall close the underlying IO by calling `xio_close`. ]*/ + if (xio_close(uws_client->underlying_io, on_underlying_io_close_complete, uws_client) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_496: [ If the close was initiated by the peer no `on_ws_close_complete` shall be called. ]*/ + indicate_ws_close_complete(uws_client); + } + } + + case IO_SEND_ERROR: + break; + } + } +} + +/*the following function does the same as sscanf(pos2, "%d", &sec)*/ +/*this function only exists because some of platforms do not have sscanf. */ +static int ParseStringToDecimal(const char *src, int* dst) +{ + int result; + char* next; + + (*dst) = (int)strtol(src, &next, 0); + if ((src == next) || ((((*dst) == INT_MAX) || ((*dst) == INT_MIN)) && (errno != 0))) + { + result = __FAILURE__; + } + else + { + result = 0; + } + + return result; +} + +/*the following function does the same as sscanf(buf, "HTTP/%*d.%*d %d %*[^\r\n]", &ret) */ +/*this function only exists because some of platforms do not have sscanf. This is not a full implementation; it only works with well-defined HTTP response. */ +static int ParseHttpResponse(const char* src, int* dst) +{ + int result; + static const char HTTPPrefix[] = "HTTP/"; + bool fail; + const char* runPrefix; + + if ((src == NULL) || (dst == NULL)) + { + result = __FAILURE__; + } + else + { + fail = false; + runPrefix = HTTPPrefix; + + while ((*runPrefix) != '\0') + { + if ((*runPrefix) != (*src)) + { + fail = true; + break; + } + src++; + runPrefix++; + } + + if (!fail) + { + while ((*src) != '.') + { + if ((*src) == '\0') + { + fail = true; + break; + } + src++; + } + } + + if (!fail) + { + while ((*src) != ' ') + { + if ((*src) == '\0') + { + fail = true; + break; + } + src++; + } + } + + if (fail) + { + result = __FAILURE__; + } + else + { + if (ParseStringToDecimal(src, dst) != 0) + { + result = __FAILURE__; + } + else + { + result = 0; + } + } + } + + return result; +} + +static int process_frame_fragment(UWS_CLIENT_INSTANCE *uws_client, size_t length, size_t needed_bytes) +{ + int result; + unsigned char *new_fragment_bytes = (unsigned char *)realloc(uws_client->fragment_buffer, uws_client->fragment_buffer_count + length); + if (new_fragment_bytes == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_379: [ If allocating memory for accumulating the bytes fails, uws shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_NOT_ENOUGH_MEMORY`. ]*/ + LogError("Cannot allocate memory for received data"); + indicate_ws_error(uws_client, WS_ERROR_NOT_ENOUGH_MEMORY); + result = __FAILURE__; + } + else + { + uws_client->fragment_buffer = new_fragment_bytes; + (void)memcpy(uws_client->fragment_buffer + uws_client->fragment_buffer_count, uws_client->stream_buffer + needed_bytes - length, length); + uws_client->fragment_buffer_count += length; + result = 0; + } + + return result; +} + +static void on_underlying_io_bytes_received(void* context, const unsigned char* buffer, size_t size) +{ + /* Codes_SRS_UWS_CLIENT_01_415: [ If called with a NULL `context` argument, `on_underlying_io_bytes_received` shall do nothing. ]*/ + if (context != NULL) + { + UWS_CLIENT_HANDLE uws_client = (UWS_CLIENT_HANDLE)context; + + if ((buffer == NULL) || + (size == 0)) + { + /* Codes_SRS_UWS_CLIENT_01_416: [ If called with NULL `buffer` or zero `size` and the state of the iws is OPENING, uws shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_INVALID_BYTES_RECEIVED_ARGUMENTS`. ]*/ + indicate_ws_open_complete_error_and_close(uws_client, WS_OPEN_ERROR_INVALID_BYTES_RECEIVED_ARGUMENTS); + } + else + { + unsigned char decode_stream = 1; + + switch (uws_client->uws_state) + { + default: + case UWS_STATE_CLOSED: + decode_stream = 0; + break; + + case UWS_STATE_OPENING_UNDERLYING_IO: + /* Codes_SRS_UWS_CLIENT_01_417: [ When `on_underlying_io_bytes_received` is called while OPENING but before the `on_underlying_io_open_complete` has been called, uws shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_BYTES_RECEIVED_BEFORE_UNDERLYING_OPEN`. ]*/ + indicate_ws_open_complete_error_and_close(uws_client, WS_OPEN_ERROR_BYTES_RECEIVED_BEFORE_UNDERLYING_OPEN); + decode_stream = 0; + break; + + case UWS_STATE_WAITING_FOR_UPGRADE_RESPONSE: + { + /* Codes_SRS_UWS_CLIENT_01_378: [ When `on_underlying_io_bytes_received` is called while the uws is OPENING, the received bytes shall be accumulated in order to attempt parsing the WebSocket Upgrade response. ]*/ + unsigned char* new_received_bytes = (unsigned char*)realloc(uws_client->stream_buffer, uws_client->stream_buffer_count + size + 1); + if (new_received_bytes == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_379: [ If allocating memory for accumulating the bytes fails, uws shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_NOT_ENOUGH_MEMORY`. ]*/ + indicate_ws_open_complete_error_and_close(uws_client, WS_OPEN_ERROR_NOT_ENOUGH_MEMORY); + decode_stream = 0; + } + else + { + uws_client->stream_buffer = new_received_bytes; + (void)memcpy(uws_client->stream_buffer + uws_client->stream_buffer_count, buffer, size); + uws_client->stream_buffer_count += size; + + decode_stream = 1; + } + + break; + } + + case UWS_STATE_OPEN: + case UWS_STATE_CLOSING_WAITING_FOR_CLOSE: + { + /* Codes_SRS_UWS_CLIENT_01_385: [ If the state of the uws instance is OPEN, the received bytes shall be used for decoding WebSocket frames. ]*/ + unsigned char* new_received_bytes = (unsigned char*)realloc(uws_client->stream_buffer, uws_client->stream_buffer_count + size + 1); + if (new_received_bytes == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_418: [ If allocating memory for the bytes accumulated for decoding WebSocket frames fails, an error shall be indicated by calling the `on_ws_error` callback with `WS_ERROR_NOT_ENOUGH_MEMORY`. ]*/ + LogError("Cannot allocate memory for received data"); + indicate_ws_error(uws_client, WS_ERROR_NOT_ENOUGH_MEMORY); + + decode_stream = 0; + } + else + { + uws_client->stream_buffer = new_received_bytes; + (void)memcpy(uws_client->stream_buffer + uws_client->stream_buffer_count, buffer, size); + uws_client->stream_buffer_count += size; + + decode_stream = 1; + } + + break; + } + } + + while (decode_stream) + { + decode_stream = 0; + + switch (uws_client->uws_state) + { + default: + case UWS_STATE_CLOSED: + break; + + case UWS_STATE_OPENING_UNDERLYING_IO: + /* Codes_SRS_UWS_CLIENT_01_417: [ When `on_underlying_io_bytes_received` is called while OPENING but before the `on_underlying_io_open_complete` has been called, uws shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_BYTES_RECEIVED_BEFORE_UNDERLYING_OPEN`. ]*/ + indicate_ws_open_complete_error_and_close(uws_client, WS_OPEN_ERROR_BYTES_RECEIVED_BEFORE_UNDERLYING_OPEN); + break; + + case UWS_STATE_WAITING_FOR_UPGRADE_RESPONSE: + { + const char* request_end_ptr; + + /* Make sure it is zero terminated */ + uws_client->stream_buffer[uws_client->stream_buffer_count] = '\0'; + + /* Codes_SRS_UWS_CLIENT_01_380: [ If an WebSocket Upgrade request can be parsed from the accumulated bytes, the status shall be read from the WebSocket upgrade response. ]*/ + /* Codes_SRS_UWS_CLIENT_01_381: [ If the status is 101, uws shall be considered OPEN and this shall be indicated by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `IO_OPEN_OK`. ]*/ + if ((uws_client->stream_buffer_count >= 4) && + ((request_end_ptr = strstr((const char*)uws_client->stream_buffer, "\r\n\r\n")) != NULL)) + { + int status_code; + + /* This part should really be done with the HTTPAPI, but that has to be done as a separate step + as the HTTPAPI has to expose somehow the underlying IO and currently this would be a too big of a change. */ + + /* Codes_SRS_UWS_CLIENT_01_382: [ If a negative status is decoded from the WebSocket upgrade request, an error shall be indicated by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_BAD_RESPONSE_STATUS`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_478: [ A Status-Line with a 101 response code as per RFC 2616 [RFC2616]. ]*/ + if (ParseHttpResponse((const char*)uws_client->stream_buffer, &status_code) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_383: [ If the WebSocket upgrade request cannot be decoded an error shall be indicated by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_BAD_UPGRADE_RESPONSE`. ]*/ + LogError("Cannot decode HTTP response"); + indicate_ws_open_complete_error_and_close(uws_client, WS_OPEN_ERROR_BAD_UPGRADE_RESPONSE); + } + else if (status_code != 101) + { + /* Codes_SRS_UWS_CLIENT_01_382: [ If a negative status is decoded from the WebSocket upgrade request, an error shall be indicated by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_BAD_RESPONSE_STATUS`. ]*/ + LogError("Bad status (%d) received in WebSocket Upgrade response", status_code); + indicate_ws_open_complete_error_and_close(uws_client, WS_OPEN_ERROR_BAD_RESPONSE_STATUS); + } + else + { + /* Codes_SRS_UWS_CLIENT_01_384: [ Any extra bytes that are left unconsumed after decoding a succesfull WebSocket upgrade response shall be used for decoding WebSocket frames ]*/ + consume_stream_buffer_bytes(uws_client, request_end_ptr - (char*)uws_client->stream_buffer + 4); + + /* Codes_SRS_UWS_CLIENT_01_381: [ If the status is 101, uws shall be considered OPEN and this shall be indicated by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `IO_OPEN_OK`. ]*/ + uws_client->uws_state = UWS_STATE_OPEN; + + /* Codes_SRS_UWS_CLIENT_01_065: [ When the client is to _Establish a WebSocket Connection_ given a set of (/host/, /port/, /resource name/, and /secure/ flag), along with a list of /protocols/ and /extensions/ to be used, and an /origin/ in the case of web browsers, it MUST open a connection, send an opening handshake, and read the server's handshake in response. ]*/ + /* Codes_SRS_UWS_CLIENT_01_115: [ If the server's response is validated as provided for above, it is said that _The WebSocket Connection is Established_ and that the WebSocket Connection is in the OPEN state. ]*/ + uws_client->on_ws_open_complete(uws_client->on_ws_open_complete_context, WS_OPEN_OK); + + decode_stream = 1; + } + } + + break; + } + + case UWS_STATE_OPEN: + case UWS_STATE_CLOSING_WAITING_FOR_CLOSE: + { + size_t needed_bytes = 2; + size_t length; + + /* Codes_SRS_UWS_CLIENT_01_277: [ To receive WebSocket data, an endpoint listens on the underlying network connection. ]*/ + /* Codes_SRS_UWS_CLIENT_01_278: [ Incoming data MUST be parsed as WebSocket frames as defined in Section 5.2. ]*/ + if (uws_client->stream_buffer_count >= needed_bytes) + { + unsigned char has_error = 0; + + /* Codes_SRS_UWS_CLIENT_01_160: [ Defines whether the "Payload data" is masked. ]*/ + if ((uws_client->stream_buffer[1] & 0x80) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_144: [ A client MUST close a connection if it detects a masked frame. ]*/ + /* Codes_SRS_UWS_CLIENT_01_145: [ In this case, it MAY use the status code 1002 (protocol error) as defined in Section 7.4.1. (These rules might be relaxed in a future specification.) ]*/ + LogError("Masked frame detected by WebSocket client"); + indicate_ws_error_and_close(uws_client, WS_ERROR_BAD_FRAME_RECEIVED, 1002); + } + + /* Codes_SRS_UWS_CLIENT_01_163: [ The length of the "Payload data", in bytes: ]*/ + /* Codes_SRS_UWS_CLIENT_01_164: [ if 0-125, that is the payload length. ]*/ + length = uws_client->stream_buffer[1]; + + if (length == 126) + { + /* Codes_SRS_UWS_CLIENT_01_165: [ If 126, the following 2 bytes interpreted as a 16-bit unsigned integer are the payload length. ]*/ + needed_bytes += 2; + if (uws_client->stream_buffer_count >= needed_bytes) + { + /* Codes_SRS_UWS_CLIENT_01_167: [ Multibyte length quantities are expressed in network byte order. ]*/ + length = ((size_t)(uws_client->stream_buffer[2]) << 8) + (size_t)uws_client->stream_buffer[3]; + + if (length < 126) + { + /* Codes_SRS_UWS_CLIENT_01_168: [ Note that in all cases, the minimal number of bytes MUST be used to encode the length, for example, the length of a 124-byte-long string can't be encoded as the sequence 126, 0, 124. ]*/ + LogError("Bad frame: received a %u length on the 16 bit length", (unsigned int)length); + + /* Codes_SRS_UWS_CLIENT_01_419: [ If there is an error decoding the WebSocket frame, an error shall be indicated by calling the `on_ws_error` callback with `WS_ERROR_BAD_FRAME_RECEIVED`. ]*/ + indicate_ws_error(uws_client, WS_ERROR_BAD_FRAME_RECEIVED); + has_error = 1; + } + else + { + needed_bytes += (size_t)length; + } + } + } + else if (length == 127) + { + /* Codes_SRS_UWS_CLIENT_01_166: [ If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the most significant bit MUST be 0) are the payload length. ]*/ + needed_bytes += 8; + if (uws_client->stream_buffer_count >= needed_bytes) + { + if ((uws_client->stream_buffer[2] & 0x80) != 0) + { + LogError("Bad frame: received a 64 bit length frame with the highest bit set"); + + /* Codes_SRS_UWS_CLIENT_01_419: [ If there is an error decoding the WebSocket frame, an error shall be indicated by calling the `on_ws_error` callback with `WS_ERROR_BAD_FRAME_RECEIVED`. ]*/ + indicate_ws_error(uws_client, WS_ERROR_BAD_FRAME_RECEIVED); + has_error = 1; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_167: [ Multibyte length quantities are expressed in network byte order. ]*/ + length = (size_t)(((uint64_t)(uws_client->stream_buffer[2]) << 56) + + (((uint64_t)uws_client->stream_buffer[3]) << 48) + + (((uint64_t)uws_client->stream_buffer[4]) << 40) + + (((uint64_t)uws_client->stream_buffer[5]) << 32) + + (((uint64_t)uws_client->stream_buffer[6]) << 24) + + (((uint64_t)uws_client->stream_buffer[7]) << 16) + + (((uint64_t)uws_client->stream_buffer[8]) << 8) + + (uint64_t)(uws_client->stream_buffer[9])); + + if (length < 65536) + { + /* Codes_SRS_UWS_CLIENT_01_168: [ Note that in all cases, the minimal number of bytes MUST be used to encode the length, for example, the length of a 124-byte-long string can't be encoded as the sequence 126, 0, 124. ]*/ + LogError("Bad frame: received a %u length on the 64 bit length", (unsigned int)length); + + /* Codes_SRS_UWS_CLIENT_01_419: [ If there is an error decoding the WebSocket frame, an error shall be indicated by calling the `on_ws_error` callback with `WS_ERROR_BAD_FRAME_RECEIVED`. ]*/ + indicate_ws_error(uws_client, WS_ERROR_BAD_FRAME_RECEIVED); + has_error = 1; + } + else + { + needed_bytes += length; + } + } + } + } + else + { + needed_bytes += length; + } + + if ((has_error == 0) && + (uws_client->stream_buffer_count >= needed_bytes)) + { + unsigned char opcode = uws_client->stream_buffer[0] & 0xF; + + /* Codes_SRS_UWS_CLIENT_01_147: [ Indicates that this is the final fragment in a message. ]*/ + bool is_final = (uws_client->stream_buffer[0] & 0x80) != 0; + + switch (opcode) + { + default: + break; + /* Codes_SRS_UWS_CLIENT_01_152: [* * %x0 denotes a continuation frame *]*/ + case (unsigned char)WS_CONTINUATION_FRAME: + { + /* Codes_SRS_UWS_CLIENT_01_213: [ A fragmented message consists of a single frame with the FIN bit clear and an opcode other than 0, followed by zero or more frames with the FIN bit clear and the opcode set to 0, and terminated by a single frame with the FIN bit set and an opcode of 0. ]*/ + /* Codes_SRS_UWS_CLIENT_01_216: [ Message fragments MUST be delivered to the recipient in the order sent by the sender. ]*/ + /* Codes_SRS_UWS_CLIENT_01_219: [ A sender MAY create fragments of any size for non-control messages. ]*/ + if (process_frame_fragment(uws_client, length, needed_bytes) != 0) + { + break; + } + + if (is_final) + { + /* Codes_SRS_UWS_CLIENT_01_225: [ As a consequence of these rules, all fragments of a message are of the same type, as set by the first fragment's opcode. ]*/ + if (uws_client->fragmented_frame_type == WS_FRAME_TYPE_UNKNOWN) + { + LogError("Continuation fragment received without initial fragment specifying frame data type"); + indicate_ws_error(uws_client, WS_ERROR_BAD_FRAME_RECEIVED); + decode_stream = 1; + break; + } + uws_client->on_ws_frame_received(uws_client->on_ws_frame_received_context, uws_client->fragmented_frame_type, uws_client->fragment_buffer, uws_client->fragment_buffer_count); + uws_client->fragment_buffer_count = 0; + uws_client->fragmented_frame_type = WS_FRAME_TYPE_UNKNOWN; + } + decode_stream = 1; + break; + } + /* Codes_SRS_UWS_CLIENT_01_153: [ * %x1 denotes a text frame ]*/ + /* Codes_SRS_UWS_CLIENT_01_258: [** Currently defined opcodes for data frames include 0x1 (Text), 0x2 (Binary). ]*/ + case (unsigned char)WS_TEXT_FRAME: + { + /* Codes_SRS_UWS_CLIENT_01_386: [ When a WebSocket data frame is decoded succesfully it shall be indicated via the callback `on_ws_frame_received`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_169: [ The payload length is the length of the "Extension data" + the length of the "Application data". ]*/ + /* Codes_SRS_UWS_CLIENT_01_173: [ The "Payload data" is defined as "Extension data" concatenated with "Application data". ]*/ + /* Codes_SRS_UWS_CLIENT_01_280: [ Upon receiving a data frame (Section 5.6), the endpoint MUST note the /type/ of the data as defined by the opcode (frame-opcode) from Section 5.2. ]*/ + /* Codes_SRS_UWS_CLIENT_01_281: [ The "Application data" from this frame is defined as the /data/ of the message. ]*/ + /* Codes_SRS_UWS_CLIENT_01_282: [ If the frame comprises an unfragmented message (Section 5.4), it is said that _A WebSocket Message Has Been Received_ with type /type/ and data /data/. ]*/ + if (is_final) + { + uws_client->on_ws_frame_received(uws_client->on_ws_frame_received_context, WS_FRAME_TYPE_TEXT, uws_client->stream_buffer + needed_bytes - length, length); + } + else + { + /* Codes_SRS_UWS_CLIENT_01_217: [ The fragments of one message MUST NOT be interleaved between the fragments of another message unless an extension has been negotiated that can interpret the interleaving. ]*/ + if (uws_client->fragmented_frame_type != WS_FRAME_TYPE_UNKNOWN) + { + LogError("Fragmented frame received interleaved between the fragments of another message"); + indicate_ws_error(uws_client, WS_ERROR_BAD_FRAME_RECEIVED); + decode_stream = 1; + break; + } + /* Codes_SRS_UWS_CLIENT_01_213: [ A fragmented message consists of a single frame with the FIN bit clear and an opcode other than 0, followed by zero or more frames with the FIN bit clear and the opcode set to 0, and terminated by a single frame with the FIN bit set and an opcode of 0. ]*/ + /* Codes_SRS_UWS_CLIENT_01_216: [ Message fragments MUST be delivered to the recipient in the order sent by the sender. ]*/ + /* Codes_SRS_UWS_CLIENT_01_219: [ A sender MAY create fragments of any size for non-control messages. ]*/ + if (process_frame_fragment(uws_client, length, needed_bytes) != 0) + { + break; + } + + /* Codes_SRS_UWS_CLIENT_01_225: [ As a consequence of these rules, all fragments of a message are of the same type, as set by the first fragment's opcode. ]*/ + /* Codes_SRS_UWS_CLIENT_01_226: [ Since control frames cannot be fragmented, the type for all fragments in a message MUST be either text, binary, or one of the reserved opcodes. ]*/ + uws_client->fragmented_frame_type = WS_FRAME_TYPE_TEXT; + } + decode_stream = 1; + break; + } + + /* Codes_SRS_UWS_CLIENT_01_154: [ * %x2 denotes a binary frame ]*/ + case (unsigned char)WS_BINARY_FRAME: + { + /* Codes_SRS_UWS_CLIENT_01_386: [ When a WebSocket data frame is decoded succesfully it shall be indicated via the callback `on_ws_frame_received`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_169: [ The payload length is the length of the "Extension data" + the length of the "Application data". ]*/ + /* Codes_SRS_UWS_CLIENT_01_173: [ The "Payload data" is defined as "Extension data" concatenated with "Application data". ]*/ + /* Codes_SRS_UWS_CLIENT_01_264: [ The "Payload data" is arbitrary binary data whose interpretation is solely up to the application layer. ]*/ + /* Codes_SRS_UWS_CLIENT_01_280: [ Upon receiving a data frame (Section 5.6), the endpoint MUST note the /type/ of the data as defined by the opcode (frame-opcode) from Section 5.2. ]*/ + /* Codes_SRS_UWS_CLIENT_01_281: [ The "Application data" from this frame is defined as the /data/ of the message. ]*/ + /* Codes_SRS_UWS_CLIENT_01_282: [ If the frame comprises an unfragmented message (Section 5.4), it is said that _A WebSocket Message Has Been Received_ with type /type/ and data /data/. ]*/ + if (is_final) + { + uws_client->on_ws_frame_received(uws_client->on_ws_frame_received_context, WS_FRAME_TYPE_BINARY, uws_client->stream_buffer + needed_bytes - length, length); + } + else + { + /* Codes_SRS_UWS_CLIENT_01_217: [ The fragments of one message MUST NOT be interleaved between the fragments of another message unless an extension has been negotiated that can interpret the interleaving. ]*/ + if (uws_client->fragmented_frame_type != WS_FRAME_TYPE_UNKNOWN) + { + LogError("Fragmented frame received interleaved between the fragments of another message"); + indicate_ws_error(uws_client, WS_ERROR_BAD_FRAME_RECEIVED); + decode_stream = 1; + break; + } + /* Codes_SRS_UWS_CLIENT_01_213: [ A fragmented message consists of a single frame with the FIN bit clear and an opcode other than 0, followed by zero or more frames with the FIN bit clear and the opcode set to 0, and terminated by a single frame with the FIN bit set and an opcode of 0. ]*/ + /* Codes_SRS_UWS_CLIENT_01_216: [ Message fragments MUST be delivered to the recipient in the order sent by the sender. ]*/ + /* Codes_SRS_UWS_CLIENT_01_219: [ A sender MAY create fragments of any size for non-control messages. ]*/ + if (process_frame_fragment(uws_client, length, needed_bytes) != 0) + { + break; + } + + /* Codes_SRS_UWS_CLIENT_01_225: [ As a consequence of these rules, all fragments of a message are of the same type, as set by the first fragment's opcode. ]*/ + /* Codes_SRS_UWS_CLIENT_01_226: [ Since control frames cannot be fragmented, the type for all fragments in a message MUST be either text, binary, or one of the reserved opcodes. ]*/ + uws_client->fragmented_frame_type = WS_FRAME_TYPE_BINARY; + } + decode_stream = 1; + break; + } + + /* Codes_SRS_UWS_CLIENT_01_156: [ * %x8 denotes a connection close ]*/ + /* Codes_SRS_UWS_CLIENT_01_234: [ The Close frame contains an opcode of 0x8. ]*/ + /* Codes_SRS_UWS_CLIENT_01_214: [ Control frames (see Section 5.5) MAY be injected in the middle of a fragmented message. ]*/ + case (unsigned char)WS_CLOSE_FRAME: + { + uint16_t close_code; + uint16_t* close_code_ptr; + const unsigned char* data_ptr = uws_client->stream_buffer + needed_bytes - length; + const unsigned char* extra_data_ptr; + size_t extra_data_length; + unsigned char* close_frame_bytes; + size_t close_frame_length; + bool utf8_error = false; + + /* Codes_SRS_UWS_CLIENT_01_215: [ Control frames themselves MUST NOT be fragmented. ]*/ + if (!is_final) + { + LogError("Fragmented control frame received."); + indicate_ws_error(uws_client, WS_ERROR_BAD_FRAME_RECEIVED); + break; + } + + /* Codes_SRS_UWS_CLIENT_01_235: [ The Close frame MAY contain a body (the "Application data" portion of the frame) that indicates a reason for closing, such as an endpoint shutting down, an endpoint having received a frame too large, or an endpoint having received a frame that does not conform to the format expected by the endpoint. ]*/ + if (length >= 2) + { + /* Codes_SRS_UWS_CLIENT_01_236: [ If there is a body, the first two bytes of the body MUST be a 2-byte unsigned integer (in network byte order) representing a status code with value /code/ defined in Section 7.4. ]*/ + close_code = (data_ptr[0] << 8) + data_ptr[1]; + + /* Codes_SRS_UWS_CLIENT_01_461: [ The argument `close_code` shall be set to point to the code extracted from the CLOSE frame. ]*/ + close_code_ptr = &close_code; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_462: [ If no code can be extracted then `close_code` shall be NULL. ]*/ + close_code_ptr = NULL; + } + + if (length > 2) + { + /* Codes_SRS_UWS_CLIENT_01_463: [ The extra bytes (besides the close code) shall be passed to the `on_ws_peer_closed` callback by using `extra_data` and `extra_data_length`. ]*/ + extra_data_ptr = data_ptr + 2; + extra_data_length = length - 2; + + /* Codes_SRS_UWS_CLIENT_01_238: [ As the data is not guaranteed to be human readable, clients MUST NOT show it to end users. ]*/ + /* Codes_SRS_UWS_CLIENT_01_237: [ Following the 2-byte integer, the body MAY contain UTF-8-encoded data with value /reason/, the interpretation of which is not defined by this specification. ]*/ + if (utf8_checker_is_valid_utf8(extra_data_ptr, extra_data_length) != true) + { + LogError("Reason in CLOSE frame is not UTF-8."); + extra_data_ptr = NULL; + extra_data_length = 0; + utf8_error = true; + } + } + else + { + extra_data_ptr = NULL; + extra_data_length = 0; + } + + if (utf8_error) + { + uws_client->uws_state = UWS_STATE_CLOSING_UNDERLYING_IO; + if (xio_close(uws_client->underlying_io, on_underlying_io_close_complete, uws_client) != 0) + { + LogError("Could not close underlying IO"); + indicate_ws_error(uws_client, WS_ERROR_CANNOT_CLOSE_UNDERLYING_IO); + uws_client->uws_state = UWS_STATE_CLOSED; + } + } + else + { + BUFFER_HANDLE close_frame_buffer; + + if (uws_client->uws_state == UWS_STATE_CLOSING_WAITING_FOR_CLOSE) + { + uws_client->uws_state = UWS_STATE_CLOSING_UNDERLYING_IO; + if (xio_close(uws_client->underlying_io, on_underlying_io_close_complete, uws_client) != 0) + { + indicate_ws_close_complete(uws_client); + uws_client->uws_state = UWS_STATE_CLOSED; + } + } + else + { + /* Codes_SRS_UWS_CLIENT_01_296: [ Upon either sending or receiving a Close control frame, it is said that _The WebSocket Closing Handshake is Started_ and that the WebSocket connection is in the CLOSING state. ]*/ + /* Codes_SRS_UWS_CLIENT_01_240: [ The application MUST NOT send any more data frames after sending a Close frame. ]*/ + uws_client->uws_state = UWS_STATE_CLOSING_SENDING_CLOSE; + } + + /* Codes_SRS_UWS_CLIENT_01_241: [ If an endpoint receives a Close frame and did not previously send a Close frame, the endpoint MUST send a Close frame in response. ]*/ + /* Codes_SRS_UWS_CLIENT_01_242: [ It SHOULD do so as soon as practical. ]*/ + /* Codes_SRS_UWS_CLIENT_01_239: [ Close frames sent from client to server must be masked as per Section 5.3. ]*/ + /* Codes_SRS_UWS_CLIENT_01_140: [ To avoid confusing network intermediaries (such as intercepting proxies) and for security reasons that are further discussed in Section 10.3, a client MUST mask all frames that it sends to the server (see Section 5.3 for further details). ]*/ + close_frame_buffer = uws_frame_encoder_encode(WS_CLOSE_FRAME, NULL, 0, true, true, 0); + if (close_frame_buffer == NULL) + { + LogError("Cannot encode the response CLOSE frame"); + + /* Codes_SRS_UWS_CLIENT_01_288: [ To _Close the WebSocket Connection_, an endpoint closes the underlying TCP connection. ]*/ + /* Codes_SRS_UWS_CLIENT_01_290: [ An endpoint MAY close the connection via any means available when necessary, such as when under attack. ]*/ + uws_client->uws_state = UWS_STATE_CLOSING_UNDERLYING_IO; + if (xio_close(uws_client->underlying_io, on_underlying_io_close_complete, uws_client) != 0) + { + indicate_ws_error(uws_client, WS_ERROR_CANNOT_CLOSE_UNDERLYING_IO); + uws_client->uws_state = UWS_STATE_CLOSED; + } + } + else + { + close_frame_bytes = BUFFER_u_char(close_frame_buffer); + close_frame_length = BUFFER_length(close_frame_buffer); + if (xio_send(uws_client->underlying_io, close_frame_bytes, close_frame_length, on_underlying_io_close_sent, uws_client) != 0) + { + LogError("Cannot send the response CLOSE frame"); + + /* Codes_SRS_UWS_CLIENT_01_288: [ To _Close the WebSocket Connection_, an endpoint closes the underlying TCP connection. ]*/ + /* Codes_SRS_UWS_CLIENT_01_290: [ An endpoint MAY close the connection via any means available when necessary, such as when under attack. ]*/ + uws_client->uws_state = UWS_STATE_CLOSING_UNDERLYING_IO; + if (xio_close(uws_client->underlying_io, on_underlying_io_close_complete, uws_client) != 0) + { + indicate_ws_error(uws_client, WS_ERROR_CANNOT_CLOSE_UNDERLYING_IO); + uws_client->uws_state = UWS_STATE_CLOSED; + } + } + + BUFFER_delete(close_frame_buffer); + } + } + + /* Codes_SRS_UWS_CLIENT_01_460: [ When a CLOSE frame is received the callback `on_ws_peer_closed` passed to `uws_client_open_async` shall be called, while passing to it the argument `on_ws_peer_closed_context`. ]*/ + uws_client->on_ws_peer_closed(uws_client->on_ws_peer_closed_context, close_code_ptr, extra_data_ptr, extra_data_length); + + break; + } + + /* Codes_SRS_UWS_CLIENT_01_157: [ * %x9 denotes a ping ]*/ + /* Codes_SRS_UWS_CLIENT_01_247: [ The Ping frame contains an opcode of 0x9. ]*/ + /* Codes_SRS_UWS_CLIENT_01_251: [ An endpoint MAY send a Ping frame any time after the connection is established and before the connection is closed. ]*/ + /* Codes_SRS_UWS_CLIENT_01_214: [ Control frames (see Section 5.5) MAY be injected in the middle of a fragmented message. ]*/ + case (unsigned char)WS_PING_FRAME: + { + /* Codes_SRS_UWS_CLIENT_01_249: [ Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in response ]*/ + /* Codes_SRS_UWS_CLIENT_01_250: [ It SHOULD respond with Pong frame as soon as is practical. ]*/ + unsigned char* pong_frame; + size_t pong_frame_length; + BUFFER_HANDLE pong_frame_buffer; + + /* Codes_SRS_UWS_CLIENT_01_215: [ Control frames themselves MUST NOT be fragmented. ]*/ + if (!is_final) + { + LogError("Fragmented control frame received."); + indicate_ws_error(uws_client, WS_ERROR_BAD_FRAME_RECEIVED); + break; + } + + /* Codes_SRS_UWS_CLIENT_01_140: [ To avoid confusing network intermediaries (such as intercepting proxies) and for security reasons that are further discussed in Section 10.3, a client MUST mask all frames that it sends to the server (see Section 5.3 for further details). ]*/ + pong_frame_buffer = uws_frame_encoder_encode(WS_PONG_FRAME, uws_client->stream_buffer + needed_bytes - length, length, true, true, 0); + if (pong_frame_buffer == NULL) + { + LogError("Encoding of PONG failed."); + } + else + { + /* Codes_SRS_UWS_CLIENT_01_248: [ A Ping frame MAY include "Application data". ]*/ + pong_frame = BUFFER_u_char(pong_frame_buffer); + pong_frame_length = BUFFER_length(pong_frame_buffer); + if (xio_send(uws_client->underlying_io, pong_frame, pong_frame_length, unchecked_on_send_complete, NULL) != 0) + { + LogError("Sending PONG frame failed."); + } + + BUFFER_delete(pong_frame_buffer); + } + + break; + } + /* Codes_SRS_UWS_CLIENT_01_252: [ The Pong frame contains an opcode of 0xA. ]*/ + case (unsigned char)WS_PONG_FRAME: + break; + } + + consume_stream_buffer_bytes(uws_client, needed_bytes); + } + } + + break; + } + } + } + } + } +} + +static void on_underlying_io_error(void* context) +{ + UWS_CLIENT_HANDLE uws_client = (UWS_CLIENT_HANDLE)context; + + switch (uws_client->uws_state) + { + default: + break; + + case UWS_STATE_CLOSING_WAITING_FOR_CLOSE: + case UWS_STATE_CLOSING_SENDING_CLOSE: + case UWS_STATE_CLOSING_UNDERLYING_IO: + /* Codes_SRS_UWS_CLIENT_01_500: [ The callback `on_ws_close_complete` shall be called, while passing the `on_ws_close_complete_context` argument to it. ]*/ + /* Codes_SRS_UWS_CLIENT_01_377: [ When `on_underlying_io_error` is called while the uws instance is CLOSING the underlying IO shall be closed by calling `xio_close`. ]*/ + (void)xio_close(uws_client->underlying_io, NULL, NULL); + + /* Codes_SRS_UWS_CLIENT_01_499: [ If the CLOSE was due to the peer closing, the callback `on_ws_close_complete` shall not be called. ]*/ + indicate_ws_close_complete(uws_client); + break; + + case UWS_STATE_OPENING_UNDERLYING_IO: + case UWS_STATE_WAITING_FOR_UPGRADE_RESPONSE: + /* Codes_SRS_UWS_CLIENT_01_375: [ When `on_underlying_io_error` is called while uws is OPENING, uws shall report that the open failed by calling the `on_ws_open_complete` callback passed to `uws_client_open_async` with `WS_OPEN_ERROR_UNDERLYING_IO_ERROR`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_077: [ If this fails (e.g., the server's certificate could not be verified), then the client MUST _Fail the WebSocket Connection_ and abort the connection. ]*/ + indicate_ws_open_complete_error_and_close(uws_client, WS_OPEN_ERROR_UNDERLYING_IO_ERROR); + break; + + case UWS_STATE_OPEN: + /* Codes_SRS_UWS_CLIENT_01_376: [ When `on_underlying_io_error` is called while the uws instance is OPEN, an error shall be reported to the user by calling the `on_ws_error` callback that was passed to `uws_client_open_async` with the argument `WS_ERROR_UNDERLYING_IO_ERROR`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_318: [ Servers MAY close the WebSocket connection whenever desired. ]*/ + /* Codes_SRS_UWS_CLIENT_01_269: [ If at any point the state of the WebSocket connection changes, the endpoint MUST abort the following steps. ]*/ + indicate_ws_error(uws_client, WS_ERROR_UNDERLYING_IO_ERROR); + break; + } +} + +int uws_client_open_async(UWS_CLIENT_HANDLE uws_client, ON_WS_OPEN_COMPLETE on_ws_open_complete, void* on_ws_open_complete_context, ON_WS_FRAME_RECEIVED on_ws_frame_received, void* on_ws_frame_received_context, ON_WS_PEER_CLOSED on_ws_peer_closed, void* on_ws_peer_closed_context, ON_WS_ERROR on_ws_error, void* on_ws_error_context) +{ + int result; + + /* Codes_SRS_UWS_CLIENT_01_393: [ The context arguments for the callbacks shall be allowed to be NULL. ]*/ + if ((uws_client == NULL) || + (on_ws_open_complete == NULL) || + (on_ws_frame_received == NULL) || + (on_ws_peer_closed == NULL) || + (on_ws_error == NULL)) + { + /* Codes_SRS_UWS_CLIENT_01_027: [ If `uws_client`, `on_ws_open_complete`, `on_ws_frame_received`, `on_ws_peer_closed` or `on_ws_error` is NULL, `uws_client_open_async` shall fail and return a non-zero value. ]*/ + LogError("Invalid arguments: uws=%p, on_ws_open_complete=%p, on_ws_frame_received=%p, on_ws_error=%p", + uws_client, on_ws_open_complete, on_ws_frame_received, on_ws_error); + result = __FAILURE__; + } + else + { + if (uws_client->uws_state != UWS_STATE_CLOSED) + { + /* Codes_SRS_UWS_CLIENT_01_400: [ `uws_client_open_async` while CLOSING shall fail and return a non-zero value. ]*/ + /* Codes_SRS_UWS_CLIENT_01_394: [ `uws_client_open_async` while the uws instance is already OPEN or OPENING shall fail and return a non-zero value. ]*/ + LogError("Invalid uWS state while trying to open: %d", (int)uws_client->uws_state); + result = __FAILURE__; + } + else + { + uws_client->uws_state = UWS_STATE_OPENING_UNDERLYING_IO; + + uws_client->stream_buffer_count = 0; + uws_client->fragment_buffer_count = 0; + uws_client->fragmented_frame_type = WS_FRAME_TYPE_UNKNOWN; + + uws_client->on_ws_open_complete = on_ws_open_complete; + uws_client->on_ws_open_complete_context = on_ws_open_complete_context; + uws_client->on_ws_frame_received = on_ws_frame_received; + uws_client->on_ws_frame_received_context = on_ws_frame_received_context; + uws_client->on_ws_peer_closed = on_ws_peer_closed; + uws_client->on_ws_peer_closed_context = on_ws_peer_closed_context; + uws_client->on_ws_error = on_ws_error; + uws_client->on_ws_error_context = on_ws_error_context; + + /* Codes_SRS_UWS_CLIENT_01_025: [ `uws_client_open_async` shall open the underlying IO by calling `xio_open` and providing the IO handle created in `uws_client_create` as argument. ]*/ + /* Codes_SRS_UWS_CLIENT_01_367: [ The callbacks `on_underlying_io_open_complete`, `on_underlying_io_bytes_received` and `on_underlying_io_error` shall be passed as arguments to `xio_open`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_061: [ To _Establish a WebSocket Connection_, a client opens a connection and sends a handshake as defined in this section. ]*/ + if (xio_open(uws_client->underlying_io, on_underlying_io_open_complete, uws_client, on_underlying_io_bytes_received, uws_client, on_underlying_io_error, uws_client) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_028: [ If opening the underlying IO fails then `uws_client_open_async` shall fail and return a non-zero value. ]*/ + /* Codes_SRS_UWS_CLIENT_01_075: [ If the connection could not be opened, either because a direct connection failed or because any proxy used returned an error, then the client MUST _Fail the WebSocket Connection_ and abort the connection attempt. ]*/ + LogError("Opening the underlying IO failed"); + uws_client->uws_state = UWS_STATE_CLOSED; + result = __FAILURE__; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_026: [ On success, `uws_client_open_async` shall return 0. ]*/ + result = 0; + } + } + } + + return result; +} + +static int complete_send_frame(WS_PENDING_SEND* ws_pending_send, LIST_ITEM_HANDLE pending_send_frame_item, WS_SEND_FRAME_RESULT ws_send_frame_result) +{ + int result; + UWS_CLIENT_INSTANCE* uws_client = ws_pending_send->uws_client; + + /* Codes_SRS_UWS_CLIENT_01_432: [ The indicated sent frame shall be removed from the list by calling `singlylinkedlist_remove`. ]*/ + if (singlylinkedlist_remove(uws_client->pending_sends, pending_send_frame_item) != 0) + { + LogError("Failed removing item from list"); + result = __FAILURE__; + } + else + { + if (ws_pending_send->on_ws_send_frame_complete != NULL) + { + /* Codes_SRS_UWS_CLIENT_01_037: [ When indicating pending send frames as cancelled the callback context passed to the `on_ws_send_frame_complete` callback shall be the context given to `uws_client_send_frame_async`. ]*/ + ws_pending_send->on_ws_send_frame_complete(ws_pending_send->context, ws_send_frame_result); + } + + /* Codes_SRS_UWS_CLIENT_01_434: [ The memory associated with the sent frame shall be freed. ]*/ + free(ws_pending_send); + + result = 0; + } + + return result; +} + +/* Codes_SRS_UWS_CLIENT_01_029: [ `uws_client_close_async` shall close the uws instance connection if an open action is either pending or has completed successfully (if the IO is open). ]*/ +/* Codes_SRS_UWS_CLIENT_01_317: [ Clients SHOULD NOT close the WebSocket connection arbitrarily. ]*/ +int uws_client_close_async(UWS_CLIENT_HANDLE uws_client, ON_WS_CLOSE_COMPLETE on_ws_close_complete, void* on_ws_close_complete_context) +{ + int result; + + /* Codes_SRS_UWS_CLIENT_01_397: [ The `on_ws_close_complete` argument shall be allowed to be NULL, in which case no callback shall be called when the close is complete. ]*/ + /* Codes_SRS_UWS_CLIENT_01_398: [ `on_ws_close_complete_context` shall also be allows to be NULL. ]*/ + if (uws_client == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_030: [ if `uws_client` is NULL, `uws_client_close_async` shall return a non-zero value. ]*/ + LogError("NULL uWS handle."); + result = __FAILURE__; + } + else + { + if ((uws_client->uws_state == UWS_STATE_CLOSED) || + (uws_client->uws_state == UWS_STATE_CLOSING_SENDING_CLOSE) || + (uws_client->uws_state == UWS_STATE_CLOSING_WAITING_FOR_CLOSE) || + (uws_client->uws_state == UWS_STATE_CLOSING_UNDERLYING_IO)) + { + /* Codes_SRS_UWS_CLIENT_01_032: [ `uws_client_close_async` when no open action has been issued shall fail and return a non-zero value. ]*/ + LogError("close has been called when already CLOSED"); + result = __FAILURE__; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_399: [ `on_ws_close_complete` and `on_ws_close_complete_context` shall be saved and the callback `on_ws_close_complete` shall be triggered when the close is complete. ]*/ + uws_client->on_ws_close_complete = on_ws_close_complete; + uws_client->on_ws_close_complete_context = on_ws_close_complete_context; + + uws_client->uws_state = UWS_STATE_CLOSING_UNDERLYING_IO; + + /* Codes_SRS_UWS_CLIENT_01_031: [ `uws_client_close_async` shall close the connection by calling `xio_close` while passing as argument the IO handle created in `uws_client_create`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_368: [ The callback `on_underlying_io_close` shall be passed as argument to `xio_close`. ]*/ + if (xio_close(uws_client->underlying_io, (on_ws_close_complete == NULL) ? NULL : on_underlying_io_close_complete, (on_ws_close_complete == NULL) ? NULL : uws_client) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_395: [ If `xio_close` fails, `uws_client_close_async` shall fail and return a non-zero value. ]*/ + LogError("Closing the underlying IO failed."); + result = __FAILURE__; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_034: [ `uws_client_close_async` shall obtain all the pending send frames by repetitively querying for the head of the pending IO list and freeing that head item. ]*/ + LIST_ITEM_HANDLE first_pending_send; + + /* Codes_SRS_UWS_CLIENT_01_035: [ Obtaining the head of the pending send frames list shall be done by calling `singlylinkedlist_get_head_item`. ]*/ + while ((first_pending_send = singlylinkedlist_get_head_item(uws_client->pending_sends)) != NULL) + { + WS_PENDING_SEND* ws_pending_send = (WS_PENDING_SEND*)singlylinkedlist_item_get_value(first_pending_send); + + /* Codes_SRS_UWS_CLIENT_01_036: [ For each pending send frame the send complete callback shall be called with `UWS_SEND_FRAME_CANCELLED`. ]*/ + complete_send_frame(ws_pending_send, first_pending_send, WS_SEND_FRAME_CANCELLED); + } + + /* Codes_SRS_UWS_CLIENT_01_396: [ On success `uws_client_close_async` shall return 0. ]*/ + result = 0; + } + } + } + + return result; +} + +/* Codes_SRS_UWS_CLIENT_01_317: [ Clients SHOULD NOT close the WebSocket connection arbitrarily. ]*/ +int uws_client_close_handshake_async(UWS_CLIENT_HANDLE uws_client, uint16_t close_code, const char* close_reason, ON_WS_CLOSE_COMPLETE on_ws_close_complete, void* on_ws_close_complete_context) +{ + int result; + + if (uws_client == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_467: [ if `uws_client` is NULL, `uws_client_close_handshake_async` shall return a non-zero value. ]*/ + LogError("NULL uws_client"); + result = __FAILURE__; + } + else + { + if ((uws_client->uws_state == UWS_STATE_CLOSED) || + /* Codes_SRS_UWS_CLIENT_01_474: [ `uws_client_close_handshake_async` when already CLOSING shall fail and return a non-zero value. ]*/ + (uws_client->uws_state == UWS_STATE_CLOSING_WAITING_FOR_CLOSE) || + (uws_client->uws_state == UWS_STATE_CLOSING_SENDING_CLOSE) || + (uws_client->uws_state == UWS_STATE_CLOSING_UNDERLYING_IO)) + { + /* Codes_SRS_UWS_CLIENT_01_473: [ `uws_client_close_handshake_async` when no open action has been issued shall fail and return a non-zero value. ]*/ + LogError("uws_client_close_handshake_async has been called when already CLOSED"); + result = __FAILURE__; + } + else + { + (void)close_reason; + + /* Codes_SRS_UWS_CLIENT_01_468: [ `on_ws_close_complete` and `on_ws_close_complete_context` shall be saved and the callback `on_ws_close_complete` shall be triggered when the close is complete. ]*/ + /* Codes_SRS_UWS_CLIENT_01_469: [ The `on_ws_close_complete` argument shall be allowed to be NULL, in which case no callback shall be called when the close is complete. ]*/ + /* Codes_SRS_UWS_CLIENT_01_470: [ `on_ws_close_complete_context` shall also be allowed to be NULL. ]*/ + uws_client->on_ws_close_complete = on_ws_close_complete; + uws_client->on_ws_close_complete_context = on_ws_close_complete_context; + + uws_client->uws_state = UWS_STATE_CLOSING_WAITING_FOR_CLOSE; + + /* Codes_SRS_UWS_CLIENT_01_465: [ `uws_client_close_handshake_async` shall initiate the close handshake by sending a close frame to the peer. ]*/ + if (send_close_frame(uws_client, close_code) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_472: [ If `xio_send` fails, `uws_client_close_handshake_async` shall fail and return a non-zero value. ]*/ + LogError("Sending CLOSE frame failed"); + result = __FAILURE__; + } + else + { + LIST_ITEM_HANDLE first_pending_send; + + while ((first_pending_send = singlylinkedlist_get_head_item(uws_client->pending_sends)) != NULL) + { + WS_PENDING_SEND* ws_pending_send = (WS_PENDING_SEND*)singlylinkedlist_item_get_value(first_pending_send); + + complete_send_frame(ws_pending_send, first_pending_send, WS_SEND_FRAME_CANCELLED); + } + + /* Codes_SRS_UWS_CLIENT_01_466: [ On success `uws_client_close_handshake_async` shall return 0. ]*/ + result = 0; + } + } + } + + return result; +} + +static void on_underlying_io_send_complete(void* context, IO_SEND_RESULT send_result) +{ + if (context == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_435: [ When `on_underlying_io_send_complete` is called with a NULL `context`, it shall do nothing. ]*/ + LogError("on_underlying_io_send_complete called with NULL context"); + } + else + { + LIST_ITEM_HANDLE ws_pending_send_list_item = (LIST_ITEM_HANDLE)context; + WS_PENDING_SEND* ws_pending_send = (WS_PENDING_SEND*)singlylinkedlist_item_get_value(ws_pending_send_list_item); + UWS_CLIENT_HANDLE uws_client = ws_pending_send->uws_client; + WS_SEND_FRAME_RESULT ws_send_frame_result; + + switch (send_result) + { + /* Codes_SRS_UWS_CLIENT_01_436: [ When `on_underlying_io_send_complete` is called with any other error code, the send shall be indicated to the uws user by calling `on_ws_send_frame_complete` with `WS_SEND_FRAME_ERROR`. ]*/ + default: + case IO_SEND_ERROR: + /* Codes_SRS_UWS_CLIENT_01_390: [ When `on_underlying_io_send_complete` is called with `IO_SEND_ERROR` as a result of sending a WebSocket frame to the underlying IO, the send shall be indicated to the uws user by calling `on_ws_send_frame_complete` with `WS_SEND_FRAME_ERROR`. ]*/ + ws_send_frame_result = WS_SEND_FRAME_ERROR; + break; + + case IO_SEND_OK: + /* Codes_SRS_UWS_CLIENT_01_389: [ When `on_underlying_io_send_complete` is called with `IO_SEND_OK` as a result of sending a WebSocket frame to the underlying IO, the send shall be indicated to the uws user by calling `on_ws_send_frame_complete` with `WS_SEND_FRAME_OK`. ]*/ + ws_send_frame_result = WS_SEND_FRAME_OK; + break; + + case IO_SEND_CANCELLED: + /* Codes_SRS_UWS_CLIENT_01_391: [ When `on_underlying_io_send_complete` is called with `IO_SEND_CANCELLED` as a result of sending a WebSocket frame to the underlying IO, the send shall be indicated to the uws user by calling `on_ws_send_frame_complete` with `WS_SEND_FRAME_CANCELLED`. ]*/ + ws_send_frame_result = WS_SEND_FRAME_CANCELLED; + break; + } + + if (complete_send_frame(ws_pending_send, ws_pending_send_list_item, ws_send_frame_result) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_433: [ If `singlylinkedlist_remove` fails an error shall be indicated by calling the `on_ws_error` callback with `WS_ERROR_CANNOT_REMOVE_SENT_ITEM_FROM_LIST`. ]*/ + indicate_ws_error(uws_client, WS_ERROR_CANNOT_REMOVE_SENT_ITEM_FROM_LIST); + } + } +} + +static bool find_list_node(LIST_ITEM_HANDLE list_item, const void* match_context) +{ + return list_item == (LIST_ITEM_HANDLE)match_context; +} + +int uws_client_send_frame_async(UWS_CLIENT_HANDLE uws_client, unsigned char frame_type, const unsigned char* buffer, size_t size, bool is_final, ON_WS_SEND_FRAME_COMPLETE on_ws_send_frame_complete, void* on_ws_send_frame_complete_context) +{ + int result; + + if (uws_client == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_044: [ If any the arguments `uws_client` is NULL, `uws_client_send_frame_async` shall fail and return a non-zero value. ]*/ + LogError("NULL uws handle."); + result = __FAILURE__; + } + else if ((buffer == NULL) && + (size > 0)) + { + /* Codes_SRS_UWS_CLIENT_01_045: [ If `size` is non-zero and `buffer` is NULL then `uws_client_send_frame_async` shall fail and return a non-zero value. ]*/ + LogError("NULL buffer with %u size.", (unsigned int)size); + result = __FAILURE__; + } + /* Codes_SRS_UWS_CLIENT_01_146: [ A data frame MAY be transmitted by either the client or the server at any time after opening handshake completion and before that endpoint has sent a Close frame (Section 5.5.1). ]*/ + /* Codes_SRS_UWS_CLIENT_01_268: [ The endpoint MUST ensure the WebSocket connection is in the OPEN state ]*/ + else if (uws_client->uws_state != UWS_STATE_OPEN) + { + /* Codes_SRS_UWS_CLIENT_01_043: [ If the uws instance is not OPEN (open has not been called or is still in progress) then `uws_client_send_frame_async` shall fail and return a non-zero value. ]*/ + LogError("uws not in OPEN state."); + result = __FAILURE__; + } + else + { + WS_PENDING_SEND* ws_pending_send = (WS_PENDING_SEND*)malloc(sizeof(WS_PENDING_SEND)); + if (ws_pending_send == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_047: [ If allocating memory for the newly queued item fails, `uws_client_send_frame_async` shall fail and return a non-zero value. ]*/ + LogError("Cannot allocate memory for frame to be sent."); + result = __FAILURE__; + } + else + { + BUFFER_HANDLE non_control_frame_buffer; + + /* Codes_SRS_UWS_CLIENT_01_425: [ Encoding shall be done by calling `uws_frame_encoder_encode` and passing to it the `buffer` and `size` argument for payload, the `is_final` flag and setting `is_masked` to true. ]*/ + /* Codes_SRS_UWS_CLIENT_01_270: [ An endpoint MUST encapsulate the /data/ in a WebSocket frame as defined in Section 5.2. ]*/ + /* Codes_SRS_UWS_CLIENT_01_272: [ The opcode (frame-opcode) of the first frame containing the data MUST be set to the appropriate value from Section 5.2 for data that is to be interpreted by the recipient as text or binary data. ]*/ + /* Codes_SRS_UWS_CLIENT_01_274: [ If the data is being sent by the client, the frame(s) MUST be masked as defined in Section 5.3. ]*/ + non_control_frame_buffer = uws_frame_encoder_encode((WS_FRAME_TYPE)frame_type, buffer, size, true, is_final, 0); + if (non_control_frame_buffer == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_426: [ If `uws_frame_encoder_encode` fails, `uws_client_send_frame_async` shall fail and return a non-zero value. ]*/ + LogError("Failed encoding WebSocket frame"); + free(ws_pending_send); + result = __FAILURE__; + } + else + { + const unsigned char* encoded_frame; + size_t encoded_frame_length; + LIST_ITEM_HANDLE new_pending_send_list_item; + + /* Codes_SRS_UWS_CLIENT_01_428: [ The encoded frame buffer memory shall be obtained by calling `BUFFER_u_char` on the encode buffer. ]*/ + encoded_frame = BUFFER_u_char(non_control_frame_buffer); + /* Codes_SRS_UWS_CLIENT_01_429: [ The encoded frame size shall be obtained by calling `BUFFER_length` on the encode buffer. ]*/ + encoded_frame_length = BUFFER_length(non_control_frame_buffer); + + /* Codes_SRS_UWS_CLIENT_01_038: [ `uws_client_send_frame_async` shall create and queue a structure that contains: ]*/ + /* Codes_SRS_UWS_CLIENT_01_050: [ The argument `on_ws_send_frame_complete` shall be optional, if NULL is passed by the caller then no send complete callback shall be triggered. ]*/ + /* Codes_SRS_UWS_CLIENT_01_040: [ - the send complete callback `on_ws_send_frame_complete` ]*/ + /* Codes_SRS_UWS_CLIENT_01_041: [ - the send complete callback context `on_ws_send_frame_complete_context` ]*/ + ws_pending_send->on_ws_send_frame_complete = on_ws_send_frame_complete; + ws_pending_send->context = on_ws_send_frame_complete_context; + ws_pending_send->uws_client = uws_client; + + /* Codes_SRS_UWS_CLIENT_01_048: [ Queueing shall be done by calling `singlylinkedlist_add`. ]*/ + new_pending_send_list_item = singlylinkedlist_add(uws_client->pending_sends, ws_pending_send); + if (new_pending_send_list_item == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_049: [ If `singlylinkedlist_add` fails, `uws_client_send_frame_async` shall fail and return a non-zero value. ]*/ + LogError("Could not allocate memory for pending frames"); + free(ws_pending_send); + result = __FAILURE__; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_431: [ Once encoded the frame shall be sent by using `xio_send` with the following arguments: ]*/ + /* Codes_SRS_UWS_CLIENT_01_053: [ - the io handle shall be the underlyiong IO handle created in `uws_client_create`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_054: [ - the `buffer` argument shall point to the complete websocket frame to be sent. ]*/ + /* Codes_SRS_UWS_CLIENT_01_055: [ - the `size` argument shall indicate the websocket frame length. ]*/ + /* Codes_SRS_UWS_CLIENT_01_056: [ - the `send_complete` callback shall be the `on_underlying_io_send_complete` function. ]*/ + /* Codes_SRS_UWS_CLIENT_01_057: [ - the `send_complete_context` argument shall identify the pending send. ]*/ + /* Codes_SRS_UWS_CLIENT_01_276: [ The frame(s) that have been formed MUST be transmitted over the underlying network connection. ]*/ + if (xio_send(uws_client->underlying_io, encoded_frame, encoded_frame_length, on_underlying_io_send_complete, new_pending_send_list_item) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_058: [ If `xio_send` fails, `uws_client_send_frame_async` shall fail and return a non-zero value. ]*/ + LogError("Could not send bytes through the underlying IO"); + + /* Codes_SRS_UWS_CLIENT_09_001: [ If `xio_send` fails and the message is still queued, it shall be de-queued and destroyed. ] */ + if (singlylinkedlist_find(uws_client->pending_sends, find_list_node, new_pending_send_list_item) != NULL) + { + // Guards against double free in case the underlying I/O invoked 'on_underlying_io_send_complete' within xio_send. + (void)singlylinkedlist_remove(uws_client->pending_sends, new_pending_send_list_item); + free(ws_pending_send); + } + + result = __FAILURE__; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_042: [ On success, `uws_client_send_frame_async` shall return 0. ]*/ + result = 0; + } + } + + BUFFER_delete(non_control_frame_buffer); + } + } + } + + return result; +} + +void uws_client_dowork(UWS_CLIENT_HANDLE uws_client) +{ + if (uws_client == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_059: [ If the `uws_client` argument is NULL, `uws_client_dowork` shall do nothing. ]*/ + LogError("NULL uws handle."); + } + else + { + /* Codes_SRS_UWS_CLIENT_01_060: [ If the IO is not yet open, `uws_client_dowork` shall do nothing. ]*/ + if (uws_client->uws_state != UWS_STATE_CLOSED) + { + /* Codes_SRS_UWS_CLIENT_01_430: [ `uws_client_dowork` shall call `xio_dowork` with the IO handle argument set to the underlying IO created in `uws_client_create`. ]*/ + xio_dowork(uws_client->underlying_io); + } + } +} + +int uws_client_set_option(UWS_CLIENT_HANDLE uws_client, const char* option_name, const void* value) +{ + int result; + + if ( + (uws_client == NULL) || + (option_name == NULL) + ) + { + /* Codes_SRS_UWS_CLIENT_01_440: [ If any of the arguments `uws_client` or `option_name` is NULL `uws_client_set_option` shall return a non-zero value. ]*/ + LogError("invalid parameter (NULL) passed to uws_client_set_option"); + result = __FAILURE__; + } + else + { + if (strcmp(UWS_CLIENT_OPTIONS, option_name) == 0) + { + /* Codes_SRS_UWS_CLIENT_01_510: [ If the option name is `uWSClientOptions` then `uws_client_set_option` shall call `OptionHandler_FeedOptions` and pass to it the underlying IO handle and the `value` argument. ]*/ + if (OptionHandler_FeedOptions((OPTIONHANDLER_HANDLE)value, uws_client->underlying_io) != OPTIONHANDLER_OK) + { + /* Codes_SRS_UWS_CLIENT_01_511: [ If `OptionHandler_FeedOptions` fails, `uws_client_set_option` shall fail and return a non-zero value. ]*/ + LogError("OptionHandler_FeedOptions failed"); + result = __FAILURE__; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_442: [ On success, `uws_client_set_option` shall return 0. ]*/ + result = 0; + } + } + else + { + /* Codes_SRS_UWS_CLIENT_01_441: [ Otherwise all options shall be passed as they are to the underlying IO by calling `xio_setoption`. ]*/ + if (xio_setoption(uws_client->underlying_io, option_name, value) != 0) + { + /* Codes_SRS_UWS_CLIENT_01_443: [ If `xio_setoption` fails, `uws_client_set_option` shall fail and return a non-zero value. ]*/ + LogError("xio_setoption failed."); + result = __FAILURE__; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_442: [ On success, `uws_client_set_option` shall return 0. ]*/ + result = 0; + } + } + } + + return result; +} + +static void* uws_client_clone_option(const char* name, const void* value) +{ + void* result; + + if ( + (name == NULL) || + (value == NULL) + ) + { + /* Codes_SRS_UWS_CLIENT_01_506: [ If `uws_client_clone_option` is called with NULL `name` or `value` it shall return NULL. ]*/ + LogError("invalid argument detected: const char* name=%p, const void* value=%p", name, value); + result = NULL; + } + else + { + if (strcmp(name, UWS_CLIENT_OPTIONS) == 0) + { + /* Codes_SRS_UWS_CLIENT_01_507: [ `uws_client_clone_option` called with `name` being `uWSClientOptions` shall return the same value. ]*/ + result = (void*)value; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_512: [ `uws_client_clone_option` called with any other option name than `uWSClientOptions` shall return NULL. ]*/ + LogError("unknown option: %s", name); + result = NULL; + } + } + + return result; +} + +static void uws_client_destroy_option(const char* name, const void* value) +{ + if ( + (name == NULL) || + (value == NULL) + ) + { + /* Codes_SRS_UWS_CLIENT_01_509: [ If `uws_client_destroy_option` is called with NULL `name` or `value` it shall do nothing. ]*/ + LogError("invalid argument detected: const char* name=%p, const void* value=%p", name, value); + } + else + { + if (strcmp(name, UWS_CLIENT_OPTIONS) == 0) + { + /* Codes_SRS_UWS_CLIENT_01_508: [ `uws_client_destroy_option` called with the option `name` being `uWSClientOptions` shall destroy the value by calling `OptionHandler_Destroy`. ]*/ + OptionHandler_Destroy((OPTIONHANDLER_HANDLE)value); + } + else + { + /* Codes_SRS_UWS_CLIENT_01_513: [ If `uws_client_destroy_option` is called with any other `name` it shall do nothing. ]*/ + LogError("unknown option: %s", name); + } + } +} + +OPTIONHANDLER_HANDLE uws_client_retrieve_options(UWS_CLIENT_HANDLE uws_client) +{ + OPTIONHANDLER_HANDLE result; + + if (uws_client == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_444: [ If parameter `uws_client` is `NULL` then `uws_client_retrieve_options` shall fail and return NULL. ]*/ + LogError("NULL uws handle."); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_445: [ `uws_client_retrieve_options` shall call `OptionHandler_Create` to produce an `OPTIONHANDLER_HANDLE` and on success return the new `OPTIONHANDLER_HANDLE` handle. ]*/ + result = OptionHandler_Create(uws_client_clone_option, uws_client_destroy_option, (pfSetOption)uws_client_set_option); + if (result == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_446: [ If `OptionHandler_Create` fails then `uws_client_retrieve_options` shall fail and return NULL. ]*/ + LogError("OptionHandler_Create failed"); + } + else + { + /* Codes_SRS_UWS_CLIENT_01_502: [ When calling `xio_retrieveoptions` the underlying IO handle shall be passed to it. ]*/ + OPTIONHANDLER_HANDLE underlying_io_options = xio_retrieveoptions(uws_client->underlying_io); + if (underlying_io_options == NULL) + { + /* Codes_SRS_UWS_CLIENT_01_503: [ If `xio_retrieveoptions` fails, `uws_client_retrieve_options` shall fail and return NULL. ]*/ + LogError("unable to concrete_io_retrieveoptions"); + OptionHandler_Destroy(result); + result = NULL; + } + else + { + /* Codes_SRS_UWS_CLIENT_01_501: [ `uws_client_retrieve_options` shall add to the option handler one option, whose name shall be `uWSClientOptions` and the value shall be queried by calling `xio_retrieveoptions`. ]*/ + /* Codes_SRS_UWS_CLIENT_01_504: [ Adding the option shall be done by calling `OptionHandler_AddOption`. ]*/ + if (OptionHandler_AddOption(result, UWS_CLIENT_OPTIONS, underlying_io_options) != OPTIONHANDLER_OK) + { + /* Codes_SRS_UWS_CLIENT_01_505: [ If `OptionHandler_AddOption` fails, `uws_client_retrieve_options` shall fail and return NULL. ]*/ + LogError("OptionHandler_AddOption failed"); + OptionHandler_Destroy(underlying_io_options); + OptionHandler_Destroy(result); + result = NULL; + } + } + } + + } + + return result; +}