Skip to content

Commit e684773

Browse files
authored
internal settings change function (#232)
settings change from internal. - create a queue for pending settings - when we want to change our settings, we send a setting frame to our peer, then push the pending settings to the back of the queue. - when the setting ACK is received from the peer, we pop the front one from the queue and apply the settings to our connection. Co-authored-by: Dengke Tang <[email protected]>
1 parent b30e25d commit e684773

File tree

10 files changed

+257
-54
lines changed

10 files changed

+257
-54
lines changed

include/aws/http/private/h2_connection.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ struct aws_h2_connection {
4848
/* My settings to send/sent to peer, which affects the decoding */
4949
uint32_t settings_self[AWS_H2_SETTINGS_END_RANGE];
5050

51+
/* List using h2_pending_settings.node
52+
* Contains settings waiting to be ACKed by peer and applied */
53+
struct aws_linked_list pending_settings_queue;
54+
5155
/* Most recent stream-id that was initiated by peer */
5256
uint32_t latest_peer_initiated_stream_id;
5357

@@ -151,6 +155,12 @@ AWS_EXTERN_C_END
151155

152156
/* Private functions called from multiple .c files... */
153157

158+
/* Internal API for changing self settings of the connection */
159+
int aws_h2_connection_change_settings(
160+
struct aws_h2_connection *connection,
161+
const struct aws_h2_frame_setting *setting_array,
162+
size_t num_settings);
163+
154164
/**
155165
* Enqueue outgoing frame.
156166
* Connection takes ownership of frame.

include/aws/http/private/h2_frames.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,6 @@ struct aws_h2_frame_encoder {
154154

155155
/* Settings for frame encoder, which is based on the settings received from peer */
156156
struct {
157-
/* the maximum size of the header compression table used to decode header blocks */
158-
uint32_t header_table_size;
159157
/* the size of the largest frame payload */
160158
uint32_t max_frame_size;
161159
} settings;

include/aws/http/private/h2_stream.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ struct aws_h2_stream *aws_h2_stream_new_request(
7878

7979
enum aws_h2_stream_state aws_h2_stream_get_state(const struct aws_h2_stream *stream);
8080

81-
int aws_h2_stream_window_size_change(struct aws_h2_stream *stream, int32_t size_changed);
81+
struct aws_h2err aws_h2_stream_window_size_change(struct aws_h2_stream *stream, int32_t size_changed, bool self);
8282

8383
/* Connection is ready to send frames from stream now */
8484
int aws_h2_stream_on_activated(struct aws_h2_stream *stream, bool *out_has_outgoing_data);

include/aws/http/private/hpack.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ int aws_hpack_resize_dynamic_table(struct aws_hpack_context *context, size_t new
131131
* two header blocks. The dynamic table resize and the dynamic table size update entry will be handled properly when we
132132
* encode the next header block */
133133
AWS_HTTP_API
134-
void aws_hpack_set_max_table_size(struct aws_hpack_context *context, size_t new_max_size);
134+
void aws_hpack_set_max_table_size(struct aws_hpack_context *context, uint32_t new_max_size);
135+
136+
AWS_HTTP_API
137+
void aws_hpack_set_protocol_max_size_setting(struct aws_hpack_context *context, uint32_t setting_max_size);
135138

136139
AWS_HTTP_API
137140
void aws_hpack_set_huffman_mode(struct aws_hpack_context *context, enum aws_hpack_huffman_mode mode);

source/h2_connection.c

Lines changed: 143 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ static int s_record_closed_stream(
6868
struct aws_h2_connection *connection,
6969
uint32_t stream_id,
7070
enum aws_h2_stream_closed_when closed_when);
71+
static void s_try_write_outgoing_frames(struct aws_h2_connection *connection);
7172

7273
static struct aws_h2err s_decoder_on_headers_begin(uint32_t stream_id, void *userdata);
7374
static struct aws_h2err s_decoder_on_headers_i(
@@ -239,6 +240,7 @@ static struct aws_h2_connection *s_connection_new(
239240
aws_linked_list_init(&connection->synced_data.pending_stream_list);
240241

241242
aws_linked_list_init(&connection->thread_data.outgoing_streams_list);
243+
aws_linked_list_init(&connection->thread_data.pending_settings_queue);
242244
aws_linked_list_init(&connection->thread_data.stalled_window_streams_list);
243245
aws_linked_list_init(&connection->thread_data.outgoing_frames_queue);
244246

@@ -340,6 +342,12 @@ struct aws_http_connection *aws_http_connection_new_http2_client(
340342
return &connection->base;
341343
}
342344

345+
struct h2_pending_settings {
346+
struct aws_h2_frame_setting *settings_array;
347+
size_t num_settings;
348+
struct aws_linked_list_node node;
349+
};
350+
343351
static void s_handler_destroy(struct aws_channel_handler *handler) {
344352
struct aws_h2_connection *connection = handler->impl;
345353
CONNECTION_LOG(TRACE, connection, "Destroying connection");
@@ -348,6 +356,12 @@ static void s_handler_destroy(struct aws_channel_handler *handler) {
348356
AWS_ASSERT(
349357
!aws_hash_table_is_valid(&connection->thread_data.active_streams_map) ||
350358
aws_hash_table_get_entry_count(&connection->thread_data.active_streams_map) == 0);
359+
while (!aws_linked_list_empty(&connection->thread_data.pending_settings_queue)) {
360+
/* Some settings are sent, but peer never sends ACK back. It's not an error. We just have to clean it up */
361+
struct aws_linked_list_node *node = aws_linked_list_pop_front(&connection->thread_data.pending_settings_queue);
362+
struct h2_pending_settings *pending_settings = AWS_CONTAINER_OF(node, struct h2_pending_settings, node);
363+
aws_mem_release(connection->base.alloc, pending_settings);
364+
}
351365
AWS_ASSERT(aws_linked_list_empty(&connection->thread_data.stalled_window_streams_list));
352366
AWS_ASSERT(aws_linked_list_empty(&connection->thread_data.outgoing_streams_list));
353367
AWS_ASSERT(aws_linked_list_empty(&connection->synced_data.pending_stream_list));
@@ -368,6 +382,65 @@ static void s_handler_destroy(struct aws_channel_handler *handler) {
368382
aws_mem_release(connection->base.alloc, connection);
369383
}
370384

385+
static struct h2_pending_settings *s_new_pending_settings(
386+
struct aws_allocator *allocator,
387+
const struct aws_h2_frame_setting *settings_array,
388+
size_t num_settings) {
389+
390+
size_t settings_storage_size = sizeof(struct aws_h2_frame_setting) * num_settings;
391+
struct h2_pending_settings *pending_settings;
392+
void *settings_storage;
393+
if (!aws_mem_acquire_many(
394+
allocator,
395+
2,
396+
&pending_settings,
397+
sizeof(struct h2_pending_settings),
398+
&settings_storage,
399+
settings_storage_size)) {
400+
return NULL;
401+
}
402+
403+
AWS_ZERO_STRUCT(*pending_settings);
404+
/* We buffer the settings up, incase the caller has freed them when the ACK arrives */
405+
pending_settings->settings_array = settings_storage;
406+
memcpy(pending_settings->settings_array, settings_array, num_settings * sizeof(*settings_array));
407+
pending_settings->num_settings = num_settings;
408+
409+
return pending_settings;
410+
}
411+
412+
int aws_h2_connection_change_settings(
413+
struct aws_h2_connection *connection,
414+
const struct aws_h2_frame_setting *settings_array,
415+
size_t num_settings) {
416+
AWS_PRECONDITION(aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel));
417+
AWS_PRECONDITION(settings_array);
418+
419+
if (!num_settings) {
420+
return AWS_OP_SUCCESS;
421+
}
422+
423+
/* push the setting array into the queue, not applying the change until it is ACKed by peer */
424+
struct h2_pending_settings *pending_settings =
425+
s_new_pending_settings(connection->base.alloc, settings_array, num_settings);
426+
if (!pending_settings) {
427+
return AWS_OP_ERR;
428+
}
429+
/* Send setting frame to inform our peer */
430+
struct aws_h2_frame *setting_frame =
431+
aws_h2_frame_new_settings(connection->base.alloc, settings_array, num_settings, false /*ACK*/);
432+
if (!setting_frame) {
433+
CONNECTION_LOGF(ERROR, connection, "Failed to send setting_frames, error %s", aws_error_name(aws_last_error()));
434+
aws_mem_release(connection->base.alloc, pending_settings);
435+
return AWS_OP_ERR;
436+
}
437+
aws_h2_connection_enqueue_outgoing_frame(connection, setting_frame);
438+
439+
aws_linked_list_push_back(&connection->thread_data.pending_settings_queue, &pending_settings->node);
440+
s_try_write_outgoing_frames(connection);
441+
return AWS_OP_SUCCESS;
442+
}
443+
371444
void aws_h2_connection_enqueue_outgoing_frame(struct aws_h2_connection *connection, struct aws_h2_frame *frame) {
372445
AWS_PRECONDITION(frame->type != AWS_H2_FRAME_T_DATA);
373446
AWS_PRECONDITION(aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel));
@@ -991,6 +1064,7 @@ static struct aws_h2err s_decoder_on_settings(
9911064
size_t num_settings,
9921065
void *userdata) {
9931066
struct aws_h2_connection *connection = userdata;
1067+
struct aws_h2err err;
9941068
/* Once all values have been processed, the recipient MUST immediately emit a SETTINGS frame with the ACK flag
9951069
* set.(RFC-7540 6.5.3) */
9961070
CONNECTION_LOG(TRACE, connection, "Setting frame processing ends");
@@ -1009,12 +1083,9 @@ static struct aws_h2err s_decoder_on_settings(
10091083
continue;
10101084
}
10111085
switch (settings_array[i].id) {
1012-
case AWS_H2_SETTINGS_HEADER_TABLE_SIZE:
1086+
case AWS_H2_SETTINGS_HEADER_TABLE_SIZE: {
10131087
aws_h2_frame_encoder_set_setting_header_table_size(encoder, settings_array[i].value);
1014-
break;
1015-
case AWS_H2_SETTINGS_MAX_FRAME_SIZE:
1016-
aws_h2_frame_encoder_set_setting_max_frame_size(encoder, settings_array[i].value);
1017-
break;
1088+
} break;
10181089
case AWS_H2_SETTINGS_INITIAL_WINDOW_SIZE: {
10191090
/* When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream
10201091
* flow-control windows that it maintains by the difference between the new value and the old value. */
@@ -1024,16 +1095,20 @@ static struct aws_h2err s_decoder_on_settings(
10241095
while (!aws_hash_iter_done(&stream_iter)) {
10251096
struct aws_h2_stream *stream = stream_iter.element.value;
10261097
aws_hash_iter_next(&stream_iter);
1027-
if (aws_h2_stream_window_size_change(stream, size_changed)) {
1098+
err = aws_h2_stream_window_size_change(stream, size_changed, false /*self*/);
1099+
if (aws_h2err_failed(err)) {
10281100
CONNECTION_LOG(
10291101
ERROR,
10301102
connection,
10311103
"Connection error, change to SETTINGS_INITIAL_WINDOW_SIZE caused a stream's flow-control "
10321104
"window to exceed the maximum size");
1033-
return aws_h2err_from_h2_code(AWS_H2_ERR_FLOW_CONTROL_ERROR);
1105+
return err;
10341106
}
10351107
}
10361108
} break;
1109+
case AWS_H2_SETTINGS_MAX_FRAME_SIZE: {
1110+
aws_h2_frame_encoder_set_setting_max_frame_size(encoder, settings_array[i].value);
1111+
} break;
10371112
}
10381113
connection->thread_data.settings_peer[settings_array[i].id] = settings_array[i].value;
10391114
}
@@ -1042,14 +1117,58 @@ static struct aws_h2err s_decoder_on_settings(
10421117

10431118
static struct aws_h2err s_decoder_on_settings_ack(void *userdata) {
10441119
struct aws_h2_connection *connection = userdata;
1045-
/* #TODO track which SETTINGS frames is ACKed by this */
1046-
1047-
/* inform decoder about the settings */
1120+
if (aws_linked_list_empty(&connection->thread_data.pending_settings_queue)) {
1121+
CONNECTION_LOG(ERROR, connection, "Connection error, received a malicious extra SETTINGS acknowledgement");
1122+
return aws_h2err_from_h2_code(AWS_H2_ERR_PROTOCOL_ERROR);
1123+
}
1124+
struct aws_h2err err;
1125+
struct aws_linked_list_node *node = aws_linked_list_front(&connection->thread_data.pending_settings_queue);
1126+
struct h2_pending_settings *pending_settings = AWS_CONTAINER_OF(node, struct h2_pending_settings, node);
1127+
struct aws_h2_frame_setting *settings_array = pending_settings->settings_array;
1128+
/* Apply the settings */
10481129
struct aws_h2_decoder *decoder = connection->thread_data.decoder;
1049-
uint32_t *settings_self = connection->thread_data.settings_self;
1050-
aws_h2_decoder_set_setting_header_table_size(decoder, settings_self[AWS_H2_SETTINGS_HEADER_TABLE_SIZE]);
1051-
aws_h2_decoder_set_setting_enable_push(decoder, settings_self[AWS_H2_SETTINGS_ENABLE_PUSH]);
1052-
aws_h2_decoder_set_setting_max_frame_size(decoder, settings_self[AWS_H2_SETTINGS_MAX_FRAME_SIZE]);
1130+
for (size_t i = 0; i < pending_settings->num_settings; i++) {
1131+
if (connection->thread_data.settings_self[settings_array[i].id] == settings_array[i].value) {
1132+
/* No change, don't do any work */
1133+
continue;
1134+
}
1135+
switch (settings_array[i].id) {
1136+
case AWS_H2_SETTINGS_HEADER_TABLE_SIZE: {
1137+
aws_h2_decoder_set_setting_header_table_size(decoder, settings_array[i].value);
1138+
} break;
1139+
case AWS_H2_SETTINGS_ENABLE_PUSH: {
1140+
aws_h2_decoder_set_setting_enable_push(decoder, settings_array[i].value);
1141+
} break;
1142+
case AWS_H2_SETTINGS_INITIAL_WINDOW_SIZE: {
1143+
/* When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream
1144+
* flow-control windows that it maintains by the difference between the new value and the old value. */
1145+
int32_t size_changed =
1146+
settings_array[i].value - connection->thread_data.settings_self[settings_array[i].id];
1147+
struct aws_hash_iter stream_iter = aws_hash_iter_begin(&connection->thread_data.active_streams_map);
1148+
while (!aws_hash_iter_done(&stream_iter)) {
1149+
struct aws_h2_stream *stream = stream_iter.element.value;
1150+
aws_hash_iter_next(&stream_iter);
1151+
err = aws_h2_stream_window_size_change(stream, size_changed, true /*self*/);
1152+
if (aws_h2err_failed(err)) {
1153+
CONNECTION_LOG(
1154+
ERROR,
1155+
connection,
1156+
"Connection error, change to SETTINGS_INITIAL_WINDOW_SIZE from internal caused a stream's "
1157+
"flow-control window to exceed the maximum size");
1158+
return err;
1159+
}
1160+
}
1161+
} break;
1162+
case AWS_H2_SETTINGS_MAX_FRAME_SIZE: {
1163+
aws_h2_decoder_set_setting_max_frame_size(decoder, settings_array[i].value);
1164+
} break;
1165+
}
1166+
connection->thread_data.settings_self[settings_array[i].id] = settings_array[i].value;
1167+
}
1168+
/* finish applying the settings, remove the front of the queue */
1169+
aws_linked_list_pop_front(&connection->thread_data.pending_settings_queue);
1170+
/* clean up the pending_settings */
1171+
aws_mem_release(connection->base.alloc, pending_settings);
10531172
return AWS_H2ERR_SUCCESS;
10541173
}
10551174

@@ -1142,20 +1261,6 @@ static int s_send_connection_preface_client_string(struct aws_h2_connection *con
11421261
return AWS_OP_ERR;
11431262
}
11441263

1145-
/* #TODO actually fill with initial settings */
1146-
/* #TODO track which SETTINGS frames have been ACK'd */
1147-
static int s_enqueue_settings_frame(struct aws_h2_connection *connection) {
1148-
struct aws_allocator *alloc = connection->base.alloc;
1149-
1150-
struct aws_h2_frame *settings_frame = aws_h2_frame_new_settings(alloc, NULL, 0, false /*ack*/);
1151-
if (!settings_frame) {
1152-
return AWS_OP_ERR;
1153-
}
1154-
1155-
aws_h2_connection_enqueue_outgoing_frame(connection, settings_frame);
1156-
return AWS_OP_SUCCESS;
1157-
}
1158-
11591264
static void s_handler_installed(struct aws_channel_handler *handler, struct aws_channel_slot *slot) {
11601265
AWS_PRECONDITION(aws_channel_thread_is_callers_thread(slot->channel));
11611266
struct aws_h2_connection *connection = handler->impl;
@@ -1169,6 +1274,7 @@ static void s_handler_installed(struct aws_channel_handler *handler, struct aws_
11691274
/* Send HTTP/2 connection preface (RFC-7540 3.5)
11701275
* - clients must send magic string
11711276
* - both client and server must send SETTINGS frame */
1277+
11721278
if (connection->base.client_data) {
11731279
if (s_send_connection_preface_client_string(connection)) {
11741280
CONNECTION_LOGF(
@@ -1180,16 +1286,22 @@ static void s_handler_installed(struct aws_channel_handler *handler, struct aws_
11801286
}
11811287
}
11821288

1183-
if (s_enqueue_settings_frame(connection)) {
1289+
/* #TODO actually fill with initial settings, now we just disable the push promise for client */
1290+
/* #TODO Probably we will move the initial settings to connection_new_http2_XXX after we got API for user to change
1291+
* settings */
1292+
struct aws_h2_frame_setting initial_settings[2];
1293+
size_t new_setting_iter = 0;
1294+
initial_settings[new_setting_iter].id = AWS_H2_SETTINGS_ENABLE_PUSH;
1295+
initial_settings[new_setting_iter].value = 0;
1296+
new_setting_iter++;
1297+
if (aws_h2_connection_change_settings(connection, initial_settings, new_setting_iter)) {
11841298
CONNECTION_LOGF(
11851299
ERROR,
11861300
connection,
11871301
"Failed to send SETTINGS frame for connection preface, %s",
11881302
aws_error_name(aws_last_error()));
11891303
goto error;
11901304
}
1191-
1192-
s_try_write_outgoing_frames(connection);
11931305
return;
11941306

11951307
error:

source/h2_decoder.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,6 @@ struct aws_h2_decoder {
270270

271271
/* Settings for decoder, which is based on the settings sent to the peer and ACKed by peer */
272272
struct {
273-
/* the maximum size of the header compression table used to decode header blocks */
274-
uint32_t header_table_size;
275273
/* enable/disable server push */
276274
uint32_t enable_push;
277275
/* the size of the largest frame payload */
@@ -326,7 +324,6 @@ struct aws_h2_decoder *aws_h2_decoder_new(struct aws_h2_decoder_params *params)
326324
decoder->state = &s_state_prefix;
327325
}
328326

329-
decoder->settings.header_table_size = aws_h2_settings_initial[AWS_H2_SETTINGS_HEADER_TABLE_SIZE];
330327
decoder->settings.enable_push = aws_h2_settings_initial[AWS_H2_SETTINGS_ENABLE_PUSH];
331328
decoder->settings.max_frame_size = aws_h2_settings_initial[AWS_H2_SETTINGS_MAX_FRAME_SIZE];
332329

@@ -1534,7 +1531,8 @@ static struct aws_h2err s_state_fn_connection_preface_string(
15341531
}
15351532

15361533
void aws_h2_decoder_set_setting_header_table_size(struct aws_h2_decoder *decoder, uint32_t data) {
1537-
decoder->settings.header_table_size = data;
1534+
/* Set the protocol_max_size_setting for hpack. */
1535+
aws_hpack_set_protocol_max_size_setting(decoder->hpack, data);
15381536
}
15391537

15401538
void aws_h2_decoder_set_setting_enable_push(struct aws_h2_decoder *decoder, uint32_t data) {

source/h2_frames.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,6 @@ int aws_h2_frame_encoder_init(
312312
return AWS_OP_ERR;
313313
}
314314

315-
encoder->settings.header_table_size = aws_h2_settings_initial[AWS_H2_SETTINGS_HEADER_TABLE_SIZE];
316315
encoder->settings.max_frame_size = aws_h2_settings_initial[AWS_H2_SETTINGS_MAX_FRAME_SIZE];
317316
return AWS_OP_SUCCESS;
318317
}
@@ -1227,8 +1226,10 @@ int aws_h2_encode_frame(
12271226
}
12281227

12291228
void aws_h2_frame_encoder_set_setting_header_table_size(struct aws_h2_frame_encoder *encoder, uint32_t data) {
1229+
/* Setting for dynamic table size changed from peer, we will update the dynamic table size when we encoder the next
1230+
* header block */
12301231
aws_hpack_set_max_table_size(encoder->hpack, data);
1231-
encoder->settings.header_table_size = data;
1232+
aws_hpack_set_protocol_max_size_setting(encoder->hpack, data);
12321233
}
12331234

12341235
void aws_h2_frame_encoder_set_setting_max_frame_size(struct aws_h2_frame_encoder *encoder, uint32_t data) {

0 commit comments

Comments
 (0)