Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions source/connection_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -978,10 +978,13 @@ struct aws_http_connection_manager *aws_http_connection_manager_new(
manager->initial_settings = aws_mem_calloc(allocator, 1, sizeof(struct aws_array_list));
aws_array_list_init_dynamic(
manager->initial_settings, allocator, options->num_initial_settings, sizeof(struct aws_http2_setting));
/* Copy the initial_settings_array to the internal structure */
memcpy(
manager->initial_settings->data,
options->initial_settings_array,
options->num_initial_settings * sizeof(struct aws_http2_setting));
/* Update the length of the array as the memcpy only copies the data, but the length remains 0. */
manager->initial_settings->length = options->num_initial_settings;
}
manager->max_closed_streams = options->max_closed_streams;
manager->http2_conn_manual_window_management = options->http2_conn_manual_window_management;
Expand Down
28 changes: 28 additions & 0 deletions source/h1_stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,34 @@ struct aws_h1_stream *aws_h1_stream_new_request(
stream->thread_data.is_final_stream = true;
}

struct aws_http_headers *headers = aws_http_message_get_headers(options->request);

/* Log the headers that we are sending out. */
for (size_t i = 0; i < aws_http_headers_count(headers); i++) {
struct aws_http_header header;
aws_http_headers_get_index(headers, i, &header);
enum aws_http_header_name name_enum = aws_http_str_to_header_name(header.name);
switch (name_enum) {
case AWS_HTTP_HEADER_AUTHORIZATION:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we also skip session/security token?

/* Sensitive header, do not log the value of the header */
AWS_LOGF_TRACE(
AWS_LS_HTTP_STREAM,
"id=%p: Sending header: " PRInSTR ": ***",
(void *)&stream->base,
AWS_BYTE_CURSOR_PRI(header.name));
break;
default:
/* Log the headers we are sending out */
AWS_LOGF_TRACE(
AWS_LS_HTTP_STREAM,
"id=%p: Sending header: " PRInSTR ": " PRInSTR "",
(void *)&stream->base,
AWS_BYTE_CURSOR_PRI(header.name),
AWS_BYTE_CURSOR_PRI(header.value));
break;
}
}

stream->synced_data.using_chunked_encoding = stream->thread_data.encoder_message.has_chunked_encoding_header;

return stream;
Expand Down
47 changes: 47 additions & 0 deletions source/h2_stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,45 @@ int aws_h2_stream_on_activated(struct aws_h2_stream *stream, enum aws_h2_stream_
connection->thread_data.settings_self[AWS_HTTP2_SETTINGS_INITIAL_WINDOW_SIZE] / 2;
}

/* Log the headers that we are sending out. */
for (size_t i = 0; i < aws_http_headers_count(h2_headers); i++) {
struct aws_http_header header;
aws_http_headers_get_index(h2_headers, i, &header);
enum aws_http_header_name name_enum = aws_http_str_to_header_name(header.name);
switch (name_enum) {
case AWS_HTTP_HEADER_CONNECTION:
case AWS_HTTP_HEADER_TRANSFER_ENCODING:
case AWS_HTTP_HEADER_UPGRADE:
case AWS_HTTP_HEADER_KEEP_ALIVE:
case AWS_HTTP_HEADER_PROXY_CONNECTION:
/**
* An endpoint MUST NOT generate an HTTP/2 message containing connection-specific header fields.
* (RFC=9113 8.2.2)
*/
AWS_H2_STREAM_LOGF(
TRACE,
stream,
"Found connection-specific header that is allowed in HTTP/2. : " PRInSTR ": " PRInSTR "",
AWS_BYTE_CURSOR_PRI(header.name),
AWS_BYTE_CURSOR_PRI(header.value));
aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_FIELD);
goto error;
case AWS_HTTP_HEADER_AUTHORIZATION:
/* Sensitive header, do not log the value of the header */
AWS_H2_STREAM_LOGF(TRACE, stream, "Sending header: " PRInSTR ": ***", AWS_BYTE_CURSOR_PRI(header.name));
break;
default:
/* Log the headers we are sending out */
AWS_H2_STREAM_LOGF(
TRACE,
stream,
"Sending header: " PRInSTR ": " PRInSTR "",
AWS_BYTE_CURSOR_PRI(header.name),
AWS_BYTE_CURSOR_PRI(header.value));
break;
}
}

