summaryrefslogtreecommitdiffstats
path: root/contrib/restricted/aws/aws-c-http/source/websocket_bootstrap.c
diff options
context:
space:
mode:
authordakovalkov <[email protected]>2023-12-03 13:33:55 +0300
committerdakovalkov <[email protected]>2023-12-03 14:04:39 +0300
commit2a718325637e5302334b6d0a6430f63168f8dbb3 (patch)
tree64be81080b7df9ec1d86d053a0c394ae53fcf1fe /contrib/restricted/aws/aws-c-http/source/websocket_bootstrap.c
parente0d94a470142d95c3007e9c5d80380994940664a (diff)
Update contrib/libs/aws-sdk-cpp to 1.11.37
Diffstat (limited to 'contrib/restricted/aws/aws-c-http/source/websocket_bootstrap.c')
-rw-r--r--contrib/restricted/aws/aws-c-http/source/websocket_bootstrap.c866
1 files changed, 866 insertions, 0 deletions
diff --git a/contrib/restricted/aws/aws-c-http/source/websocket_bootstrap.c b/contrib/restricted/aws/aws-c-http/source/websocket_bootstrap.c
new file mode 100644
index 00000000000..b5225873059
--- /dev/null
+++ b/contrib/restricted/aws/aws-c-http/source/websocket_bootstrap.c
@@ -0,0 +1,866 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+
+#include <aws/cal/hash.h>
+#include <aws/common/encoding.h>
+#include <aws/common/logging.h>
+#include <aws/common/string.h>
+#include <aws/http/connection.h>
+#include <aws/http/private/http_impl.h>
+#include <aws/http/private/strutil.h>
+#include <aws/http/private/websocket_impl.h>
+#include <aws/http/request_response.h>
+#include <aws/http/status_code.h>
+#include <aws/io/uri.h>
+
+#include <inttypes.h>
+
+#ifdef _MSC_VER
+# pragma warning(disable : 4204) /* non-constant aggregate initializer */
+#endif
+
+/**
+ * Allow unit-tests to mock interactions with external systems.
+ */
+static const struct aws_websocket_client_bootstrap_system_vtable s_default_system_vtable = {
+ .aws_http_client_connect = aws_http_client_connect,
+ .aws_http_connection_release = aws_http_connection_release,
+ .aws_http_connection_close = aws_http_connection_close,
+ .aws_http_connection_get_channel = aws_http_connection_get_channel,
+ .aws_http_connection_make_request = aws_http_connection_make_request,
+ .aws_http_stream_activate = aws_http_stream_activate,
+ .aws_http_stream_release = aws_http_stream_release,
+ .aws_http_stream_get_connection = aws_http_stream_get_connection,
+ .aws_http_stream_update_window = aws_http_stream_update_window,
+ .aws_http_stream_get_incoming_response_status = aws_http_stream_get_incoming_response_status,
+ .aws_websocket_handler_new = aws_websocket_handler_new,
+};
+
+static const struct aws_websocket_client_bootstrap_system_vtable *s_system_vtable = &s_default_system_vtable;
+
+void aws_websocket_client_bootstrap_set_system_vtable(
+ const struct aws_websocket_client_bootstrap_system_vtable *system_vtable) {
+
+ s_system_vtable = system_vtable;
+}
+
+/**
+ * The websocket bootstrap brings a websocket connection into this world, and sees it out again.
+ * Spins up an HTTP client, performs the opening handshake (HTTP Upgrade request),
+ * creates the websocket handler, and inserts it into the channel.
+ * The bootstrap is responsible for firing the on_connection_setup and on_connection_shutdown callbacks.
+ */
+struct aws_websocket_client_bootstrap {
+ /* Settings copied in from aws_websocket_client_connection_options */
+ struct aws_allocator *alloc;
+ size_t initial_window_size;
+ bool manual_window_update;
+ void *user_data;
+ /* Setup callback will be set NULL once it's invoked.
+ * This is used to determine whether setup or shutdown should be invoked
+ * from the HTTP-shutdown callback. */
+ aws_websocket_on_connection_setup_fn *websocket_setup_callback;
+ aws_websocket_on_connection_shutdown_fn *websocket_shutdown_callback;
+ aws_websocket_on_incoming_frame_begin_fn *websocket_frame_begin_callback;
+ aws_websocket_on_incoming_frame_payload_fn *websocket_frame_payload_callback;
+ aws_websocket_on_incoming_frame_complete_fn *websocket_frame_complete_callback;
+
+ /* Handshake request data */
+ struct aws_http_message *handshake_request;
+
+ /* Given the "Sec-WebSocket-Key" from the request,
+ * this is what we expect the response's "Sec-WebSocket-Accept" to be */
+ struct aws_byte_buf expected_sec_websocket_accept;
+
+ /* Comma-separated values from the request's "Sec-WebSocket-Protocol" (or NULL if none) */
+ struct aws_string *expected_sec_websocket_protocols;
+
+ /* Handshake response data */
+ int response_status;
+ struct aws_http_headers *response_headers;
+ bool got_full_response_headers;
+ struct aws_byte_buf response_body;
+ bool got_full_response_body;
+
+ int setup_error_code;
+ struct aws_websocket *websocket;
+};
+
+static void s_ws_bootstrap_destroy(struct aws_websocket_client_bootstrap *ws_bootstrap);
+static int s_ws_bootstrap_calculate_sec_websocket_accept(
+ struct aws_byte_cursor sec_websocket_key,
+ struct aws_byte_buf *out_buf,
+ struct aws_allocator *alloc);
+static void s_ws_bootstrap_cancel_setup_due_to_err(
+ struct aws_websocket_client_bootstrap *ws_bootstrap,
+ struct aws_http_connection *http_connection,
+ int error_code);
+static void s_ws_bootstrap_on_http_setup(struct aws_http_connection *http_connection, int error_code, void *user_data);
+static void s_ws_bootstrap_on_http_shutdown(
+ struct aws_http_connection *http_connection,
+ int error_code,
+ void *user_data);
+static int s_ws_bootstrap_on_handshake_response_headers(
+ struct aws_http_stream *stream,
+ enum aws_http_header_block header_block,
+ const struct aws_http_header *header_array,
+ size_t num_headers,
+ void *user_data);
+static int s_ws_bootstrap_on_handshake_response_header_block_done(
+ struct aws_http_stream *stream,
+ enum aws_http_header_block header_block,
+ void *user_data);
+static int s_ws_bootstrap_on_handshake_response_body(
+ struct aws_http_stream *stream,
+ const struct aws_byte_cursor *data,
+ void *user_data);
+static void s_ws_bootstrap_on_stream_complete(struct aws_http_stream *stream, int error_code, void *user_data);
+
+int aws_websocket_client_connect(const struct aws_websocket_client_connection_options *options) {
+ aws_http_fatal_assert_library_initialized();
+ AWS_ASSERT(options);
+
+ /* Validate options */
+ struct aws_byte_cursor path;
+ aws_http_message_get_request_path(options->handshake_request, &path);
+ if (!options->allocator || !options->bootstrap || !options->socket_options || !options->host.len || !path.len ||
+ !options->on_connection_setup) {
+
+ AWS_LOGF_ERROR(AWS_LS_HTTP_WEBSOCKET_SETUP, "id=static: Missing required websocket connection options.");
+ return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
+ }
+
+ struct aws_byte_cursor method;
+ aws_http_message_get_request_method(options->handshake_request, &method);
+ if (aws_http_str_to_method(method) != AWS_HTTP_METHOD_GET) {
+
+ AWS_LOGF_ERROR(AWS_LS_HTTP_WEBSOCKET_SETUP, "id=static: Websocket request must have method be 'GET'.");
+ return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
+ }
+
+ if (!options->handshake_request) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=static: Invalid connection options, missing required request for websocket client handshake.");
+ return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
+ }
+
+ const struct aws_http_headers *request_headers = aws_http_message_get_headers(options->handshake_request);
+ struct aws_byte_cursor sec_websocket_key;
+ if (aws_http_headers_get(request_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Key"), &sec_websocket_key)) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=static: Websocket handshake request is missing required 'Sec-WebSocket-Key' header");
+ return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
+ }
+
+ /* Extensions are not currently supported */
+ if (aws_http_headers_has(request_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Extensions"))) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP, "id=static: 'Sec-WebSocket-Extensions' are not currently supported");
+ return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
+ }
+
+ /* Create bootstrap */
+ struct aws_websocket_client_bootstrap *ws_bootstrap =
+ aws_mem_calloc(options->allocator, 1, sizeof(struct aws_websocket_client_bootstrap));
+
+ ws_bootstrap->alloc = options->allocator;
+ ws_bootstrap->initial_window_size = options->initial_window_size;
+ ws_bootstrap->manual_window_update = options->manual_window_management;
+ ws_bootstrap->user_data = options->user_data;
+ ws_bootstrap->websocket_setup_callback = options->on_connection_setup;
+ ws_bootstrap->websocket_shutdown_callback = options->on_connection_shutdown;
+ ws_bootstrap->websocket_frame_begin_callback = options->on_incoming_frame_begin;
+ ws_bootstrap->websocket_frame_payload_callback = options->on_incoming_frame_payload;
+ ws_bootstrap->websocket_frame_complete_callback = options->on_incoming_frame_complete;
+ ws_bootstrap->handshake_request = aws_http_message_acquire(options->handshake_request);
+ ws_bootstrap->response_status = AWS_HTTP_STATUS_CODE_UNKNOWN;
+ ws_bootstrap->response_headers = aws_http_headers_new(ws_bootstrap->alloc);
+ aws_byte_buf_init(&ws_bootstrap->response_body, ws_bootstrap->alloc, 0);
+
+ if (s_ws_bootstrap_calculate_sec_websocket_accept(
+ sec_websocket_key, &ws_bootstrap->expected_sec_websocket_accept, ws_bootstrap->alloc)) {
+ goto error;
+ }
+
+ ws_bootstrap->expected_sec_websocket_protocols =
+ aws_http_headers_get_all(request_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Protocol"));
+
+ /* Initiate HTTP connection */
+ struct aws_http_client_connection_options http_options = AWS_HTTP_CLIENT_CONNECTION_OPTIONS_INIT;
+ http_options.allocator = ws_bootstrap->alloc;
+ http_options.bootstrap = options->bootstrap;
+ http_options.host_name = options->host;
+ http_options.socket_options = options->socket_options;
+ http_options.tls_options = options->tls_options;
+ http_options.proxy_options = options->proxy_options;
+
+ if (options->manual_window_management) {
+ http_options.manual_window_management = true;
+
+ /* Give HTTP handler enough window to comfortably receive the handshake response.
+ *
+ * If the upgrade is unsuccessful, the HTTP window will shrink as the response body is received.
+ * In this case, we'll keep incrementing the window back to its original size so data keeps arriving.
+ *
+ * If the upgrade is successful, then the websocket handler is installed, and
+ * the HTTP handler will take over its own window management. */
+ http_options.initial_window_size = 1024;
+ }
+
+ http_options.user_data = ws_bootstrap;
+ http_options.on_setup = s_ws_bootstrap_on_http_setup;
+ http_options.on_shutdown = s_ws_bootstrap_on_http_shutdown;
+ http_options.requested_event_loop = options->requested_event_loop;
+
+ /* Infer port, if not explicitly specified in URI */
+ http_options.port = options->port;
+ if (!http_options.port) {
+ http_options.port = options->tls_options ? 443 : 80;
+ }
+
+ if (s_system_vtable->aws_http_client_connect(&http_options)) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=static: Websocket failed to initiate HTTP connection, error %d (%s)",
+ aws_last_error(),
+ aws_error_name(aws_last_error()));
+ goto error;
+ }
+
+ /* Success! (so far) */
+ AWS_LOGF_TRACE(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Websocket setup begun, connecting to " PRInSTR ":%" PRIu16 PRInSTR,
+ (void *)ws_bootstrap,
+ AWS_BYTE_CURSOR_PRI(options->host),
+ options->port,
+ AWS_BYTE_CURSOR_PRI(path));
+
+ return AWS_OP_SUCCESS;
+
+error:
+ s_ws_bootstrap_destroy(ws_bootstrap);
+ return AWS_OP_ERR;
+}
+
+static void s_ws_bootstrap_destroy(struct aws_websocket_client_bootstrap *ws_bootstrap) {
+ if (!ws_bootstrap) {
+ return;
+ }
+
+ aws_http_message_release(ws_bootstrap->handshake_request);
+ aws_http_headers_release(ws_bootstrap->response_headers);
+ aws_byte_buf_clean_up(&ws_bootstrap->expected_sec_websocket_accept);
+ aws_string_destroy(ws_bootstrap->expected_sec_websocket_protocols);
+ aws_byte_buf_clean_up(&ws_bootstrap->response_body);
+
+ aws_mem_release(ws_bootstrap->alloc, ws_bootstrap);
+}
+
+/* Given the handshake request's "Sec-WebSocket-Key" value,
+ * calculate the expected value for the response's "Sec-WebSocket-Accept".
+ * RFC-6455 Section 4.1:
+ * base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket-Key|
+ * (as a string, not base64-decoded) with the string
+ * "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
+ * trailing whitespace
+ */
+static int s_ws_bootstrap_calculate_sec_websocket_accept(
+ struct aws_byte_cursor sec_websocket_key,
+ struct aws_byte_buf *out_buf,
+ struct aws_allocator *alloc) {
+
+ AWS_ASSERT(out_buf && !out_buf->allocator && out_buf->len == 0); /* expect buf to be uninitialized */
+
+ /* note: leading and trailing whitespace was already trimmed by aws_http_headers */
+
+ /* optimization: skip concatenating Sec-WebSocket-Key and the magic string.
+ * just run the SHA1 over the first string, and then the 2nd. */
+
+ bool success = false;
+ struct aws_byte_cursor magic_string = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
+
+ /* SHA-1 */
+ struct aws_hash *sha1 = aws_sha1_new(alloc);
+ if (!sha1) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=static: Failed to initiate SHA1, error %d (%s)",
+ aws_last_error(),
+ aws_error_name(aws_last_error()));
+ goto cleanup;
+ }
+
+ if (aws_hash_update(sha1, &sec_websocket_key) || aws_hash_update(sha1, &magic_string)) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=static: Failed to update SHA1, error %d (%s)",
+ aws_last_error(),
+ aws_error_name(aws_last_error()));
+ goto cleanup;
+ }
+
+ uint8_t sha1_storage[AWS_SHA1_LEN];
+ struct aws_byte_buf sha1_buf = aws_byte_buf_from_empty_array(sha1_storage, sizeof(sha1_storage));
+ if (aws_hash_finalize(sha1, &sha1_buf, 0)) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=static: Failed to finalize SHA1, error %d (%s)",
+ aws_last_error(),
+ aws_error_name(aws_last_error()));
+ goto cleanup;
+ }
+
+ /* base64-encoded SHA-1 (clear out_buf, and write to it again) */
+ size_t base64_encode_sha1_len;
+ if (aws_base64_compute_encoded_len(sha1_buf.len, &base64_encode_sha1_len)) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=static: Failed to determine Base64-encoded length, error %d (%s)",
+ aws_last_error(),
+ aws_error_name(aws_last_error()));
+ goto cleanup;
+ }
+ aws_byte_buf_init(out_buf, alloc, base64_encode_sha1_len);
+
+ struct aws_byte_cursor sha1_cursor = aws_byte_cursor_from_buf(&sha1_buf);
+ if (aws_base64_encode(&sha1_cursor, out_buf)) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=static: Failed to Base64-encode, error %d (%s)",
+ aws_last_error(),
+ aws_error_name(aws_last_error()));
+ goto cleanup;
+ }
+
+ success = true;
+cleanup:
+ if (sha1) {
+ aws_hash_destroy(sha1);
+ }
+ return success ? AWS_OP_SUCCESS : AWS_OP_ERR;
+}
+
+/* Called if something goes wrong after an HTTP connection is established.
+ * The HTTP connection is closed.
+ * We must wait for its shutdown to complete before informing user of the failed websocket setup. */
+static void s_ws_bootstrap_cancel_setup_due_to_err(
+ struct aws_websocket_client_bootstrap *ws_bootstrap,
+ struct aws_http_connection *http_connection,
+ int error_code) {
+
+ AWS_ASSERT(error_code);
+ AWS_ASSERT(http_connection);
+
+ if (!ws_bootstrap->setup_error_code) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Canceling websocket setup due to error %d (%s).",
+ (void *)ws_bootstrap,
+ error_code,
+ aws_error_name(error_code));
+
+ ws_bootstrap->setup_error_code = error_code;
+
+ s_system_vtable->aws_http_connection_close(http_connection);
+ }
+}
+
+static void s_ws_bootstrap_invoke_setup_callback(struct aws_websocket_client_bootstrap *ws_bootstrap, int error_code) {
+
+ /* sanity check: websocket XOR error_code is set. both cannot be set. both cannot be unset */
+ AWS_FATAL_ASSERT((error_code != 0) ^ (ws_bootstrap->websocket != NULL));
+
+ /* Report things about the response, if we received them */
+ int *response_status_ptr = NULL;
+ struct aws_http_header *response_header_array = NULL;
+ size_t num_response_headers = 0;
+ struct aws_byte_cursor *response_body_ptr = NULL;
+ struct aws_byte_cursor response_body_cursor = {.len = 0};
+
+ if (ws_bootstrap->got_full_response_headers) {
+ response_status_ptr = &ws_bootstrap->response_status;
+
+ num_response_headers = aws_http_headers_count(ws_bootstrap->response_headers);
+
+ response_header_array =
+ aws_mem_calloc(ws_bootstrap->alloc, aws_max_size(1, num_response_headers), sizeof(struct aws_http_header));
+
+ for (size_t i = 0; i < num_response_headers; ++i) {
+ aws_http_headers_get_index(ws_bootstrap->response_headers, i, &response_header_array[i]);
+ }
+
+ if (ws_bootstrap->got_full_response_body) {
+ response_body_cursor = aws_byte_cursor_from_buf(&ws_bootstrap->response_body);
+ response_body_ptr = &response_body_cursor;
+ }
+ }
+
+ struct aws_websocket_on_connection_setup_data setup_data = {
+ .error_code = error_code,
+ .websocket = ws_bootstrap->websocket,
+ .handshake_response_status = response_status_ptr,
+ .handshake_response_header_array = response_header_array,
+ .num_handshake_response_headers = num_response_headers,
+ .handshake_response_body = response_body_ptr,
+ };
+
+ ws_bootstrap->websocket_setup_callback(&setup_data, ws_bootstrap->user_data);
+
+ /* Clear setup callback so that we know that it's been invoked. */
+ ws_bootstrap->websocket_setup_callback = NULL;
+
+ if (response_header_array) {
+ aws_mem_release(ws_bootstrap->alloc, response_header_array);
+ }
+}
+
+/* Invoked when HTTP connection has been established (or failed to be established) */
+static void s_ws_bootstrap_on_http_setup(struct aws_http_connection *http_connection, int error_code, void *user_data) {
+
+ struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
+
+ /* Setup callback contract is: if error_code is non-zero then connection is NULL. */
+ AWS_FATAL_ASSERT((error_code != 0) == (http_connection == NULL));
+
+ /* If http connection failed, inform the user immediately and clean up the websocket bootstrapper. */
+ if (error_code) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Websocket setup failed to establish HTTP connection, error %d (%s).",
+ (void *)ws_bootstrap,
+ error_code,
+ aws_error_name(error_code));
+
+ s_ws_bootstrap_invoke_setup_callback(ws_bootstrap, error_code);
+
+ s_ws_bootstrap_destroy(ws_bootstrap);
+ return;
+ }
+
+ /* Connection exists!
+ * Note that if anything goes wrong with websocket setup from hereon out, we must close the http connection
+ * first and wait for shutdown to complete before informing the user of setup failure. */
+
+ /* Send the handshake request */
+ struct aws_http_make_request_options options = {
+ .self_size = sizeof(options),
+ .request = ws_bootstrap->handshake_request,
+ .user_data = ws_bootstrap,
+ .on_response_headers = s_ws_bootstrap_on_handshake_response_headers,
+ .on_response_header_block_done = s_ws_bootstrap_on_handshake_response_header_block_done,
+ .on_response_body = s_ws_bootstrap_on_handshake_response_body,
+ .on_complete = s_ws_bootstrap_on_stream_complete,
+ };
+
+ struct aws_http_stream *handshake_stream =
+ s_system_vtable->aws_http_connection_make_request(http_connection, &options);
+
+ if (!handshake_stream) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Failed to make websocket upgrade request, error %d (%s).",
+ (void *)ws_bootstrap,
+ aws_last_error(),
+ aws_error_name(aws_last_error()));
+ goto error;
+ }
+
+ if (s_system_vtable->aws_http_stream_activate(handshake_stream)) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Failed to activate websocket upgrade request, error %d (%s).",
+ (void *)ws_bootstrap,
+ aws_last_error(),
+ aws_error_name(aws_last_error()));
+ goto error;
+ }
+
+ /* Success! (so far) */
+ AWS_LOGF_TRACE(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: HTTP connection established, sending websocket upgrade request.",
+ (void *)ws_bootstrap);
+ return;
+
+error:
+ s_system_vtable->aws_http_stream_release(handshake_stream);
+ s_ws_bootstrap_cancel_setup_due_to_err(ws_bootstrap, http_connection, aws_last_error());
+}
+
+/* Invoked when the HTTP connection has shut down.
+ * This is never called if the HTTP connection failed its setup */
+static void s_ws_bootstrap_on_http_shutdown(
+ struct aws_http_connection *http_connection,
+ int error_code,
+ void *user_data) {
+
+ struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
+
+ /* Inform user that connection has completely shut down.
+ * If setup callback still hasn't fired, invoke it now and indicate failure.
+ * Otherwise, invoke shutdown callback. */
+ if (ws_bootstrap->websocket_setup_callback) {
+ AWS_ASSERT(!ws_bootstrap->websocket);
+
+ /* If there's already a setup_error_code, use that */
+ if (ws_bootstrap->setup_error_code) {
+ error_code = ws_bootstrap->setup_error_code;
+ }
+
+ /* Ensure non-zero error_code is passed */
+ if (!error_code) {
+ error_code = AWS_ERROR_UNKNOWN;
+ }
+
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Websocket setup failed, error %d (%s).",
+ (void *)ws_bootstrap,
+ error_code,
+ aws_error_name(error_code));
+
+ s_ws_bootstrap_invoke_setup_callback(ws_bootstrap, error_code);
+
+ } else if (ws_bootstrap->websocket_shutdown_callback) {
+ AWS_ASSERT(ws_bootstrap->websocket);
+
+ AWS_LOGF_DEBUG(
+ AWS_LS_HTTP_WEBSOCKET,
+ "id=%p: Websocket client connection shut down with error %d (%s).",
+ (void *)ws_bootstrap->websocket,
+ error_code,
+ aws_error_name(error_code));
+
+ ws_bootstrap->websocket_shutdown_callback(ws_bootstrap->websocket, error_code, ws_bootstrap->user_data);
+ }
+
+ /* Clean up HTTP connection and websocket-bootstrap.
+ * It's still up to the user to release the websocket itself. */
+ s_system_vtable->aws_http_connection_release(http_connection);
+
+ s_ws_bootstrap_destroy(ws_bootstrap);
+}
+
+/* Invoked repeatedly as handshake response headers arrive */
+static int s_ws_bootstrap_on_handshake_response_headers(
+ struct aws_http_stream *stream,
+ enum aws_http_header_block header_block,
+ const struct aws_http_header *header_array,
+ size_t num_headers,
+ void *user_data) {
+
+ (void)stream;
+ (void)header_block;
+
+ struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
+
+ /* Deep-copy headers into ws_bootstrap */
+ aws_http_headers_add_array(ws_bootstrap->response_headers, header_array, num_headers);
+
+ /* Don't report a partially-received response */
+ ws_bootstrap->got_full_response_headers = false;
+
+ return AWS_OP_SUCCESS;
+}
+
+static int s_ws_bootstrap_validate_header(
+ struct aws_websocket_client_bootstrap *ws_bootstrap,
+ const char *name,
+ struct aws_byte_cursor expected_value,
+ bool case_sensitive) {
+
+ struct aws_byte_cursor actual_value;
+ if (aws_http_headers_get(ws_bootstrap->response_headers, aws_byte_cursor_from_c_str(name), &actual_value)) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP, "id=%p: Response lacks required '%s' header", (void *)ws_bootstrap, name);
+ return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
+ }
+
+ bool matches = case_sensitive ? aws_byte_cursor_eq(&expected_value, &actual_value)
+ : aws_byte_cursor_eq_ignore_case(&expected_value, &actual_value);
+ if (!matches) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Response '%s' header has wrong value. Expected '" PRInSTR "'. Received '" PRInSTR "'",
+ (void *)ws_bootstrap,
+ name,
+ AWS_BYTE_CURSOR_PRI(expected_value),
+ AWS_BYTE_CURSOR_PRI(actual_value));
+ return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
+ }
+
+ return AWS_OP_SUCCESS;
+}
+
+static int s_ws_bootstrap_validate_sec_websocket_protocol(const struct aws_websocket_client_bootstrap *ws_bootstrap) {
+ /* First handle the easy case:
+ * If client requested no protocols, then the response should not pick any */
+ if (ws_bootstrap->expected_sec_websocket_protocols == NULL) {
+ if (aws_http_headers_has(
+ ws_bootstrap->response_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Protocol"))) {
+
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Response has 'Sec-WebSocket-Protocol' header, no protocol was requested",
+ (void *)ws_bootstrap);
+ return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
+ } else {
+ return AWS_OP_SUCCESS;
+ }
+ }
+
+ /* Check that server has picked one of the protocols listed in the request */
+ struct aws_byte_cursor response_protocol;
+ if (aws_http_headers_get(
+ ws_bootstrap->response_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Protocol"), &response_protocol)) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Response lacks required 'Sec-WebSocket-Protocol' header",
+ (void *)ws_bootstrap);
+ return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
+ }
+
+ struct aws_byte_cursor request_protocols =
+ aws_byte_cursor_from_string(ws_bootstrap->expected_sec_websocket_protocols);
+ struct aws_byte_cursor request_protocol_i;
+ AWS_ZERO_STRUCT(request_protocol_i);
+ while (aws_byte_cursor_next_split(&request_protocols, ',', &request_protocol_i)) {
+ struct aws_byte_cursor request_protocol = aws_strutil_trim_http_whitespace(request_protocol_i);
+ if (aws_byte_cursor_eq(&response_protocol, &request_protocol)) {
+ /* Success! */
+ AWS_LOGF_DEBUG(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Server selected Sec-WebSocket-Protocol: " PRInSTR,
+ (void *)ws_bootstrap,
+ AWS_BYTE_CURSOR_PRI(response_protocol));
+ return AWS_OP_SUCCESS;
+ }
+ }
+
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Response 'Sec-WebSocket-Protocol' header has wrong value. Received '" PRInSTR
+ "'. Expected one of '" PRInSTR "'",
+ (void *)ws_bootstrap,
+ AWS_BYTE_CURSOR_PRI(response_protocol),
+ AWS_BYTE_CURSOR_PRI(request_protocols));
+ return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
+}
+
+/* OK, we've got all the headers for the 101 Switching Protocols response.
+ * Validate the handshake response, install the websocket handler into the channel,
+ * and invoke the on_connection_setup callback. */
+static int s_ws_bootstrap_validate_response_and_install_websocket_handler(
+ struct aws_websocket_client_bootstrap *ws_bootstrap,
+ struct aws_http_connection *http_connection) {
+
+ /* RFC-6455 Section 4.1 - The client MUST validate the server's response as follows... */
+
+ /* (we already checked step 1, that status code is 101) */
+ AWS_FATAL_ASSERT(ws_bootstrap->response_status == AWS_HTTP_STATUS_CODE_101_SWITCHING_PROTOCOLS);
+
+ /* 2. If the response lacks an |Upgrade| header field or the |Upgrade|
+ * header field contains a value that is not an ASCII case-
+ * insensitive match for the value "websocket", the client MUST
+ * _Fail the WebSocket Connection_. */
+ if (s_ws_bootstrap_validate_header(
+ ws_bootstrap, "Upgrade", aws_byte_cursor_from_c_str("websocket"), false /*case_sensitive*/)) {
+ goto error;
+ }
+
+ /* 3. If the response lacks a |Connection| header field or the
+ * |Connection| header field doesn't contain a token that is an
+ * ASCII case-insensitive match for the value "Upgrade", the client
+ * MUST _Fail the WebSocket Connection_. */
+ if (s_ws_bootstrap_validate_header(
+ ws_bootstrap, "Connection", aws_byte_cursor_from_c_str("Upgrade"), false /*case_sensitive*/)) {
+ goto error;
+ }
+
+ /* 4. If the response lacks a |Sec-WebSocket-Accept| header field or
+ * the |Sec-WebSocket-Accept| contains a value other than the
+ * base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket-
+ * Key| (as a string, not base64-decoded) with the string "258EAFA5-
+ * E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
+ * trailing whitespace, the client MUST _Fail the WebSocket
+ * Connection_. */
+ if (s_ws_bootstrap_validate_header(
+ ws_bootstrap,
+ "Sec-WebSocket-Accept",
+ aws_byte_cursor_from_buf(&ws_bootstrap->expected_sec_websocket_accept),
+ true /*case_sensitive*/)) {
+ goto error;
+ }
+
+ /* (step 5 is about validating Sec-WebSocket-Extensions, but we don't support extensions) */
+ if (aws_http_headers_has(ws_bootstrap->response_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Extensions"))) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Response has 'Sec-WebSocket-Extensions' header, but client does not support extensions.",
+ (void *)ws_bootstrap);
+ aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
+ goto error;
+ }
+
+ /* 6. If the response includes a |Sec-WebSocket-Protocol| header field
+ * and this header field indicates the use of a subprotocol that was
+ * not present in the client's handshake (the server has indicated a
+ * subprotocol not requested by the client), the client MUST _Fail
+ * the WebSocket Connection_. */
+ if (s_ws_bootstrap_validate_sec_websocket_protocol(ws_bootstrap)) {
+ goto error;
+ }
+
+ /* Insert websocket handler into channel */
+ struct aws_channel *channel = s_system_vtable->aws_http_connection_get_channel(http_connection);
+ AWS_ASSERT(channel);
+
+ struct aws_websocket_handler_options ws_options = {
+ .allocator = ws_bootstrap->alloc,
+ .channel = channel,
+ .initial_window_size = ws_bootstrap->initial_window_size,
+ .user_data = ws_bootstrap->user_data,
+ .on_incoming_frame_begin = ws_bootstrap->websocket_frame_begin_callback,
+ .on_incoming_frame_payload = ws_bootstrap->websocket_frame_payload_callback,
+ .on_incoming_frame_complete = ws_bootstrap->websocket_frame_complete_callback,
+ .is_server = false,
+ .manual_window_update = ws_bootstrap->manual_window_update,
+ };
+
+ ws_bootstrap->websocket = s_system_vtable->aws_websocket_handler_new(&ws_options);
+ if (!ws_bootstrap->websocket) {
+ AWS_LOGF_ERROR(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Failed to create websocket handler, error %d (%s)",
+ (void *)ws_bootstrap,
+ aws_last_error(),
+ aws_error_name(aws_last_error()));
+
+ goto error;
+ }
+
+ /* Success! Setup complete! */
+ AWS_LOGF_TRACE(/* Log for tracing setup id to websocket id. */
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Setup success, created websocket=%p",
+ (void *)ws_bootstrap,
+ (void *)ws_bootstrap->websocket);
+
+ AWS_LOGF_DEBUG(/* Debug log about creation of websocket. */
+ AWS_LS_HTTP_WEBSOCKET,
+ "id=%p: Websocket client connection established.",
+ (void *)ws_bootstrap->websocket);
+
+ s_ws_bootstrap_invoke_setup_callback(ws_bootstrap, 0 /*error_code*/);
+ return AWS_OP_SUCCESS;
+
+error:
+ s_ws_bootstrap_cancel_setup_due_to_err(ws_bootstrap, http_connection, aws_last_error());
+ /* Returning error stops HTTP from processing any further data */
+ return AWS_OP_ERR;
+}
+
+/**
+ * Invoked each time we reach the end of a block of response headers.
+ * If we got a valid 101 Switching Protocols response, we insert the websocket handler.
+ * Note:
+ * In HTTP, 1xx responses are "interim" responses. So a 101 Switching Protocols
+ * response does not "complete" the stream. Once the connection has switched
+ * protocols, the stream does not end until the whole connection is closed.
+ */
+static int s_ws_bootstrap_on_handshake_response_header_block_done(
+ struct aws_http_stream *stream,
+ enum aws_http_header_block header_block,
+ void *user_data) {
+
+ struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
+ struct aws_http_connection *http_connection = s_system_vtable->aws_http_stream_get_connection(stream);
+ AWS_ASSERT(http_connection);
+
+ /* Get status code from stream */
+ s_system_vtable->aws_http_stream_get_incoming_response_status(stream, &ws_bootstrap->response_status);
+
+ ws_bootstrap->got_full_response_headers = true;
+
+ if (header_block == AWS_HTTP_HEADER_BLOCK_INFORMATIONAL) {
+ if (ws_bootstrap->response_status == AWS_HTTP_STATUS_CODE_101_SWITCHING_PROTOCOLS) {
+ /* OK, got 101 response, proceed with upgrade! */
+ return s_ws_bootstrap_validate_response_and_install_websocket_handler(ws_bootstrap, http_connection);
+
+ } else {
+ /* It would be weird to get any other kind of 1xx response, but anything is possible.
+ * Another response should come eventually. Just ignore the headers from this one... */
+ AWS_LOGF_DEBUG(
+ AWS_LS_HTTP_WEBSOCKET_SETUP,
+ "id=%p: Server sent interim response with status code %d",
+ (void *)ws_bootstrap,
+ ws_bootstrap->response_status);
+
+ aws_http_headers_clear(ws_bootstrap->response_headers);
+ ws_bootstrap->got_full_response_headers = false;
+ return AWS_OP_SUCCESS;
+ }
+ }
+
+ /* Otherwise, we got normal headers (from a non-1xx response), or trailing headers.
+ * This can only happen if the handshake did not succeed. Keep the connection going.
+ * We'll report failed setup to the user after we've received the complete response */
+ ws_bootstrap->setup_error_code = AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE;
+ return AWS_OP_SUCCESS;
+}
+
+/**
+ * Invoked as we receive the body of a failed response.
+ * This is never invoked if the handshake succeeds.
+ */
+static int s_ws_bootstrap_on_handshake_response_body(
+ struct aws_http_stream *stream,
+ const struct aws_byte_cursor *data,
+ void *user_data) {
+
+ struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
+
+ aws_byte_buf_append_dynamic(&ws_bootstrap->response_body, data);
+
+ /* If we're managing the read window...
+ * bump the HTTP window back to its starting size, so that we keep receiving the whole response. */
+ if (ws_bootstrap->manual_window_update) {
+ s_system_vtable->aws_http_stream_update_window(stream, data->len);
+ }
+
+ return AWS_OP_SUCCESS;
+}
+
+/**
+ * Invoked when the stream completes.
+ *
+ * If the handshake succeeded and the websocket was installed,
+ * then this is invoked at the end of the websocket connection.
+ *
+ * If the handshake response was not 101, then this is invoked
+ * after we've received the whole response.
+ *
+ * Or this is invoked because the connection failed unexpectedly before the handshake could complete,
+ * (or we killed the connection because the 101 response didn't pass validation).
+ */
+static void s_ws_bootstrap_on_stream_complete(struct aws_http_stream *stream, int error_code, void *user_data) {
+ struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
+ struct aws_http_connection *http_connection = s_system_vtable->aws_http_stream_get_connection(stream);
+
+ /* Only report the body if we received a complete response */
+ if (error_code == 0) {
+ ws_bootstrap->got_full_response_body = true;
+ }
+
+ /* Make sure the connection closes.
+ * We'll deal with finishing setup or shutdown from the http-shutdown callback */
+ s_system_vtable->aws_http_connection_close(http_connection);
+
+ /* Done with stream, let it be cleaned up */
+ s_system_vtable->aws_http_stream_release(stream);
+}