Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions include/aws/http/private/http_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ enum aws_http_header_name {
AWS_HTTP_HEADER_UPGRADE,
AWS_HTTP_HEADER_KEEP_ALIVE,
AWS_HTTP_HEADER_PROXY_CONNECTION,
AWS_HTTP_HEADER_SIGNING_SECURITY_TOKEN,
AWS_HTTP_HEADER_SIGNING_S3SESSION_TOKEN,

AWS_HTTP_HEADER_COUNT, /* Number of enums */
};
Expand Down
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
31 changes: 31 additions & 0 deletions source/h1_stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,37 @@ 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?

case AWS_HTTP_HEADER_SIGNING_SECURITY_TOKEN:
case AWS_HTTP_HEADER_SIGNING_S3SESSION_TOKEN:
/* TODO: move the filter to SDKs, not the http client. */
/* 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
50 changes: 50 additions & 0 deletions source/h2_stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,48 @@ 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:
case AWS_HTTP_HEADER_SIGNING_SECURITY_TOKEN:
case AWS_HTTP_HEADER_SIGNING_S3SESSION_TOKEN:
/* TODO: move the filter to SDKs, not the http client. */
/* 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 +1151,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
2 changes: 2 additions & 0 deletions source/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ static void s_headers_init(struct aws_allocator *alloc) {
s_header_enum_to_str[AWS_HTTP_HEADER_UPGRADE] = aws_byte_cursor_from_c_str("upgrade");
s_header_enum_to_str[AWS_HTTP_HEADER_KEEP_ALIVE] = aws_byte_cursor_from_c_str("keep-alive");
s_header_enum_to_str[AWS_HTTP_HEADER_PROXY_CONNECTION] = aws_byte_cursor_from_c_str("proxy-connection");
s_header_enum_to_str[AWS_HTTP_HEADER_SIGNING_SECURITY_TOKEN] = aws_byte_cursor_from_c_str("x-amz-security-token");
s_header_enum_to_str[AWS_HTTP_HEADER_SIGNING_S3SESSION_TOKEN] = aws_byte_cursor_from_c_str("x-amz-s3session-token");

s_init_str_to_enum_hash_table(
&s_header_str_to_enum,
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