if (with_data) {
/* If stream has DATA to send, put it in the outgoing_streams_list, and we'll send data later */
stream->thread_data.state = AWS_H2_STREAM_STATE_OPEN;
Expand Down Expand Up @@ -1109,6 +1148,14 @@ struct aws_h2err aws_h2_stream_on_decoder_data_begin(
return s_send_rst_and_close_stream(stream, aws_h2err_from_h2_code(AWS_HTTP2_ERR_FLOW_CONTROL_ERROR));
}
stream->thread_data.window_size_self -= payload_len;
if (stream->thread_data.window_size_self == 0) {
AWS_H2_STREAM_LOGF(
ERROR,
stream,
"DATA length=%" PRIu32 " exceeds flow-control window=%" PRIi32,
payload_len,
stream->thread_data.window_size_self);
}

/* If stream isn't over, we may need to send automatic window updates to keep data flowing */
if (!end_stream) {
Expand Down
10 changes: 6 additions & 4 deletions source/request_response.c
Original file line number Diff line number Diff line change
Expand Up @@ -1036,15 +1036,17 @@ struct aws_http_message *aws_http2_message_new_from_http1(
struct aws_byte_cursor lower_name_cursor = aws_byte_cursor_from_buf(&lower_name_buf);
enum aws_http_header_name name_enum = aws_http_lowercase_str_to_header_name(lower_name_cursor);
switch (name_enum) {
/**
* An intermediary transforming an HTTP/1.x message to HTTP/2 MUST remove connection-specific header
* fields as discussed in Section 7.6.1 of [HTTP]. (RFC=9113 8.2.2)
*/
case AWS_HTTP_HEADER_CONNECTION:
case AWS_HTTP_HEADER_TRANSFER_ENCODING:
case AWS_HTTP_HEADER_UPGRADE:
case AWS_HTTP_HEADER_KEEP_ALIVE:
case AWS_HTTP_HEADER_PROXY_CONNECTION:
/* Host has been converted to :authority pseudo header, skip it as well. */
case AWS_HTTP_HEADER_HOST:
/**
* An intermediary transforming an HTTP/1.x message to HTTP/2 MUST remove connection-specific header
* fields as discussed in Section 7.6.1 of [HTTP]. (RFC=9113 8.2.2)
*/
AWS_LOGF_TRACE(
AWS_LS_HTTP_GENERAL,
"Skip connection-specific headers - \"%.*s\" ",
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,8 @@ add_net_test_case(h2_sm_mock_ideal_num_streams)
add_net_test_case(h2_sm_mock_large_ideal_num_streams)
add_net_test_case(h2_sm_mock_goaway)
add_net_test_case(h2_sm_connection_ping)
add_net_test_case(h2_sm_with_flow_control_err)
add_net_test_case(h2_sm_with_initial_settings)

# Tests against real world server
add_net_test_case(h2_sm_acquire_stream)
Expand Down
117 changes: 111 additions & 6 deletions tests/test_stream_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ struct sm_tester_options {
bool close_connection_on_server_error;
size_t connection_ping_period_ms;
size_t connection_ping_timeout_ms;

/* HTTP/2 initial settings */
const struct aws_http2_setting *initial_settings_array;
size_t num_initial_settings;

bool manual_window;
size_t initial_window_size;
};

static struct aws_logger s_logger;
Expand Down Expand Up @@ -290,6 +297,10 @@ static int s_tester_init(struct sm_tester_options *options) {
.connection_ping_period_ms = options->connection_ping_period_ms,
.connection_ping_timeout_ms = options->connection_ping_timeout_ms,
.http2_prior_knowledge = options->prior_knowledge,
.initial_settings_array = options->initial_settings_array,
.num_initial_settings = options->num_initial_settings,
.initial_window_size = options->initial_window_size,
.enable_read_back_pressure = options->manual_window,
};
s_tester.stream_manager = aws_http2_stream_manager_new(alloc, &sm_options);

Expand Down Expand Up @@ -330,7 +341,8 @@ static int s_fake_connection_get_stream_received(struct sm_fake_connection *fake
/* complete first num_streams_to_complete. If num_streams_to_complete is zero, complete all the streams. */
static void s_fake_connection_complete_streams(
struct sm_fake_connection *fake_connection,
int num_streams_to_complete) {
int num_streams_to_complete,
bool with_data) {
if (!fake_connection->connection) {
return;
}
Expand All @@ -350,8 +362,16 @@ static void s_fake_connection_complete_streams(
struct h2_decoded_frame *frame = h2_decode_tester_get_frame(&fake_connection->peer.decode, i);
if (frame->end_stream) {
struct aws_h2_frame *response_frame = aws_h2_frame_new_headers(
s_tester.allocator, frame->stream_id, response_headers, true /*end_stream*/, 0, NULL);
s_tester.allocator, frame->stream_id, response_headers, !with_data /*end_stream*/, 0, NULL);
AWS_FATAL_ASSERT(h2_fake_peer_send_frame(&fake_connection->peer, response_frame) == AWS_OP_SUCCESS);
if (with_data) {
AWS_FATAL_ASSERT(
h2_fake_peer_send_data_frame(
&fake_connection->peer,
frame->stream_id,
aws_byte_cursor_from_c_str("tests"),
true /*end_stream*/) == AWS_OP_SUCCESS);
}
if (num_streams_to_complete && ++streams_completed >= num_streams_to_complete) {
break;
}
Expand Down Expand Up @@ -395,18 +415,26 @@ static void s_release_fake_connections(void) {
s_drain_all_fake_connection_testing_channel();
}

static int s_complete_all_fake_connection_streams(void) {
static int s_complete_all_fake_connection_streams_impl(bool with_data) {
size_t count = aws_array_list_length(&s_tester.fake_connections);
for (size_t i = 0; i < count; ++i) {
struct sm_fake_connection *fake_connection = NULL;
ASSERT_SUCCESS(aws_array_list_get_at(&s_tester.fake_connections, &fake_connection, i));
/* complete all the streams from the fake connection */
s_fake_connection_complete_streams(fake_connection, 0 /*all streams*/);
s_fake_connection_complete_streams(fake_connection, 0 /*all streams*/, with_data);
testing_channel_drain_queued_tasks(&fake_connection->testing_channel);
}
return AWS_OP_SUCCESS;
}

static int s_complete_all_fake_connection_streams(void) {
return s_complete_all_fake_connection_streams_impl(false);
}

static int s_complete_all_fake_connection_streams_with_data(void) {
return s_complete_all_fake_connection_streams_impl(true);
}

static int s_tester_clean_up(void) {
s_release_all_streams();
if (s_tester.stream_manager) {
Expand Down Expand Up @@ -952,7 +980,7 @@ TEST_CASE(h2_sm_mock_complete_stream) {
/* Fake peer send settings that only allow 2 concurrent streams */
struct sm_fake_connection *fake_connection = s_get_fake_connection(0);
ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&fake_connection->peer));
s_fake_connection_complete_streams(fake_connection, 1);
s_fake_connection_complete_streams(fake_connection, 1, false);

/* Acquire a new streams */
ASSERT_SUCCESS(s_sm_stream_acquiring(1));
Expand Down Expand Up @@ -1160,7 +1188,7 @@ TEST_CASE(h2_sm_connection_ping) {
ASSERT_SUCCESS(h2_fake_peer_send_frame(&fake_connection_1->peer, peer_frame));
testing_channel_drain_queued_tasks(&fake_connection_1->testing_channel);
s_fake_connection_complete_streams(
fake_connection_1, 0 /*all streams*/); /* Make sure the streams completed successfully */
fake_connection_1, 0 /*all streams*/, false); /* Make sure the streams completed successfully */

/* Check fake connection 2 received PING */
ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&fake_connection_2->peer));
Expand Down Expand Up @@ -1188,6 +1216,83 @@ TEST_CASE(h2_sm_connection_ping) {
return s_tester_clean_up();
}

/* Test that stream manager with flow control error if the server didn't respect the window */
TEST_CASE(h2_sm_with_flow_control_err) {
(void)ctx;

struct sm_tester_options options = {
.max_connections = 5,
.alloc = allocator,
.manual_window = true,
.initial_window_size = 0,
};

ASSERT_SUCCESS(s_tester_init(&options));
ASSERT_NOT_NULL(s_tester.stream_manager);

/* Verify stream manager works with initial settings */
s_override_cm_connect_function(s_aws_http_connection_manager_create_connection_sync_mock);
int num_to_acquire = 1;
ASSERT_SUCCESS(s_sm_stream_acquiring(num_to_acquire));
ASSERT_SUCCESS(s_wait_on_fake_connection_count(1));
s_drain_all_fake_connection_testing_channel();
ASSERT_SUCCESS(s_wait_on_streams_acquired_count(num_to_acquire));
ASSERT_SUCCESS(s_complete_all_fake_connection_streams_with_data());
ASSERT_INT_EQUALS(1, s_tester.stream_complete_errors);
ASSERT_INT_EQUALS(AWS_ERROR_HTTP_PROTOCOL_ERROR, s_tester.stream_completed_error_code);

return s_tester_clean_up();
}

/* Test that stream manager can be created with initial_settings_array configured */
TEST_CASE(h2_sm_with_initial_settings) {
(void)ctx;
/* TODO: VALIDATE from the peer that those settings received. For now we test this with settings to override the
* initial window. */
/* Configure custom HTTP/2 initial settings */
struct aws_http2_setting initial_settings[] = {
{
.id = AWS_HTTP2_SETTINGS_HEADER_TABLE_SIZE,
.value = 8192,
},
{
.id = AWS_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
.value = 128,
},
{
.id = AWS_HTTP2_SETTINGS_INITIAL_WINDOW_SIZE,
.value = 65536,
},
};

struct sm_tester_options options = {
.max_connections = 5,
.alloc = allocator,
.initial_settings_array = initial_settings,
.num_initial_settings = AWS_ARRAY_SIZE(initial_settings),
.manual_window = true,
/* Set initial window size to 0, but the settings should override it to keep the data flow. */
.initial_window_size = 0,
};

ASSERT_SUCCESS(s_tester_init(&options));
ASSERT_NOT_NULL(s_tester.stream_manager);

/* Verify stream manager works with initial settings */
s_override_cm_connect_function(s_aws_http_connection_manager_create_connection_sync_mock);
int num_to_acquire = 1;
ASSERT_SUCCESS(s_sm_stream_acquiring(num_to_acquire));
ASSERT_SUCCESS(s_wait_on_fake_connection_count(1));
s_drain_all_fake_connection_testing_channel();
ASSERT_SUCCESS(s_wait_on_streams_acquired_count(num_to_acquire));
ASSERT_SUCCESS(s_complete_all_fake_connection_streams_with_data());
/* Should complete without error */
ASSERT_INT_EQUALS(0, s_tester.stream_complete_errors);
ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, s_tester.stream_completed_error_code);

return s_tester_clean_up();
}

/*******************************************************************************
* Net test, that makes real HTTP/2 connection and requests
******************************************************************************/
Expand Down