From 1abf4741ac0738e61a990fbb1eca6bca86c9c567 Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Fri, 24 Apr 2020 15:50:51 -0700 Subject: [PATCH 1/7] test ping ack with higher priority --- tests/CMakeLists.txt | 1 + tests/test_h2_client.c | 73 +++++++++++++++++++++++++++++++++++------- 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2886d93c6..3b2feaec5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -331,6 +331,7 @@ 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) diff --git a/tests/test_h2_client.c b/tests/test_h2_client.c index 2217aaf76..3d1c6a101 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,60 @@ 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 +274,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)); From fbd98830ea5cfb006e4cd66471bfdbcff9915f1a Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Wed, 29 Apr 2020 22:51:00 -0700 Subject: [PATCH 2/7] trivial updates for elasticurl --- bin/elasticurl/main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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; From 3b6ab72e6870210d345cd50a10f87d811ea599da Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Thu, 30 Apr 2020 17:49:18 -0700 Subject: [PATCH 3/7] stream state&headers test --- tests/CMakeLists.txt | 12 +- tests/test_h2_client.c | 408 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 411 insertions(+), 9 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3b2feaec5..67f6a2728 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -339,15 +339,15 @@ 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) diff --git a/tests/test_h2_client.c b/tests/test_h2_client.c index 3d1c6a101..87ec31840 100644 --- a/tests/test_h2_client.c +++ b/tests/test_h2_client.c @@ -237,9 +237,9 @@ TEST_CASE(h2_client_ping_ack_higher_priority) { 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)); @@ -247,7 +247,8 @@ TEST_CASE(h2_client_ping_ack_higher_priority) { 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); + 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); @@ -619,6 +620,86 @@ TEST_CASE(h2_client_stream_err_malformed_header) { 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); + 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)); + /* validate that stream completed with error */ + testing_channel_run_currently_queued_tasks(&s_tester.testing_channel); + + /* 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)); @@ -721,6 +802,327 @@ TEST_CASE(h2_client_stream_ignores_some_frames_received_soon_after_closing) { 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)); + + /* 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)); + + /* 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)); + + /* 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_stream_err_receive_info_headers_after_main) { + 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 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)); + 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)); + + /* 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)); + + 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); + /* 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(info_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_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); + + /* 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", "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)); + + /* 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_trailer = aws_http_headers_new(allocator); + aws_http_headers_add_array(response_trailer, response_trailer_src, AWS_ARRAY_SIZE(response_trailer_src)); + + 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(); +} + +TEST_CASE(h2_client_stream_err_receive_trailing_before_main) { + 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 trailing-header-block response */ + struct aws_http_header response_trailer_src[] = { + DEFINE_HEADER("user-agent", "test"), + }; + + 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_trailer, 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_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_trailer); + aws_http_message_release(request); + client_stream_tester_clean_up(&stream_tester); + return s_tester_clean_up(); +} + /* Test receiving a response with DATA frames */ TEST_CASE(h2_client_stream_receive_data) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); From 3addecedd6bf9fd376f2f7a438b4fa2ec352c1a2 Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Fri, 1 May 2020 11:55:19 -0700 Subject: [PATCH 4/7] tests for input stream & receive before done sending --- source/h2_connection.c | 1 - tests/CMakeLists.txt | 8 +- tests/test_h2_client.c | 171 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 155 insertions(+), 25 deletions(-) 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/tests/CMakeLists.txt b/tests/CMakeLists.txt index 67f6a2728..bb5af3f97 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -336,7 +336,6 @@ 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) add_test_case(h2_client_stream_err_state_forbids_frame) @@ -363,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 87ec31840..15981bd53 100644 --- a/tests/test_h2_client.c +++ b/tests/test_h2_client.c @@ -2284,56 +2284,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 */ @@ -2389,9 +2419,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); @@ -2401,6 +2433,107 @@ 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_max_bytes_per_read(request_body, 1); + + struct client_stream_tester stream_tester; + ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + + /* 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); + 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")); + + aws_input_stream_tester_set_reading_broken(request_body, true /*is_broken*/); + + /* validate connection shutdown due to error */ + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + ASSERT_FALSE(aws_http_connection_is_open(s_tester.connection)); + ASSERT_INT_EQUALS( + AWS_IO_STREAM_READ_FAILED, testing_channel_get_shutdown_error_code(&s_tester.testing_channel)); + /* 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 From f040c6378205637e9710edcbf5261e68de3ee6e8 Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Fri, 1 May 2020 16:06:07 -0700 Subject: [PATCH 5/7] clang format --- tests/test_h2_client.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_h2_client.c b/tests/test_h2_client.c index 15981bd53..c00c7f322 100644 --- a/tests/test_h2_client.c +++ b/tests/test_h2_client.c @@ -2478,8 +2478,7 @@ TEST_CASE(h2_client_stream_err_input_stream_failure) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); ASSERT_FALSE(aws_http_connection_is_open(s_tester.connection)); - ASSERT_INT_EQUALS( - AWS_IO_STREAM_READ_FAILED, testing_channel_get_shutdown_error_code(&s_tester.testing_channel)); + ASSERT_INT_EQUALS(AWS_IO_STREAM_READ_FAILED, testing_channel_get_shutdown_error_code(&s_tester.testing_channel)); /* clean up */ client_stream_tester_clean_up(&stream_tester); aws_http_message_release(request); From 54494fdc67ef7e51155e5c193ceeca2b299b1c05 Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Tue, 5 May 2020 14:43:16 -0700 Subject: [PATCH 6/7] stream_err for input_stream_failure --- source/h2_stream.c | 3 ++- tests/test_h2_client.c | 41 +++++++++++++++++------------------------ 2 files changed, 19 insertions(+), 25 deletions(-) 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/test_h2_client.c b/tests/test_h2_client.c index c00c7f322..03da881ed 100644 --- a/tests/test_h2_client.c +++ b/tests/test_h2_client.c @@ -641,6 +641,7 @@ TEST_CASE(h2_client_stream_err_state_forbids_frame) { 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); @@ -901,6 +902,12 @@ TEST_CASE(h2_client_stream_receive_info_headers) { 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"), @@ -918,12 +925,6 @@ TEST_CASE(h2_client_stream_receive_info_headers) { ASSERT_INT_EQUALS(404, stream_tester.response_status); ASSERT_SUCCESS(s_compare_headers(response_headers, stream_tester.response_headers)); - /* 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)); - /* clean up */ aws_http_headers_release(response_headers); aws_http_headers_release(info_response_headers); @@ -2457,28 +2458,20 @@ TEST_CASE(h2_client_stream_err_input_stream_failure) { 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); - + 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)); - /* 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); - 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")); - - aws_input_stream_tester_set_reading_broken(request_body, true /*is_broken*/); - - /* validate connection shutdown due to error */ + /* validate that stream completed with error */ testing_channel_drain_queued_tasks(&s_tester.testing_channel); - - ASSERT_FALSE(aws_http_connection_is_open(s_tester.connection)); - ASSERT_INT_EQUALS(AWS_IO_STREAM_READ_FAILED, testing_channel_get_shutdown_error_code(&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); From 2c68064070befa6a7612eb13ee68b30e92261365 Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Tue, 5 May 2020 15:53:22 -0700 Subject: [PATCH 7/7] another copy/paste error --- tests/test_h2_client.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_h2_client.c b/tests/test_h2_client.c index 03da881ed..880a29851 100644 --- a/tests/test_h2_client.c +++ b/tests/test_h2_client.c @@ -673,8 +673,6 @@ TEST_CASE(h2_client_stream_err_state_forbids_frame) { 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)); - /* validate that stream completed with error */ - testing_channel_run_currently_queued_tasks(&s_tester.testing_channel); /* 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*/));