diff --git a/bin/elasticurl/main.c b/bin/elasticurl/main.c index f727d1ffd..0cac66fbd 100644 --- a/bin/elasticurl/main.c +++ b/bin/elasticurl/main.c @@ -105,8 +105,8 @@ static void s_usage(int exit_code) { fprintf(stderr, " -t, --trace FILE: dumps logs to FILE instead of stderr.\n"); fprintf(stderr, " -v, --verbose: ERROR|INFO|DEBUG|TRACE: log level to configure. Default is none.\n"); fprintf(stderr, " --version: print the version of elasticurl.\n"); - fprintf(stderr, " --http2: HTTP/2 connection required"); - fprintf(stderr, " --http1_1: HTTP/1.1 connection required"); + fprintf(stderr, " --http2: HTTP/2 connection required\n"); + fprintf(stderr, " --http1_1: HTTP/1.1 connection required\n"); fprintf(stderr, " -h, --help\n"); fprintf(stderr, " Display this message and quit.\n"); exit(exit_code); @@ -463,7 +463,7 @@ static void s_on_client_connection_setup(struct aws_http_connection *connection, struct elasticurl_ctx *app_ctx = user_data; if (app_ctx->required_http_version) { if (aws_http_connection_get_version(connection) != app_ctx->required_http_version) { - fprintf(stderr, "Error. The requested http version, %s, is not supported by the peer.", app_ctx->alpn); + fprintf(stderr, "Error. The requested HTTP version, %s, is not supported by the peer.", app_ctx->alpn); exit(1); } } @@ -690,7 +690,7 @@ int main(int argc, char **argv) { } } else { if (app_ctx.required_http_version == AWS_HTTP_VERSION_2) { - fprintf(stderr, "Error, we don't support h2c, please use TLS for HTTP2 connection"); + fprintf(stderr, "Error, we don't support h2c, please use TLS for HTTP/2 connection"); exit(1); } port = 80; diff --git a/source/h2_connection.c b/source/h2_connection.c index dad585320..e5944eddb 100644 --- a/source/h2_connection.c +++ b/source/h2_connection.c @@ -1804,7 +1804,6 @@ static bool s_connection_is_open(const struct aws_http_connection *connection_ba /* Send a GOAWAY with the lowest possible last-stream-id */ static void s_send_goaway(struct aws_h2_connection *connection, enum aws_h2_error_code h2_error_code) { AWS_PRECONDITION(aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel)); - AWS_PRECONDITION(!connection->thread_data.is_writing_stopped); uint32_t last_stream_id = aws_min_u32( connection->thread_data.latest_peer_initiated_stream_id, connection->thread_data.goaway_sent_last_stream_id); diff --git a/source/h2_stream.c b/source/h2_stream.c index 129b50922..c9e5f2d12 100644 --- a/source/h2_stream.c +++ b/source/h2_stream.c @@ -364,7 +364,8 @@ int aws_h2_stream_encode_data_frame( /* Failed to write DATA, treat it as a Stream Error */ AWS_H2_STREAM_LOGF(ERROR, stream, "Error encoding stream DATA, %s", aws_error_name(aws_last_error())); - return aws_last_error(); + s_send_rst_and_close_stream(stream, aws_h2err_from_last_error()); + return AWS_OP_SUCCESS; } if (body_complete) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8a3ebc87a..90db5836f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -331,22 +331,22 @@ add_test_case(h2_client_stream_create) add_test_case(h2_client_unactivated_stream_cleans_up) add_test_case(h2_client_connection_preface_sent) add_test_case(h2_client_ping_ack) +add_test_case(h2_client_ping_ack_higher_priority) add_test_case(h2_client_setting_ack) add_test_case(h2_client_request_cookie_headers) add_test_case(h2_client_stream_complete) add_test_case(h2_client_close) -#TODO add_test_case(h2_client_close_with_incomplete_stream) add_test_case(h2_client_stream_with_h1_request_message) add_test_case(h2_client_stream_err_malformed_header) -#TODO add_test_case(h2_client_stream_err_state_forbids_frame) +add_test_case(h2_client_stream_err_state_forbids_frame) add_test_case(h2_client_conn_err_stream_frames_received_for_idle_stream) add_test_case(h2_client_stream_ignores_some_frames_received_soon_after_closing) #TODO add_test_case(h2_client_conn_err_stream_frames_received_long_after_closing) -#TODO add_test_case(h2_client_conn_err_stream_frames_received_after_rst_stream_received) -#TODO add_test_case(h2_client_stream_receive_info_headers) -#TODO add_test_case(h2_client_stream_err_receive_info_headers_after_main) -#TODO add_test_case(h2_client_stream_receive_trailing_headers) -#TODO add_test_case(h2_client_stream_err_receive_trailing_before_main) +add_test_case(h2_client_conn_err_stream_frames_received_after_rst_stream_received) +add_test_case(h2_client_stream_receive_info_headers) +add_test_case(h2_client_stream_err_receive_info_headers_after_main) +add_test_case(h2_client_stream_receive_trailing_headers) +add_test_case(h2_client_stream_err_receive_trailing_before_main) add_test_case(h2_client_stream_receive_data) add_test_case(h2_client_stream_err_receive_data_before_headers) add_test_case(h2_client_stream_send_data) @@ -362,11 +362,10 @@ add_test_case(h2_client_stream_send_window_update) add_test_case(h2_client_conn_err_window_update_exceed_max) add_test_case(h2_client_conn_err_window_update_size_zero) add_test_case(h2_client_conn_err_initial_window_size_cause_window_exceed_max) -#TODO add_test_case(h2_client_stream_receive_end_stream_before_done_sending) -#TODO add_test_case(h2_client_stream_receive_end_stream_and_rst_before_done_sending) -#TODO add_test_case(h2_client_stream_err_input_stream_failure) +add_test_case(h2_client_stream_receive_end_stream_before_done_sending) +add_test_case(h2_client_stream_receive_end_stream_and_rst_before_done_sending) +add_test_case(h2_client_stream_err_input_stream_failure) add_test_case(h2_client_stream_err_receive_rst_stream) -add_test_case(h2_client_stream_receive_rst_stream_after_complete_response_ok) add_test_case(h2_client_push_promise_automatically_rejected) add_test_case(h2_client_conn_receive_goaway) add_test_case(h2_client_conn_err_invalid_last_stream_id_goaway) diff --git a/tests/test_h2_client.c b/tests/test_h2_client.c index 2217aaf76..880a29851 100644 --- a/tests/test_h2_client.c +++ b/tests/test_h2_client.c @@ -168,6 +168,14 @@ TEST_CASE(h2_client_connection_preface_sent) { return s_tester_clean_up(); } +static int s_stream_tester_init(struct client_stream_tester *stream_tester, struct aws_http_message *request) { + struct client_stream_tester_options options = { + .request = request, + .connection = s_tester.connection, + }; + return client_stream_tester_init(stream_tester, s_tester.alloc, &options); +} + /* Test that client will automatically send the PING ACK frame back, when the PING frame is received */ TEST_CASE(h2_client_ping_ack) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); @@ -182,8 +190,6 @@ TEST_CASE(h2_client_ping_ack) { ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, frame)); testing_channel_drain_queued_tasks(&s_tester.testing_channel); - /* Have the fake peer to run its decoder on what the client has written. - * The decoder will raise an error if it doesn't receive the "client connection preface string" first. */ ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* Now check that client sent PING ACK frame, it should be the latest frame received by peer @@ -195,7 +201,61 @@ TEST_CASE(h2_client_ping_ack) { return s_tester_clean_up(); } -/* TODO: test that ping response is sent with higher priority than any other frame */ + +TEST_CASE(h2_client_ping_ack_higher_priority) { + ASSERT_SUCCESS(s_tester_init(allocator, ctx)); + + /* get connection preface and acks out of the way */ + ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + size_t frames_count = h2_decode_tester_frame_count(&s_tester.peer.decode); + + /* send request */ + struct aws_http_message *request = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(request); + + struct aws_http_header request_headers_src[] = { + DEFINE_HEADER(":method", "POST"), + DEFINE_HEADER(":scheme", "https"), + DEFINE_HEADER(":path", "/"), + }; + aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); + + const char *body_src = "hello"; + struct aws_byte_cursor body_cursor = aws_byte_cursor_from_c_str(body_src); + struct aws_input_stream *request_body = aws_input_stream_new_from_cursor(allocator, &body_cursor); + aws_http_message_set_body_stream(request, request_body); + + struct client_stream_tester stream_tester; + ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + + /* Frames for the request are activated. Fake peer send PING frame now */ + uint8_t opaque_data[AWS_H2_PING_DATA_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7}; + + struct aws_h2_frame *frame = aws_h2_frame_new_ping(allocator, false /*ack*/, opaque_data); + ASSERT_NOT_NULL(frame); + + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, frame)); + + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + /* validate PING ACK frame has higher priority than the normal request frames, and be received earliest */ + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + + struct h2_decoded_frame *fastest_frame = h2_decode_tester_get_frame(&s_tester.peer.decode, frames_count); + ASSERT_UINT_EQUALS(AWS_H2_FRAME_T_PING, fastest_frame->type); + ASSERT_TRUE(fastest_frame->ack); + ASSERT_BIN_ARRAYS_EQUALS( + opaque_data, AWS_H2_PING_DATA_SIZE, fastest_frame->ping_opaque_data, AWS_H2_PING_DATA_SIZE); + + /* clean up */ + aws_http_message_release(request); + client_stream_tester_clean_up(&stream_tester); + aws_input_stream_destroy(request_body); + return s_tester_clean_up(); +} TEST_CASE(h2_client_setting_ack) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); @@ -215,14 +275,6 @@ TEST_CASE(h2_client_setting_ack) { return s_tester_clean_up(); } -static int s_stream_tester_init(struct client_stream_tester *stream_tester, struct aws_http_message *request) { - struct client_stream_tester_options options = { - .request = request, - .connection = s_tester.connection, - }; - return client_stream_tester_init(stream_tester, s_tester.alloc, &options); -} - static int s_compare_headers(const struct aws_http_headers *expected, const struct aws_http_headers *got) { ASSERT_UINT_EQUALS(aws_http_headers_count(expected), aws_http_headers_count(got)); @@ -506,14 +558,380 @@ TEST_CASE(h2_client_stream_with_h1_request_message) { ASSERT_SUCCESS(s_compare_headers(expected_headers, sent_headers_frame->headers)); /* clean up */ - aws_http_headers_release(expected_headers); + aws_http_headers_release(expected_headers); + aws_http_message_release(request); + client_stream_tester_clean_up(&stream_tester); + return s_tester_clean_up(); +} + +/* Receiving malformed headers should result in a "Stream Error", not a "Connection Error". */ +TEST_CASE(h2_client_stream_err_malformed_header) { + ASSERT_SUCCESS(s_tester_init(allocator, ctx)); + + /* fake peer sends connection preface */ + ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + /* send request */ + struct aws_http_message *request = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(request); + + struct aws_http_header request_headers_src[] = { + DEFINE_HEADER(":method", "GET"), + DEFINE_HEADER(":scheme", "https"), + DEFINE_HEADER(":path", "/"), + }; + aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); + + struct client_stream_tester stream_tester; + ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + /* fake peer sends response with malformed header */ + struct aws_http_header response_headers_src[] = { + DEFINE_HEADER(":STATUS", "404"), /* uppercase name forbidden in h2 */ + }; + + struct aws_http_headers *response_headers = aws_http_headers_new(allocator); + aws_http_headers_add_array(response_headers, response_headers_src, AWS_ARRAY_SIZE(response_headers_src)); + + struct aws_h2_frame *response_frame = aws_h2_frame_new_headers( + allocator, aws_http_stream_get_id(stream_tester.stream), response_headers, true /*end_stream*/, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, response_frame)); + + /* validate that stream completed with error */ + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_TRUE(stream_tester.complete); + ASSERT_INT_EQUALS(AWS_ERROR_HTTP_PROTOCOL_ERROR, stream_tester.on_complete_error_code); + + /* a stream error should not affect the connection */ + ASSERT_TRUE(aws_http_connection_is_open(s_tester.connection)); + + /* validate that stream sent RST_STREAM */ + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + struct h2_decoded_frame *rst_stream_frame = h2_decode_tester_latest_frame(&s_tester.peer.decode); + ASSERT_INT_EQUALS(AWS_H2_FRAME_T_RST_STREAM, rst_stream_frame->type); + ASSERT_UINT_EQUALS(AWS_H2_ERR_PROTOCOL_ERROR, rst_stream_frame->error_code); + + /* clean up */ + aws_http_headers_release(response_headers); + aws_http_message_release(request); + client_stream_tester_clean_up(&stream_tester); + return s_tester_clean_up(); +} + +TEST_CASE(h2_client_stream_err_state_forbids_frame) { + ASSERT_SUCCESS(s_tester_init(allocator, ctx)); + + /* fake peer sends connection preface */ + ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + /* send request */ + struct aws_http_message *request = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(request); + + struct aws_http_header request_headers_src[] = { + DEFINE_HEADER(":method", "PUT"), + DEFINE_HEADER(":scheme", "https"), + DEFINE_HEADER(":path", "/"), + }; + aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); + + const char *body_src = "hello"; + struct aws_byte_cursor body_cursor = aws_byte_cursor_from_c_str(body_src); + struct aws_input_stream *request_body = aws_input_stream_new_tester(allocator, body_cursor); + /* Prevent END_STREAM from being sent */ + aws_input_stream_tester_set_max_bytes_per_read(request_body, 0); + + aws_http_message_set_body_stream(request, request_body); + + struct client_stream_tester stream_tester; + ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + /* Execute 1 event-loop tick. Request is sent, but no end_stream received */ + testing_channel_run_currently_queued_tasks(&s_tester.testing_channel); + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + + uint32_t stream_id = aws_http_stream_get_id(stream_tester.stream); + + struct h2_decoded_frame *sent_headers_frame = h2_decode_tester_latest_frame(&s_tester.peer.decode); + ASSERT_INT_EQUALS(AWS_H2_FRAME_T_HEADERS, sent_headers_frame->type); + ASSERT_FALSE(sent_headers_frame->end_stream); + ASSERT_SUCCESS(s_compare_headers(aws_http_message_get_headers(request), sent_headers_frame->headers)); + + /* fake peer sends response */ + struct aws_http_header response_headers_src[] = { + DEFINE_HEADER(":status", "404"), + DEFINE_HEADER("date", "Wed, 01 Apr 2020 23:02:49 GMT"), + }; + + struct aws_http_headers *response_headers = aws_http_headers_new(allocator); + aws_http_headers_add_array(response_headers, response_headers_src, AWS_ARRAY_SIZE(response_headers_src)); + + /* fake peer sends response headers with end_stream set, which cause the stream to be + * AWS_H2_STREAM_STATE_HALF_CLOSED_REMOTE */ + struct aws_h2_frame *response_frame = + aws_h2_frame_new_headers(allocator, stream_id, response_headers, true /*end_stream*/, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, response_frame)); + + /* AWS_H2_STREAM_STATE_HALF_CLOSED_REMOTE will reject body frame */ + ASSERT_SUCCESS(h2_fake_peer_send_data_frame_str(&s_tester.peer, stream_id, body_src, true /*end_stream*/)); + + /* validate that stream completed with error */ + testing_channel_run_currently_queued_tasks(&s_tester.testing_channel); + ASSERT_INT_EQUALS(AWS_ERROR_HTTP_PROTOCOL_ERROR, stream_tester.on_complete_error_code); + + /* a stream error should not affect the connection */ + ASSERT_TRUE(aws_http_connection_is_open(s_tester.connection)); + + /* validate that stream sent RST_STREAM */ + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + struct h2_decoded_frame *rst_stream_frame = + h2_decode_tester_find_stream_frame(&s_tester.peer.decode, AWS_H2_FRAME_T_RST_STREAM, stream_id, 0, NULL); + ASSERT_INT_EQUALS(AWS_H2_FRAME_T_RST_STREAM, rst_stream_frame->type); + ASSERT_UINT_EQUALS(AWS_H2_ERR_STREAM_CLOSED, rst_stream_frame->error_code); + + /* clean up */ + aws_http_headers_release(response_headers); + aws_http_message_release(request); + client_stream_tester_clean_up(&stream_tester); + aws_input_stream_destroy(request_body); + return s_tester_clean_up(); +} + +TEST_CASE(h2_client_conn_err_stream_frames_received_for_idle_stream) { + ASSERT_SUCCESS(s_tester_init(allocator, ctx)); + + /* fake peer sends connection preface */ + ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + /* fake peer sends response to "idle" (aka doesn't exist yet) stream 99 */ + struct aws_http_header response_headers_src[] = { + DEFINE_HEADER(":status", "200"), + }; + + struct aws_http_headers *response_headers = aws_http_headers_new(allocator); + aws_http_headers_add_array(response_headers, response_headers_src, AWS_ARRAY_SIZE(response_headers_src)); + + struct aws_h2_frame *response_frame = + aws_h2_frame_new_headers(allocator, 99 /*stream_id*/, response_headers, true /* end_stream */, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, response_frame)); + + /* validate that connection has closed due to PROTOCOL_ERROR */ + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_FALSE(aws_http_connection_is_open(s_tester.connection)); + ASSERT_INT_EQUALS( + AWS_ERROR_HTTP_PROTOCOL_ERROR, testing_channel_get_shutdown_error_code(&s_tester.testing_channel)); + + /* validate that client sent GOAWAY */ + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + + struct h2_decoded_frame *goaway = + h2_decode_tester_find_frame(&s_tester.peer.decode, AWS_H2_FRAME_T_GOAWAY, 0, NULL); + ASSERT_NOT_NULL(goaway); + ASSERT_UINT_EQUALS(AWS_H2_ERR_PROTOCOL_ERROR, goaway->error_code); + ASSERT_UINT_EQUALS(0, goaway->goaway_last_stream_id); + + /* clean up */ + aws_http_headers_release(response_headers); + return s_tester_clean_up(); +} + +/* Peer may have sent certain frames (WINDOW_UPDATE and RST_STREAM) before realizing + * that we have closed the stream. These frames should be ignored. */ +TEST_CASE(h2_client_stream_ignores_some_frames_received_soon_after_closing) { + ASSERT_SUCCESS(s_tester_init(allocator, ctx)); + + /* fake peer sends connection preface */ + ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + /* send request */ + struct aws_http_message *request = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(request); + + struct aws_http_header request_headers_src[] = { + DEFINE_HEADER(":method", "GET"), + DEFINE_HEADER(":scheme", "https"), + DEFINE_HEADER(":path", "/"), + }; + aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); + + struct client_stream_tester stream_tester; + ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + uint32_t stream_id = aws_http_stream_get_id(stream_tester.stream); + + /* fake peer sends complete response */ + struct aws_http_header response_headers_src[] = { + DEFINE_HEADER(":status", "404"), + DEFINE_HEADER("date", "Wed, 01 Apr 2020 23:02:49 GMT"), + }; + + struct aws_http_headers *response_headers = aws_http_headers_new(allocator); + aws_http_headers_add_array(response_headers, response_headers_src, AWS_ARRAY_SIZE(response_headers_src)); + + struct aws_h2_frame *peer_frame = + aws_h2_frame_new_headers(allocator, stream_id, response_headers, true /*end_stream*/, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); + + /* fake peer sends WINDOW_UPDATE */ + peer_frame = aws_h2_frame_new_window_update(allocator, stream_id, 99); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); + + /* fake peer sends RST_STREAM */ + peer_frame = aws_h2_frame_new_rst_stream(allocator, stream_id, AWS_H2_ERR_ENHANCE_YOUR_CALM); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); + + /* validate that stream completed successfully. + * the WINDOW_UPDATE and RST_STREAM should be ignored because + * they arrived soon after the client had sent END_STREAM */ + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_TRUE(stream_tester.complete); + ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, stream_tester.on_complete_error_code); + + ASSERT_TRUE(aws_http_connection_is_open(s_tester.connection)); + + /* clean up */ + aws_http_headers_release(response_headers); + aws_http_message_release(request); + client_stream_tester_clean_up(&stream_tester); + return s_tester_clean_up(); +} + +TEST_CASE(h2_client_conn_err_stream_frames_received_after_rst_stream_received) { + ASSERT_SUCCESS(s_tester_init(allocator, ctx)); + + /* fake peer sends connection preface */ + ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + /* send request */ + struct aws_http_message *request = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(request); + + struct aws_http_header request_headers_src[] = { + DEFINE_HEADER(":method", "GET"), + DEFINE_HEADER(":scheme", "https"), + DEFINE_HEADER(":path", "/"), + }; + aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); + + struct client_stream_tester stream_tester; + ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + uint32_t stream_id = aws_http_stream_get_id(stream_tester.stream); + + /* fake peer sends RST_STREAM */ + struct aws_h2_frame *peer_frame = aws_h2_frame_new_rst_stream(allocator, stream_id, AWS_H2_ERR_ENHANCE_YOUR_CALM); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); + + /* fake peer try sending complete response */ + struct aws_http_header response_headers_src[] = { + DEFINE_HEADER(":status", "404"), + DEFINE_HEADER("date", "Wed, 01 Apr 2020 23:02:49 GMT"), + }; + + struct aws_http_headers *response_headers = aws_http_headers_new(allocator); + aws_http_headers_add_array(response_headers, response_headers_src, AWS_ARRAY_SIZE(response_headers_src)); + + peer_frame = aws_h2_frame_new_headers(allocator, stream_id, response_headers, true /*end_stream*/, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); + + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + /* validate the stream compeleted with error */ + ASSERT_TRUE(stream_tester.complete); + ASSERT_INT_EQUALS(AWS_ERROR_HTTP_RST_STREAM_RECEIVED, stream_tester.on_complete_error_code); + /* validate the connection completed with error */ + ASSERT_FALSE(aws_http_connection_is_open(s_tester.connection)); + ASSERT_INT_EQUALS( + AWS_ERROR_HTTP_PROTOCOL_ERROR, testing_channel_get_shutdown_error_code(&s_tester.testing_channel)); + + /* client should send GOAWAY */ + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + struct h2_decoded_frame *goaway = + h2_decode_tester_find_frame(&s_tester.peer.decode, AWS_H2_FRAME_T_GOAWAY, 0, NULL); + ASSERT_NOT_NULL(goaway); + ASSERT_UINT_EQUALS(AWS_H2_ERR_STREAM_CLOSED, goaway->error_code); + + /* clean up */ + aws_http_headers_release(response_headers); + aws_http_message_release(request); + client_stream_tester_clean_up(&stream_tester); + return s_tester_clean_up(); +} + +TEST_CASE(h2_client_stream_receive_info_headers) { + ASSERT_SUCCESS(s_tester_init(allocator, ctx)); + + /* fake peer sends connection preface */ + ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + /* send request */ + struct aws_http_message *request = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(request); + + struct aws_http_header request_headers_src[] = { + DEFINE_HEADER(":method", "GET"), + DEFINE_HEADER(":scheme", "https"), + DEFINE_HEADER(":path", "/"), + }; + aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); + + struct client_stream_tester stream_tester; + ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + uint32_t stream_id = aws_http_stream_get_id(stream_tester.stream); + + /* fake peer sends a info-header-block response */ + struct aws_http_header info_response_headers_src[] = { + DEFINE_HEADER(":status", "100"), + DEFINE_HEADER("date", "Wed, 01 Apr 2020 23:03:49 GMT"), + }; + struct aws_http_headers *info_response_headers = aws_http_headers_new(allocator); + aws_http_headers_add_array( + info_response_headers, info_response_headers_src, AWS_ARRAY_SIZE(info_response_headers_src)); + struct aws_h2_frame *peer_frame = + aws_h2_frame_new_headers(allocator, stream_id, info_response_headers, false /*end_stream*/, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); + + /* check info response */ + ASSERT_INT_EQUALS(1, stream_tester.num_info_responses); + ASSERT_SUCCESS(aws_http_message_set_response_status(stream_tester.info_responses[0], 100)); + struct aws_http_headers *rev_info_headers = aws_http_message_get_headers(stream_tester.info_responses[0]); + ASSERT_SUCCESS(s_compare_headers(info_response_headers, rev_info_headers)); + + /* fake peer sends a main-header-block response */ + struct aws_http_header response_headers_src[] = { + DEFINE_HEADER(":status", "404"), + DEFINE_HEADER("date", "Wed, 01 Apr 2020 23:02:49 GMT"), + }; + struct aws_http_headers *response_headers = aws_http_headers_new(allocator); + aws_http_headers_add_array(response_headers, response_headers_src, AWS_ARRAY_SIZE(response_headers_src)); + peer_frame = aws_h2_frame_new_headers(allocator, stream_id, response_headers, true /*end_stream*/, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); + + /* validate that client received complete response */ + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_TRUE(stream_tester.complete); + ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, stream_tester.on_complete_error_code); + ASSERT_INT_EQUALS(404, stream_tester.response_status); + ASSERT_SUCCESS(s_compare_headers(response_headers, stream_tester.response_headers)); + + /* clean up */ + aws_http_headers_release(response_headers); + aws_http_headers_release(info_response_headers); aws_http_message_release(request); client_stream_tester_clean_up(&stream_tester); return s_tester_clean_up(); } -/* Receiving malformed headers should result in a "Stream Error", not a "Connection Error". */ -TEST_CASE(h2_client_stream_err_malformed_header) { +TEST_CASE(h2_client_stream_err_receive_info_headers_after_main) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); /* fake peer sends connection preface */ @@ -533,83 +951,121 @@ TEST_CASE(h2_client_stream_err_malformed_header) { struct client_stream_tester stream_tester; ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + uint32_t stream_id = aws_http_stream_get_id(stream_tester.stream); - /* fake peer sends response with malformed header */ + /* fake peer sends a main-header-block response */ struct aws_http_header response_headers_src[] = { - DEFINE_HEADER(":STATUS", "404"), /* uppercase name forbidden in h2 */ + DEFINE_HEADER(":status", "404"), + DEFINE_HEADER("date", "Wed, 01 Apr 2020 23:02:49 GMT"), }; struct aws_http_headers *response_headers = aws_http_headers_new(allocator); aws_http_headers_add_array(response_headers, response_headers_src, AWS_ARRAY_SIZE(response_headers_src)); + struct aws_h2_frame *peer_frame = + aws_h2_frame_new_headers(allocator, stream_id, response_headers, false /*end_stream*/, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); - struct aws_h2_frame *response_frame = aws_h2_frame_new_headers( - allocator, aws_http_stream_get_id(stream_tester.stream), response_headers, true /*end_stream*/, 0, NULL); - ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, response_frame)); + /* fake peer sends a info-header-block response */ + struct aws_http_header info_response_headers_src[] = { + DEFINE_HEADER(":status", "100"), + DEFINE_HEADER("date", "Wed, 01 Apr 2020 23:03:49 GMT"), + }; + + struct aws_http_headers *info_response_headers = aws_http_headers_new(allocator); + aws_http_headers_add_array( + info_response_headers, info_response_headers_src, AWS_ARRAY_SIZE(info_response_headers_src)); + peer_frame = aws_h2_frame_new_headers(allocator, stream_id, info_response_headers, false /*end_stream*/, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); - /* validate that stream completed with error */ testing_channel_drain_queued_tasks(&s_tester.testing_channel); + /* validate the stream compeleted with error */ ASSERT_TRUE(stream_tester.complete); ASSERT_INT_EQUALS(AWS_ERROR_HTTP_PROTOCOL_ERROR, stream_tester.on_complete_error_code); - - /* a stream error should not affect the connection */ + /* validate the connection is not affected */ ASSERT_TRUE(aws_http_connection_is_open(s_tester.connection)); /* validate that stream sent RST_STREAM */ ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); - struct h2_decoded_frame *rst_stream_frame = h2_decode_tester_latest_frame(&s_tester.peer.decode); + struct h2_decoded_frame *rst_stream_frame = + h2_decode_tester_find_stream_frame(&s_tester.peer.decode, AWS_H2_FRAME_T_RST_STREAM, stream_id, 0, NULL); ASSERT_INT_EQUALS(AWS_H2_FRAME_T_RST_STREAM, rst_stream_frame->type); ASSERT_UINT_EQUALS(AWS_H2_ERR_PROTOCOL_ERROR, rst_stream_frame->error_code); /* clean up */ aws_http_headers_release(response_headers); + aws_http_headers_release(info_response_headers); aws_http_message_release(request); client_stream_tester_clean_up(&stream_tester); return s_tester_clean_up(); } -TEST_CASE(h2_client_conn_err_stream_frames_received_for_idle_stream) { +TEST_CASE(h2_client_stream_receive_trailing_headers) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); /* fake peer sends connection preface */ ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); testing_channel_drain_queued_tasks(&s_tester.testing_channel); - /* fake peer sends response to "idle" (aka doesn't exist yet) stream 99 */ + /* send request */ + struct aws_http_message *request = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(request); + + struct aws_http_header request_headers_src[] = { + DEFINE_HEADER(":method", "GET"), + DEFINE_HEADER(":scheme", "https"), + DEFINE_HEADER(":path", "/"), + }; + aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); + + struct client_stream_tester stream_tester; + ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + uint32_t stream_id = aws_http_stream_get_id(stream_tester.stream); + + /* fake peer sends a main-header-block response */ struct aws_http_header response_headers_src[] = { - DEFINE_HEADER(":status", "200"), + DEFINE_HEADER(":status", "404"), + DEFINE_HEADER("date", "Wed, 01 Apr 2020 23:02:49 GMT"), }; struct aws_http_headers *response_headers = aws_http_headers_new(allocator); aws_http_headers_add_array(response_headers, response_headers_src, AWS_ARRAY_SIZE(response_headers_src)); - struct aws_h2_frame *response_frame = - aws_h2_frame_new_headers(allocator, 99 /*stream_id*/, response_headers, true /* end_stream */, 0, NULL); - ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, response_frame)); + struct aws_h2_frame *peer_frame = + aws_h2_frame_new_headers(allocator, stream_id, response_headers, false /*end_stream*/, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); - /* validate that connection has closed due to PROTOCOL_ERROR */ - testing_channel_drain_queued_tasks(&s_tester.testing_channel); - ASSERT_FALSE(aws_http_connection_is_open(s_tester.connection)); - ASSERT_INT_EQUALS( - AWS_ERROR_HTTP_PROTOCOL_ERROR, testing_channel_get_shutdown_error_code(&s_tester.testing_channel)); + /* fake peer sends a trailing-header-block response */ + struct aws_http_header response_trailer_src[] = { + DEFINE_HEADER("user-agent", "test"), + }; - /* validate that client sent GOAWAY */ - ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + struct aws_http_headers *response_trailer = aws_http_headers_new(allocator); + aws_http_headers_add_array(response_trailer, response_trailer_src, AWS_ARRAY_SIZE(response_trailer_src)); - struct h2_decoded_frame *goaway = - h2_decode_tester_find_frame(&s_tester.peer.decode, AWS_H2_FRAME_T_GOAWAY, 0, NULL); - ASSERT_NOT_NULL(goaway); - ASSERT_UINT_EQUALS(AWS_H2_ERR_PROTOCOL_ERROR, goaway->error_code); - ASSERT_UINT_EQUALS(0, goaway->goaway_last_stream_id); + peer_frame = aws_h2_frame_new_headers(allocator, stream_id, response_trailer, true /*end_stream*/, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); + + /* validate that client received complete response */ + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_TRUE(stream_tester.complete); + ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, stream_tester.on_complete_error_code); + ASSERT_INT_EQUALS(404, stream_tester.response_status); + ASSERT_SUCCESS(s_compare_headers(response_headers, stream_tester.response_headers)); + ASSERT_SUCCESS(s_compare_headers(response_trailer, stream_tester.response_trailer)); /* clean up */ aws_http_headers_release(response_headers); + aws_http_headers_release(response_trailer); + aws_http_message_release(request); + client_stream_tester_clean_up(&stream_tester); return s_tester_clean_up(); } -/* Peer may have sent certain frames (WINDOW_UPDATE and RST_STREAM) before realizing - * that we have closed the stream. These frames should be ignored. */ -TEST_CASE(h2_client_stream_ignores_some_frames_received_soon_after_closing) { +TEST_CASE(h2_client_stream_err_receive_trailing_before_main) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); /* fake peer sends connection preface */ @@ -633,38 +1089,34 @@ TEST_CASE(h2_client_stream_ignores_some_frames_received_soon_after_closing) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); uint32_t stream_id = aws_http_stream_get_id(stream_tester.stream); - /* fake peer sends complete response */ - struct aws_http_header response_headers_src[] = { - DEFINE_HEADER(":status", "404"), - DEFINE_HEADER("date", "Wed, 01 Apr 2020 23:02:49 GMT"), + /* fake peer sends a trailing-header-block response */ + struct aws_http_header response_trailer_src[] = { + DEFINE_HEADER("user-agent", "test"), }; - struct aws_http_headers *response_headers = aws_http_headers_new(allocator); - aws_http_headers_add_array(response_headers, response_headers_src, AWS_ARRAY_SIZE(response_headers_src)); + struct aws_http_headers *response_trailer = aws_http_headers_new(allocator); + aws_http_headers_add_array(response_trailer, response_trailer_src, AWS_ARRAY_SIZE(response_trailer_src)); struct aws_h2_frame *peer_frame = - aws_h2_frame_new_headers(allocator, stream_id, response_headers, true /*end_stream*/, 0, NULL); - ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); - - /* fake peer sends WINDOW_UPDATE */ - peer_frame = aws_h2_frame_new_window_update(allocator, stream_id, 99); - ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); - - /* fake peer sends RST_STREAM */ - peer_frame = aws_h2_frame_new_rst_stream(allocator, stream_id, AWS_H2_ERR_ENHANCE_YOUR_CALM); + aws_h2_frame_new_headers(allocator, stream_id, response_trailer, true /*end_stream*/, 0, NULL); ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame)); - /* validate that stream completed successfully. - * the WINDOW_UPDATE and RST_STREAM should be ignored because - * they arrived soon after the client had sent END_STREAM */ testing_channel_drain_queued_tasks(&s_tester.testing_channel); + /* validate the stream compeleted with error */ ASSERT_TRUE(stream_tester.complete); - ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, stream_tester.on_complete_error_code); - + ASSERT_INT_EQUALS(AWS_ERROR_HTTP_PROTOCOL_ERROR, stream_tester.on_complete_error_code); + /* validate the connection is not affected */ ASSERT_TRUE(aws_http_connection_is_open(s_tester.connection)); + /* validate that stream sent RST_STREAM */ + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + struct h2_decoded_frame *rst_stream_frame = + h2_decode_tester_find_stream_frame(&s_tester.peer.decode, AWS_H2_FRAME_T_RST_STREAM, stream_id, 0, NULL); + ASSERT_INT_EQUALS(AWS_H2_FRAME_T_RST_STREAM, rst_stream_frame->type); + ASSERT_UINT_EQUALS(AWS_H2_ERR_PROTOCOL_ERROR, rst_stream_frame->error_code); + /* clean up */ - aws_http_headers_release(response_headers); + aws_http_headers_release(response_trailer); aws_http_message_release(request); client_stream_tester_clean_up(&stream_tester); return s_tester_clean_up(); @@ -1831,56 +2283,86 @@ TEST_CASE(h2_client_conn_err_initial_window_size_cause_window_exceed_max) { return s_tester_clean_up(); } -/* A request stream that receives RST_STREAM should terminate */ -TEST_CASE(h2_client_stream_err_receive_rst_stream) { +/* A server MAY finish the response before client done sending, and client just keep sending the rest of request. */ +TEST_CASE(h2_client_stream_receive_end_stream_before_done_sending) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); - /* fake peer sends connection preface */ + /* get connection preface and acks out of the way */ ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); - /* send request */ + /* get request ready */ struct aws_http_message *request = aws_http_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { - DEFINE_HEADER(":method", "GET"), + DEFINE_HEADER(":method", "POST"), DEFINE_HEADER(":scheme", "https"), DEFINE_HEADER(":path", "/"), }; aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); + /* use a stalled body-stream so our test can send the response before the request is completely sent */ + const char *body_src = "hello"; + struct aws_byte_cursor body_cursor = aws_byte_cursor_from_c_str(body_src); + struct aws_input_stream *request_body = aws_input_stream_new_tester(allocator, body_cursor); + aws_http_message_set_body_stream(request, request_body); + aws_input_stream_tester_set_max_bytes_per_read(request_body, 1); + struct client_stream_tester stream_tester; ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); - testing_channel_drain_queued_tasks(&s_tester.testing_channel); + /* execute 1 event-loop tick, 1 byte of the body and header should be written */ + testing_channel_run_currently_queued_tasks(&s_tester.testing_channel); uint32_t stream_id = aws_http_stream_get_id(stream_tester.stream); + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + ASSERT_NOT_NULL( + h2_decode_tester_find_frame(&s_tester.peer.decode, AWS_H2_FRAME_T_HEADERS, 0 /*search_start_idx*/, NULL)); + struct h2_decoded_frame *sent_data_frame = + h2_decode_tester_find_frame(&s_tester.peer.decode, AWS_H2_FRAME_T_DATA, 0 /*search_start_idx*/, NULL); + ASSERT_FALSE(sent_data_frame->end_stream); + ASSERT_TRUE(aws_byte_buf_eq_c_str(&sent_data_frame->data, "h")); + /* fake peer sends complete response */ + struct aws_http_header response_headers_src[] = { + DEFINE_HEADER(":status", "404"), + }; + /* stop stalling the input stream */ + aws_input_stream_tester_set_max_bytes_per_read(request_body, 5); + size_t frames_count = h2_decode_tester_frame_count(&s_tester.peer.decode); - /* fake peer sends RST_STREAM */ - struct aws_h2_frame *rst_stream = aws_h2_frame_new_rst_stream(allocator, stream_id, AWS_H2_ERR_HTTP_1_1_REQUIRED); - ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, rst_stream)); + struct aws_http_headers *response_headers = aws_http_headers_new(allocator); + aws_http_headers_add_array(response_headers, response_headers_src, AWS_ARRAY_SIZE(response_headers_src)); - /* validate that stream completed with error */ + struct aws_h2_frame *response_frame = + aws_h2_frame_new_headers(allocator, stream_id, response_headers, true /*end_stream*/, 0, NULL); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, response_frame)); + + /* No rst stream sent, we wait until the client finish sending body */ + /* validate the client request completes successfully */ testing_channel_drain_queued_tasks(&s_tester.testing_channel); ASSERT_TRUE(stream_tester.complete); - ASSERT_INT_EQUALS(AWS_ERROR_HTTP_RST_STREAM_RECEIVED, stream_tester.on_complete_error_code); - - /* a stream error should not affect the connection */ - ASSERT_TRUE(aws_http_connection_is_open(s_tester.connection)); + ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, stream_tester.on_complete_error_code); + ASSERT_INT_EQUALS(404, stream_tester.response_status); - /* validate that stream did NOT send RST_STREAM */ + /* Check the rest of the body received by peer */ ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); - ASSERT_NULL(h2_decode_tester_find_frame(&s_tester.peer.decode, AWS_H2_FRAME_T_RST_STREAM, 0, NULL)); + struct h2_decoded_frame *rest_data_frame = h2_decode_tester_find_frame( + &s_tester.peer.decode, AWS_H2_FRAME_T_DATA, frames_count /*search_start_idx*/, NULL); + ASSERT_TRUE(rest_data_frame->end_stream); + ASSERT_TRUE(aws_byte_buf_eq_c_str(&rest_data_frame->data, "ello")); /* clean up */ - aws_http_message_release(request); + aws_http_headers_release(response_headers); client_stream_tester_clean_up(&stream_tester); + aws_http_message_release(request); + aws_input_stream_destroy(request_body); return s_tester_clean_up(); } /* A server MAY request that the client abort transmission of a request without error by sending a * RST_STREAM with an error code of NO_ERROR after sending a complete response. */ -TEST_CASE(h2_client_stream_receive_rst_stream_after_complete_response_ok) { +TEST_CASE(h2_client_stream_receive_end_stream_and_rst_before_done_sending) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); /* get connection preface and acks out of the way */ @@ -1936,9 +2418,11 @@ TEST_CASE(h2_client_stream_receive_rst_stream_after_complete_response_ok) { /* validate the client request completes successfully */ testing_channel_drain_queued_tasks(&s_tester.testing_channel); ASSERT_TRUE(stream_tester.complete); - ASSERT_TRUE(stream_tester.complete); ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, stream_tester.on_complete_error_code); ASSERT_INT_EQUALS(404, stream_tester.response_status); + /* Check no data frame received by the peer */ + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + ASSERT_NULL(h2_decode_tester_find_frame(&s_tester.peer.decode, AWS_H2_FRAME_T_DATA, 0 /*search_start_idx*/, NULL)); /* clean up */ aws_http_headers_release(response_headers); @@ -1948,6 +2432,98 @@ TEST_CASE(h2_client_stream_receive_rst_stream_after_complete_response_ok) { return s_tester_clean_up(); } +TEST_CASE(h2_client_stream_err_input_stream_failure) { + ASSERT_SUCCESS(s_tester_init(allocator, ctx)); + + /* get connection preface and acks out of the way */ + ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + + /* get request ready */ + struct aws_http_message *request = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(request); + + struct aws_http_header request_headers_src[] = { + DEFINE_HEADER(":method", "POST"), + DEFINE_HEADER(":scheme", "https"), + DEFINE_HEADER(":path", "/"), + }; + aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); + + /* use a stalled body-stream so our test can send the response before the request is completely sent */ + const char *body_src = "hello"; + struct aws_byte_cursor body_cursor = aws_byte_cursor_from_c_str(body_src); + struct aws_input_stream *request_body = aws_input_stream_new_tester(allocator, body_cursor); + aws_http_message_set_body_stream(request, request_body); + aws_input_stream_tester_set_reading_broken(request_body, true /*is_broken*/); + struct client_stream_tester stream_tester; + ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + + /* validate that stream completed with error */ + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_TRUE(stream_tester.complete); + ASSERT_INT_EQUALS(AWS_IO_STREAM_READ_FAILED, stream_tester.on_complete_error_code); + /* a stream error should not affect the connection */ + ASSERT_TRUE(aws_http_connection_is_open(s_tester.connection)); + /* validate that stream sent RST_STREAM */ + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + struct h2_decoded_frame *rst_stream_frame = h2_decode_tester_latest_frame(&s_tester.peer.decode); + ASSERT_INT_EQUALS(AWS_H2_ERR_INTERNAL_ERROR, rst_stream_frame->error_code); + /* clean up */ + client_stream_tester_clean_up(&stream_tester); + aws_http_message_release(request); + aws_input_stream_destroy(request_body); + return s_tester_clean_up(); +} + +/* A request stream that receives RST_STREAM should terminate */ +TEST_CASE(h2_client_stream_err_receive_rst_stream) { + ASSERT_SUCCESS(s_tester_init(allocator, ctx)); + + /* fake peer sends connection preface */ + ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + /* send request */ + struct aws_http_message *request = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(request); + + struct aws_http_header request_headers_src[] = { + DEFINE_HEADER(":method", "GET"), + DEFINE_HEADER(":scheme", "https"), + DEFINE_HEADER(":path", "/"), + }; + aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); + + struct client_stream_tester stream_tester; + ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + uint32_t stream_id = aws_http_stream_get_id(stream_tester.stream); + + /* fake peer sends RST_STREAM */ + struct aws_h2_frame *rst_stream = aws_h2_frame_new_rst_stream(allocator, stream_id, AWS_H2_ERR_HTTP_1_1_REQUIRED); + ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, rst_stream)); + + /* validate that stream completed with error */ + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_TRUE(stream_tester.complete); + ASSERT_INT_EQUALS(AWS_ERROR_HTTP_RST_STREAM_RECEIVED, stream_tester.on_complete_error_code); + + /* a stream error should not affect the connection */ + ASSERT_TRUE(aws_http_connection_is_open(s_tester.connection)); + + /* validate that stream did NOT send RST_STREAM */ + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + ASSERT_NULL(h2_decode_tester_find_frame(&s_tester.peer.decode, AWS_H2_FRAME_T_RST_STREAM, 0, NULL)); + + /* clean up */ + aws_http_message_release(request); + client_stream_tester_clean_up(&stream_tester); + return s_tester_clean_up(); +} + /* We don't fully support PUSH_PROMISE, so we automatically send RST_STREAM to reject any promised streams. * Why, you ask, don't we simply send SETTINGS_ENABLE_PUSH=0 in the initial SETTINGS frame and call it a day? * Because it's theoretically possible for a server to start sending PUSH_PROMISE frames in the initial