Skip to content

Commit 8c12b8c

Browse files
TingDaoKgraebmJonathanHensonJustin Boswell
authored
Ping frame for H2 (#205)
* revamp encoder API (wip) * hpack pre-encode cmd. Do encoding in 2 passes, so we know exactly how much buffer space we'll need. I will probably revert this. I thought this was clever, but seeing it implemented it's just so much more complicated than writing everything to a dynamic buffer, then copying it into the aws_io_mesage. * simplify decoder state machine loop * hpack encoding expands output buffer ditch 2 pass approach, it was just too complicated * Revamp HEADERS/PUSH_PROMISE/DATA frames * all tests passing again * Exposed options for toggling read back pressure behavior, updated to match aws-c-io api changes. (#194) * Fix bug when new request has same memory address as old request. (#195) As seen in real life * Enabled compilation on VS 2015 (#196) * Enabled compilation on VS 2015 * Fix VS narrowing warning * Updated to v0.5.3 of builder * Fix proxies connect and tests (#198) Ignore connection: close on 200/OK responses to a CONNECT Request, since the proxy is obviously drunk and needs to hail an uber to get home from the bar safely. Fix the broken tests from the tcp back pressure refactor in aws-c-io. * Fix fuzz tests * clang-format * tweaks * tweaks * Simplify how most frames are encoded: - Use common struct - Pre-encode the entire frame - Incrementally copy that to aws_io_message whenever encode() is called. This is simpler/better because: 1) more shared code 2) unique payload-writing code all goes in the one new() function, instead of being spread across the new() and encode() functions 3) less chance of incorrect size calculations, since we're encoding to a buffer of the exact correct length * THANK YOU MSVC COMPILER WARNING YOU SAVED MY ASS * replaced lots of AWS_ERROR_SHORT_BUFFER error-handling with asserts. If this kind of error happens now, it's programmer error * compiler warning * insert ping ACK to the front of the queue * auto send ping ack frame back * h2_test_helper.h Extract useful machinery from test_h2_decoder.c for use with other tests. * merge with master * test_h2_client.c is using fake peer * test_for_ping_ack * more h2_fake_peer functionality * msvc warning * delete the message * prototype function * tiny changes for making the code clear * clang format * Stop just the reading direction. * error check&udpate read window * move comments * remove redundant connection id and style change * LOGF->LOG Co-authored-by: Michael Graeb <graebm@amazon.com> Co-authored-by: Jonathan M. Henson <henso@amazon.com> Co-authored-by: Justin Boswell <boswej@amazon.com> Co-authored-by: Dengke Tang <dengket@amazon.com>
1 parent 57883a8 commit 8c12b8c

File tree

5 files changed

+113
-6
lines changed

5 files changed

+113
-6
lines changed

include/aws/http/private/h2_frames.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ struct aws_h2_frame {
115115
struct aws_linked_list_node node;
116116
enum aws_h2_frame_type type;
117117
uint32_t stream_id;
118+
119+
/* If true, frame will be sent before those with normal priority.
120+
* Useful for frames like PING ACK where low latency is important. */
121+
bool high_priority;
118122
};
119123

120124
/* A h2 setting and its value, used in SETTINGS frame */

source/h2_connection.c

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ static struct aws_http_stream *s_connection_make_request(
6161
static void s_cross_thread_work_task(struct aws_channel_task *task, void *arg, enum aws_task_status status);
6262
static void s_outgoing_frames_task(struct aws_channel_task *task, void *arg, enum aws_task_status status);
6363

64+
static int s_decoder_on_ping(uint8_t opaque_data[AWS_H2_PING_DATA_SIZE], void *userdata);
65+
6466
static struct aws_http_connection_vtable s_h2_connection_vtable = {
6567
.channel_handler_vtable =
6668
{
@@ -84,6 +86,7 @@ static struct aws_http_connection_vtable s_h2_connection_vtable = {
8486

8587
static const struct aws_h2_decoder_vtable s_h2_decoder_vtable = {
8688
.on_data = NULL,
89+
.on_ping = s_decoder_on_ping,
8790
};
8891

8992
static void s_lock_synced_data(struct aws_h2_connection *connection) {
@@ -294,7 +297,22 @@ void aws_h2_connection_enqueue_outgoing_frame(struct aws_h2_connection *connecti
294297
AWS_PRECONDITION(frame->type != AWS_H2_FRAME_T_DATA);
295298
AWS_PRECONDITION(aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel));
296299

297-
aws_linked_list_push_back(&connection->thread_data.outgoing_frames_queue, &frame->node);
300+
if (frame->high_priority) {
301+
/* Check from the head of the queue, and find a node with normal priority, and insert before it */
302+
struct aws_linked_list_node *iter = aws_linked_list_begin(&connection->thread_data.outgoing_frames_queue);
303+
/* one past the last element */
304+
const struct aws_linked_list_node *end = aws_linked_list_end(&connection->thread_data.outgoing_frames_queue);
305+
while (iter != end) {
306+
struct aws_h2_frame *frame_i = AWS_CONTAINER_OF(iter, struct aws_h2_frame, node);
307+
if (!frame_i->high_priority) {
308+
break;
309+
}
310+
iter = iter->next;
311+
}
312+
aws_linked_list_insert_before(iter, &frame->node);
313+
} else {
314+
aws_linked_list_push_back(&connection->thread_data.outgoing_frames_queue, &frame->node);
315+
}
298316
}
299317

300318
static void s_on_channel_write_complete(
@@ -492,6 +510,24 @@ static void s_try_write_outgoing_frames(struct aws_h2_connection *connection) {
492510
s_outgoing_frames_task(&connection->outgoing_frames_task, connection, AWS_TASK_STATUS_RUN_READY);
493511
}
494512

513+
/* Decoder callbacks */
514+
static int s_decoder_on_ping(uint8_t opaque_data[AWS_H2_PING_DATA_SIZE], void *userdata) {
515+
struct aws_h2_connection *connection = userdata;
516+
517+
/* send a PING frame with the ACK flag set in response, with an identical payload. */
518+
struct aws_h2_frame *ping_ack_frame = aws_h2_frame_new_ping(connection->base.alloc, true, opaque_data);
519+
if (!ping_ack_frame) {
520+
goto error;
521+
}
522+
523+
aws_h2_connection_enqueue_outgoing_frame(connection, ping_ack_frame);
524+
s_try_write_outgoing_frames(connection);
525+
return AWS_OP_SUCCESS;
526+
error:
527+
CONNECTION_LOGF(ERROR, connection, "Ping ACK frame failed to be sent, error %s", aws_error_name(aws_last_error()));
528+
return AWS_OP_ERR;
529+
}
530+
495531
static int s_send_connection_preface_client_string(struct aws_h2_connection *connection) {
496532

497533
/* Just send the magic string on its own aws_io_message. */
@@ -735,16 +771,51 @@ static int s_handler_process_read_message(
735771
struct aws_channel_handler *handler,
736772
struct aws_channel_slot *slot,
737773
struct aws_io_message *message) {
738-
739-
(void)handler;
740774
(void)slot;
741-
(void)message;
775+
struct aws_h2_connection *connection = handler->impl;
776+
777+
CONNECTION_LOGF(TRACE, connection, "Begin processing message of size %zu.", message->message_data.len);
778+
779+
if (connection->thread_data.is_reading_stopped) {
780+
CONNECTION_LOG(ERROR, connection, "Cannot process message because connection is shutting down.");
781+
aws_raise_error(AWS_ERROR_HTTP_CONNECTION_CLOSED);
782+
goto shutdown;
783+
}
784+
785+
struct aws_byte_cursor message_cursor = aws_byte_cursor_from_buf(&message->message_data);
786+
if (aws_h2_decode(connection->thread_data.decoder, &message_cursor)) {
787+
CONNECTION_LOGF(
788+
ERROR,
789+
connection,
790+
"Decoding message failed, error %d (%s). Closing connection",
791+
aws_last_error(),
792+
aws_error_name(aws_last_error()));
793+
}
742794

743795
/* HTTP/2 protocol uses WINDOW_UPDATE frames to coordinate data rates with peer,
744796
* so we can just keep the aws_channel's read-window wide open */
745-
/* #TODO update read window by however much we just read */
797+
if (aws_channel_slot_increment_read_window(slot, message->message_data.len)) {
798+
CONNECTION_LOGF(
799+
ERROR,
800+
connection,
801+
"Incrementing read window failed, error %d (%s). Closing connection",
802+
aws_last_error(),
803+
aws_error_name(aws_last_error()));
804+
}
746805

747-
return aws_raise_error(AWS_ERROR_UNIMPLEMENTED);
806+
/* release message */
807+
if (message) {
808+
aws_mem_release(message->allocator, message);
809+
message = NULL;
810+
}
811+
return AWS_OP_SUCCESS;
812+
shutdown:
813+
if (message) {
814+
aws_mem_release(message->allocator, message);
815+
}
816+
/* Stop reading, because the reading error happans here */
817+
s_stop(connection, true /*stop_reading*/, false /*stop_writing*/, true /*schedule_shutdown*/, aws_last_error());
818+
return AWS_OP_SUCCESS;
748819
}
749820

750821
static int s_handler_process_write_message(

source/h2_frames.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,8 @@ struct aws_h2_frame *aws_h2_frame_new_ping(
985985
writes_ok &= aws_byte_buf_write(&frame->encoded_buf, opaque_data, AWS_H2_PING_DATA_SIZE);
986986
AWS_ASSERT(writes_ok);
987987

988+
/* PING responses SHOULD be given higher priority than any other frame */
989+
frame->base.high_priority = ack;
988990
return &frame->base;
989991
}
990992

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ add_h2_decoder_test_set(h2_decoder_err_bad_preface_from_client_3)
300300
add_test_case(h2_client_sanity_check)
301301
add_test_case(h2_client_request_create)
302302
add_test_case(h2_client_connection_preface_sent)
303+
add_test_case(h2_client_ping_ack)
303304

304305
add_test_case(server_new_destroy)
305306
add_test_case(connection_setup_shutdown)

tests/test_h2_client.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,32 @@ TEST_CASE(h2_client_connection_preface_sent) {
126126

127127
return s_tester_clean_up();
128128
}
129+
130+
/* Test that client will automatically send the PING ACK frame back, when the PING frame is received */
131+
TEST_CASE(h2_client_ping_ack) {
132+
ASSERT_SUCCESS(s_tester_init(allocator, ctx));
133+
134+
/* Connection preface requires that SETTINGS be sent first (RFC-7540 3.5). */
135+
ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer));
136+
137+
uint8_t opaque_data[AWS_H2_PING_DATA_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7};
138+
139+
struct aws_h2_frame *frame = aws_h2_frame_new_ping(allocator, false /*ack*/, opaque_data);
140+
ASSERT_NOT_NULL(frame);
141+
142+
ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, frame));
143+
144+
/* Have the fake peer to run its decoder on what the client has written.
145+
* The decoder will raise an error if it doesn't receive the "client connection preface string" first. */
146+
ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer));
147+
148+
/* Now check that client sent PING ACK frame, it should be the latest frame received by peer
149+
* The last frame should be a ping type with ack on, and identical payload */
150+
struct h2_decoded_frame *latest_frame = h2_decode_tester_latest_frame(&s_tester.peer.decode);
151+
ASSERT_UINT_EQUALS(AWS_H2_FRAME_T_PING, latest_frame->type);
152+
ASSERT_TRUE(latest_frame->ack);
153+
ASSERT_BIN_ARRAYS_EQUALS(opaque_data, AWS_H2_PING_DATA_SIZE, latest_frame->ping_opaque_data, AWS_H2_PING_DATA_SIZE);
154+
155+
return s_tester_clean_up();
156+
}
157+
/* TODO: test that ping response is sent with higher priority than any other frame */

0 commit comments

Comments
 (0)