diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index e006fb94..22e30d9a 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -127,6 +127,13 @@ typedef struct { */ #define NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE ((1 << 16) - 1) +/** + * @macro + * + * The maximum header table size. + */ +#define NGHTTP2_MAX_HEADER_TABLE_SIZE (1 << 16) + /** * @macro * @@ -262,6 +269,11 @@ typedef enum { * Callback was paused by the application */ NGHTTP2_ERR_PAUSE = -526, + /** + * There are too many in-flight SETTING frame and no more + * transmission of SETTINGS is allowed. + */ + NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS = -527, /** * The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is * under unexpected condition and cannot process any further data @@ -379,9 +391,9 @@ typedef enum { */ NGHTTP2_FLAG_END_PUSH_PROMISE = 0x4, /** - * The PONG flag. + * The ACK flag. */ - NGHTTP2_FLAG_PONG = 0x1 + NGHTTP2_FLAG_ACK = 0x1 } nghttp2_flag; /** diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 0809fb42..693f9c8c 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -106,12 +106,11 @@ void nghttp2_frame_rst_stream_free(nghttp2_rst_stream *frame) {} -void nghttp2_frame_settings_init(nghttp2_settings *frame, +void nghttp2_frame_settings_init(nghttp2_settings *frame, uint8_t flags, nghttp2_settings_entry *iv, size_t niv) { memset(frame, 0, sizeof(nghttp2_settings)); - nghttp2_frame_set_hd(&frame->hd, niv*8, NGHTTP2_SETTINGS, NGHTTP2_FLAG_NONE, - 0); + nghttp2_frame_set_hd(&frame->hd, niv*8, NGHTTP2_SETTINGS, flags, 0); frame->niv = niv; frame->iv = iv; } diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index d8517f4d..54814a8b 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -470,7 +470,7 @@ void nghttp2_frame_push_promise_free(nghttp2_push_promise *frame); * ownership of |iv|, so caller must not free it. The |flags| are * bitwise-OR of one or more of nghttp2_settings_flag. */ -void nghttp2_frame_settings_init(nghttp2_settings *frame, +void nghttp2_frame_settings_init(nghttp2_settings *frame, uint8_t flags, nghttp2_settings_entry *iv, size_t niv); void nghttp2_frame_settings_free(nghttp2_settings *frame); diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 92d9ae4d..e8f2e32e 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -191,6 +191,8 @@ static int nghttp2_session_new(nghttp2_session **session_ptr, (*session_ptr)->goaway_flags = NGHTTP2_GOAWAY_NONE; (*session_ptr)->last_stream_id = 0; + (*session_ptr)->inflight_niv = -1; + if(server) { (*session_ptr)->server = 1; side_deflate = NGHTTP2_HD_SIDE_RESPONSE; @@ -341,6 +343,7 @@ void nghttp2_session_del(nghttp2_session *session) if(session == NULL) { return; } + free(session->inflight_iv); nghttp2_inbound_frame_reset(session); nghttp2_map_each_free(&session->streams, nghttp2_free_streams, NULL); nghttp2_session_ob_pq_free(&session->ob_pq); @@ -923,6 +926,27 @@ static int nghttp2_session_predicate_window_update_send return 0; } +/* + * This function checks SETTINGS can be sent at this time. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS + * There is already another in-flight SETTINGS. Note that the + * current implementation only allows 1 in-flight SETTINGS frame + * without ACK flag set. + */ +static int nghttp2_session_predicate_settings_send(nghttp2_session *session, + nghttp2_frame *frame) +{ + if((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0 && + session->inflight_niv != -1) { + return NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS; + } + return 0; +} + /* * Returns the maximum length of next data read. If the * connection-level and/or stream-wise flow control are enabled, the @@ -1102,7 +1126,12 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session, return framebuflen; } break; - case NGHTTP2_SETTINGS: + case NGHTTP2_SETTINGS: { + int r; + r = nghttp2_session_predicate_settings_send(session, frame); + if(r != 0) { + return r; + } framebuflen = nghttp2_frame_pack_settings(&session->aob.framebuf, &session->aob.framebufmax, &frame->settings); @@ -1110,6 +1139,7 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session, return framebuflen; } break; + } case NGHTTP2_PUSH_PROMISE: { int r; nghttp2_stream *stream; @@ -1412,9 +1442,29 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session) } r = 0; break; - case NGHTTP2_SETTINGS: - /* nothing to do */ + case NGHTTP2_SETTINGS: { + size_t i; + if(frame->hd.flags & NGHTTP2_FLAG_ACK) { + break; + } + /* Only update max concurrent stream here. Applying it without + ACK is safe because we can respond to the exceeding streams + with REFUSED_STREAM and client will retry later. */ + for(i = frame->settings.niv; i > 0; --i) { + if(frame->settings.iv[i - 1].settings_id == + NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) { + session->local_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = + frame->settings.iv[i - 1].value; + break; + } + } + assert(session->inflight_niv == -1); + session->inflight_iv = frame->settings.iv; + session->inflight_niv = frame->settings.niv; + frame->settings.iv = NULL; + frame->settings.niv = 0; break; + } case NGHTTP2_PUSH_PROMISE: /* nothing to do */ break; @@ -2187,6 +2237,10 @@ static int nghttp2_disable_remote_flow_control_func(nghttp2_map_entry *entry, /* * Disable remote side connection-level flow control and stream-level * flow control of existing streams. + * + * This function returns 0 if it succeeds, or one of negative error codes. + * + * The error code is always FATAL. */ static int nghttp2_session_disable_remote_flow_control (nghttp2_session *session) @@ -2219,6 +2273,20 @@ static void nghttp2_session_disable_local_flow_control assert(rv == 0); } +/* + * Apply SETTINGS values |iv| having |niv| elements to the local + * settings. SETTINGS_MAX_CONCURRENT_STREAMS is not applied here + * because it has been already applied on transmission of SETTINGS + * frame. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_HEADER_COMP + * The header table size is out of range + * NGHTTP2_ERR_NOMEM + * Out of memory + */ int nghttp2_session_update_local_settings(nghttp2_session *session, nghttp2_settings_entry *iv, size_t niv) @@ -2229,12 +2297,32 @@ int nghttp2_session_update_local_settings(nghttp2_session *session, session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]; uint8_t new_flow_control = old_flow_control; int32_t new_initial_window_size = -1; + int32_t header_table_size = -1; + uint8_t header_table_size_seen = 0; /* Use the value last seen. */ for(i = 0; i < niv; ++i) { - if(iv[i].settings_id == NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE) { + switch(iv[i].settings_id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + header_table_size_seen = 1; + header_table_size = iv[i].value; + break; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: new_initial_window_size = iv[i].value; - } else if(iv[i].settings_id == NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS) { + break; + case NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS: new_flow_control = iv[i].value; + break; + } + } + if(header_table_size_seen) { + if(header_table_size < 0 || + header_table_size > NGHTTP2_MAX_HEADER_TABLE_SIZE) { + return NGHTTP2_ERR_HEADER_COMP; + } + rv = nghttp2_hd_change_table_size(&session->hd_inflater, + header_table_size); + if(rv != 0) { + return rv; } } if(!old_flow_control && !new_flow_control && new_initial_window_size != -1) { @@ -2247,7 +2335,10 @@ int nghttp2_session_update_local_settings(nghttp2_session *session, } } for(i = 0; i < niv; ++i) { - if(iv[i].settings_id > 0 && iv[i].settings_id <= NGHTTP2_SETTINGS_MAX) { + /* SETTINGS_MAX_CONCURRENT_STREAMS has already been applied on + transmission of the SETTINGS frame. */ + if(iv[i].settings_id > 0 && iv[i].settings_id <= NGHTTP2_SETTINGS_MAX && + iv[i].settings_id != NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) { session->local_settings[iv[i].settings_id] = iv[i].value; } } @@ -2259,7 +2350,8 @@ int nghttp2_session_update_local_settings(nghttp2_session *session, } int nghttp2_session_on_settings_received(nghttp2_session *session, - nghttp2_frame *frame) + nghttp2_frame *frame, + int noack) { int rv; int i; @@ -2268,6 +2360,33 @@ int nghttp2_session_on_settings_received(nghttp2_session *session, return nghttp2_session_handle_invalid_connection(session, frame, NGHTTP2_PROTOCOL_ERROR); } + if(frame->hd.flags & NGHTTP2_FLAG_ACK) { + if(frame->settings.niv != 0) { + return nghttp2_session_handle_invalid_connection + (session, frame, NGHTTP2_FRAME_TOO_LARGE); + } + if(session->inflight_niv == -1) { + return nghttp2_session_handle_invalid_connection(session, frame, + NGHTTP2_PROTOCOL_ERROR); + } + rv = nghttp2_session_update_local_settings(session, session->inflight_iv, + session->inflight_niv); + free(session->inflight_iv); + session->inflight_iv = NULL; + session->inflight_niv = -1; + if(rv != 0) { + nghttp2_error_code error_code = NGHTTP2_INTERNAL_ERROR; + if(nghttp2_is_fatal(rv)) { + return rv; + } + if(rv == NGHTTP2_ERR_HEADER_COMP) { + error_code = NGHTTP2_COMPRESSION_ERROR; + } + return nghttp2_session_handle_invalid_connection(session, frame, + error_code); + } + return nghttp2_session_call_on_frame_received(session, frame); + } /* Check ID/value pairs and persist them if necessary. */ memset(check, 0, sizeof(check)); for(i = (int)frame->settings.niv - 1; i >= 0; --i) { @@ -2282,6 +2401,21 @@ int nghttp2_session_on_settings_received(nghttp2_session *session, } check[entry->settings_id] = 1; switch(entry->settings_id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + if(entry->value > NGHTTP2_MAX_HEADER_TABLE_SIZE) { + return nghttp2_session_handle_invalid_connection + (session, frame, NGHTTP2_COMPRESSION_ERROR); + } + rv = nghttp2_hd_change_table_size(&session->hd_deflater, entry->value); + if(rv != 0) { + if(nghttp2_is_fatal(rv)) { + return rv; + } else { + return nghttp2_session_handle_invalid_connection + (session, frame, NGHTTP2_COMPRESSION_ERROR); + } + } + break; case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: /* Update the initial window size of the all active streams */ /* Check that initial_window_size < (1u << 31) */ @@ -2306,6 +2440,8 @@ int nghttp2_session_on_settings_received(nghttp2_session *session, if(session->remote_settings[entry->settings_id] == 0) { rv = nghttp2_session_disable_remote_flow_control(session); if(rv != 0) { + /* FATAL */ + assert(rv < NGHTTP2_ERR_FATAL); return rv; } } @@ -2319,6 +2455,16 @@ int nghttp2_session_on_settings_received(nghttp2_session *session, } session->remote_settings[entry->settings_id] = entry->value; } + if(!noack) { + rv = nghttp2_session_add_settings(session, NGHTTP2_FLAG_ACK, NULL, 0); + if(rv != 0) { + if(nghttp2_is_fatal(rv)) { + return rv; + } + return nghttp2_session_handle_invalid_connection + (session, frame, NGHTTP2_INTERNAL_ERROR); + } + } return nghttp2_session_call_on_frame_received(session, frame); } @@ -2401,9 +2547,9 @@ int nghttp2_session_on_ping_received(nghttp2_session *session, return nghttp2_session_handle_invalid_connection(session, frame, NGHTTP2_PROTOCOL_ERROR); } - if((frame->hd.flags & NGHTTP2_FLAG_PONG) == 0) { + if((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { /* Peer sent ping, so ping it back */ - r = nghttp2_session_add_ping(session, NGHTTP2_FLAG_PONG, + r = nghttp2_session_add_ping(session, NGHTTP2_FLAG_ACK, frame->ping.opaque_data); if(r != 0) { return r; @@ -2645,7 +2791,7 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session) session->iframe.buf, session->iframe.buflen); if(r == 0) { - r = nghttp2_session_on_settings_received(session, frame); + r = nghttp2_session_on_settings_received(session, frame, 0 /* ACK */); if(r != NGHTTP2_ERR_PAUSE) { nghttp2_frame_settings_free(&frame->settings); } @@ -3328,6 +3474,37 @@ int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags, return r; } +int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags, + const nghttp2_settings_entry *iv, size_t niv) +{ + nghttp2_frame *frame; + nghttp2_settings_entry *iv_copy; + int r; + if(!nghttp2_iv_check(iv, niv, + session->local_settings + [NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS])) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + frame = malloc(sizeof(nghttp2_frame)); + if(frame == NULL) { + return NGHTTP2_ERR_NOMEM; + } + iv_copy = nghttp2_frame_iv_copy(iv, niv); + if(iv_copy == NULL) { + free(frame); + return NGHTTP2_ERR_NOMEM; + } + nghttp2_frame_settings_init(&frame->settings, flags, iv_copy, niv); + r = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL); + if(r != 0) { + /* The only expected error is fatal one */ + assert(r < NGHTTP2_ERR_FATAL); + nghttp2_frame_settings_free(&frame->settings); + free(frame); + } + return r; +} + ssize_t nghttp2_session_pack_data(nghttp2_session *session, uint8_t **buf_ptr, size_t *buflen_ptr, size_t datamax, @@ -3481,7 +3658,7 @@ int nghttp2_session_upgrade(nghttp2_session *session, memset(&frame.hd, 0, sizeof(frame.hd)); frame.settings.iv = iv; frame.settings.niv = niv; - rv = nghttp2_session_on_settings_received(session, &frame); + rv = nghttp2_session_on_settings_received(session, &frame, 1 /* No ACK */); } else { rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, niv); } diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index 67ca600e..381224bc 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -188,6 +188,13 @@ struct nghttp2_session { /* Settings value of the local endpoint. */ uint32_t local_settings[NGHTTP2_SETTINGS_MAX+1]; + /* In-flight SETTINGS values. NULL does not necessarily mean there + is no in-flight SETTINGS. */ + nghttp2_settings_entry *inflight_iv; + /* The number of entries in |inflight_iv|. -1 if there is no + in-flight SETTINGS. */ + ssize_t inflight_niv; + /* Option flags. This is bitwise-OR of 0 or more of nghttp2_optmask. */ uint32_t opt_flags; @@ -295,6 +302,18 @@ int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t window_size_increment); +/* + * Adds SETTINGS frame. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags, + const nghttp2_settings_entry *iv, size_t niv); + /* * Creates new stream in |session| with stream ID |stream_id|, * priority |pri| and flags |flags|. NGHTTP2_FLAG_END_STREAM flag is @@ -386,19 +405,33 @@ int nghttp2_session_on_priority_received(nghttp2_session *session, * Called when RST_STREAM is received, assuming |frame| is properly * initialized. * - * This function returns 0 and never fail. + * This function returns 0 if it succeeds, or one the following + * negative error codes: + * + * TBD */ int nghttp2_session_on_rst_stream_received(nghttp2_session *session, nghttp2_frame *frame); /* * Called when SETTINGS is received, assuming |frame| is properly - * initialized. + * initialized. If |noack| is non-zero, SETTINGS with ACK will not be + * submitted. If |frame| has NGHTTP2_FLAG_ACK flag set, no SETTINGS + * with ACK will not be submitted regardless of |noack|. * - * This function returns 0 and never fail. + * This function returns 0 if it succeeds, or one the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + * NGHTTP2_ERR_PAUSE + * Callback function returns NGHTTP2_ERR_PAUSE + * NGHTTP2_ERR_CALLBACK_FAILURE + * The read_callback failed */ int nghttp2_session_on_settings_received(nghttp2_session *session, - nghttp2_frame *frame); + nghttp2_frame *frame, + int noack); /* * Called when PUSH_PROMISE is received, assuming |frame| is properly diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index d47ce738..201b6f44 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -209,39 +209,7 @@ int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags, int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags, const nghttp2_settings_entry *iv, size_t niv) { - nghttp2_frame *frame; - nghttp2_settings_entry *iv_copy; - int r; - if(!nghttp2_iv_check(iv, niv, - session->local_settings - [NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS])) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - frame = malloc(sizeof(nghttp2_frame)); - if(frame == NULL) { - return NGHTTP2_ERR_NOMEM; - } - iv_copy = nghttp2_frame_iv_copy(iv, niv); - if(iv_copy == NULL) { - free(frame); - return NGHTTP2_ERR_NOMEM; - } - nghttp2_frame_settings_init(&frame->settings, iv_copy, niv); - - r = nghttp2_session_update_local_settings(session, iv_copy, niv); - if(r != 0) { - nghttp2_frame_settings_free(&frame->settings); - free(frame); - return r; - } - r = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL); - if(r != 0) { - /* The only expected error is fatal one */ - assert(r < NGHTTP2_ERR_FATAL); - nghttp2_frame_settings_free(&frame->settings); - free(frame); - } - return r; + return nghttp2_session_add_settings(session, NGHTTP2_FLAG_NONE, iv, niv); } int nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags, diff --git a/src/app_helper.cc b/src/app_helper.cc index cc1db54e..526daa93 100644 --- a/src/app_helper.cc +++ b/src/app_helper.cc @@ -80,12 +80,16 @@ namespace { const char* strsettingsid(int32_t id) { switch(id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + return "SETTINGS_HEADER_TABLE_SIZE"; + case NGHTTP2_SETTINGS_ENABLE_PUSH: + return "SETTINGS_ENABLE_PUSH"; case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: - return "MAX_CONCURRENT_STREAMS"; + return "SETTINGS_MAX_CONCURRENT_STREAMS"; case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: - return "INITIAL_WINDOW_SIZE"; + return "SETTINGS_INITIAL_WINDOW_SIZE"; case NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS: - return "FLOW_CONTROL_OPTIONS"; + return "SETTINGS_FLOW_CONTROL_OPTIONS"; default: return "UNKNOWN"; } @@ -208,14 +212,19 @@ void print_flags(const nghttp2_frame_hd& hd) s += "PRIORITY"; } break; + case NGHTTP2_SETTINGS: + if(hd.flags & NGHTTP2_FLAG_ACK) { + s += "ACK"; + } + break; case NGHTTP2_PUSH_PROMISE: if(hd.flags & NGHTTP2_FLAG_END_PUSH_PROMISE) { s += "END_PUSH_PROMISE"; } break; case NGHTTP2_PING: - if(hd.flags & NGHTTP2_FLAG_PONG) { - s += "PONG"; + if(hd.flags & NGHTTP2_FLAG_ACK) { + s += "ACK"; } break; } diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c index 344e9b86..24aa99d8 100644 --- a/tests/nghttp2_frame_test.c +++ b/tests/nghttp2_frame_test.c @@ -223,7 +223,8 @@ void test_nghttp2_frame_pack_settings() iv[2].settings_id = NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS; iv[2].value = 1; - nghttp2_frame_settings_init(&frame, nghttp2_frame_iv_copy(iv, 3), 3); + nghttp2_frame_settings_init(&frame, NGHTTP2_FLAG_NONE, + nghttp2_frame_iv_copy(iv, 3), 3); framelen = nghttp2_frame_pack_settings(&buf, &buflen, &frame); CU_ASSERT(NGHTTP2_FRAME_HEAD_LENGTH+3*8 == framelen); @@ -290,14 +291,14 @@ void test_nghttp2_frame_pack_ping(void) size_t buflen = 0; ssize_t framelen; const uint8_t opaque_data[] = "01234567"; - nghttp2_frame_ping_init(&frame, NGHTTP2_FLAG_PONG, opaque_data); + nghttp2_frame_ping_init(&frame, NGHTTP2_FLAG_ACK, opaque_data); framelen = nghttp2_frame_pack_ping(&buf, &buflen, &frame); CU_ASSERT(0 == nghttp2_frame_unpack_ping (&oframe, &buf[0], NGHTTP2_FRAME_HEAD_LENGTH, &buf[NGHTTP2_FRAME_HEAD_LENGTH], framelen - NGHTTP2_FRAME_HEAD_LENGTH)); - check_frame_header(8, NGHTTP2_PING, NGHTTP2_FLAG_PONG, 0, &oframe.hd); + check_frame_header(8, NGHTTP2_PING, NGHTTP2_FLAG_ACK, 0, &oframe.hd); CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, sizeof(opaque_data) - 1) == 0); free(buf); diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 0ca649d0..65a8215f 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -1134,6 +1134,7 @@ void test_nghttp2_session_on_settings_received(void) nghttp2_frame frame; const size_t niv = 5; nghttp2_settings_entry iv[255]; + nghttp2_outbound_item *item; iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; iv[0].value = 50; @@ -1166,9 +1167,10 @@ void test_nghttp2_session_on_settings_received(void) stream1->remote_window_size = 16*1024; stream2->remote_window_size = -48*1024; - nghttp2_frame_settings_init(&frame.settings, dup_iv(iv, niv), niv); + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, + dup_iv(iv, niv), niv); - CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); CU_ASSERT(1000000009 == session->remote_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]); CU_ASSERT(64*1024 == @@ -1181,7 +1183,7 @@ void test_nghttp2_session_on_settings_received(void) frame.settings.iv[2].value = 16*1024; - CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); CU_ASSERT(16*1024 == stream1->remote_window_size); CU_ASSERT(-48*1024 == stream2->remote_window_size); @@ -1193,6 +1195,37 @@ void test_nghttp2_session_on_settings_received(void) nghttp2_frame_settings_free(&frame.settings); nghttp2_session_del(session); + + /* Check ACK with niv > 0 */ + nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, + dup_iv(iv, 1), 1); + /* Specify inflight_iv deliberately */ + session->inflight_iv = frame.settings.iv; + session->inflight_niv = frame.settings.niv; + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + + session->inflight_iv = NULL; + session->inflight_niv = -1; + + nghttp2_frame_settings_free(&frame.settings); + nghttp2_session_del(session); + + /* Check ACK against no inflight SETTINGS */ + nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + + nghttp2_frame_settings_free(&frame.settings); + nghttp2_session_del(session); } void test_nghttp2_session_on_push_promise_received(void) @@ -1346,7 +1379,7 @@ void test_nghttp2_session_on_ping_received(void) callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; nghttp2_session_client_new(&session, &callbacks, &user_data); - nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_PONG, opaque_data); + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_ACK, opaque_data); CU_ASSERT(0 == nghttp2_session_on_ping_received(session, &frame)); CU_ASSERT(1 == user_data.frame_recv_cb_called); @@ -1362,7 +1395,7 @@ void test_nghttp2_session_on_ping_received(void) CU_ASSERT(2 == user_data.frame_recv_cb_called); top = nghttp2_session_get_ob_pq_top(session); CU_ASSERT(NGHTTP2_PING == OB_CTRL_TYPE(top)); - CU_ASSERT(NGHTTP2_FLAG_PONG == OB_CTRL(top)->hd.flags); + CU_ASSERT(NGHTTP2_FLAG_ACK == OB_CTRL(top)->hd.flags); CU_ASSERT(memcmp(opaque_data, OB_CTRL(top)->ping.opaque_data, 8) == 0); nghttp2_frame_ping_free(&frame.ping); @@ -2260,6 +2293,7 @@ void test_nghttp2_submit_settings(void) nghttp2_outbound_item *item; nghttp2_frame *frame; nghttp2_settings_entry iv[5]; + nghttp2_frame ack_frame; iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; iv[0].value = 5; @@ -2296,15 +2330,6 @@ void test_nghttp2_submit_settings(void) /* Now sends without 5th one */ CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 4)); - /* Make sure that local settings are changed */ - CU_ASSERT(50 == - session->local_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]); - CU_ASSERT(16*1024 == - session->local_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]); - CU_ASSERT(1 == - session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]); - CU_ASSERT(0 == session->local_flow_control); - item = nghttp2_session_get_next_ob_item(session); CU_ASSERT(NGHTTP2_SETTINGS == OB_CTRL_TYPE(item)); @@ -2323,6 +2348,20 @@ void test_nghttp2_submit_settings(void) CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(1 == ud.frame_send_cb_called); + /* Only SETTINGS_MAX_CONCURRENT_STREAMS is applied on transmission */ + CU_ASSERT(50 == + session->local_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]); + + nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + nghttp2_frame_settings_free(&ack_frame.settings); + + CU_ASSERT(16*1024 == + session->local_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]); + CU_ASSERT(1 == + session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]); + CU_ASSERT(0 == session->local_flow_control); + nghttp2_session_del(session); } @@ -2333,7 +2372,9 @@ void test_nghttp2_submit_settings_update_local_window_size(void) nghttp2_outbound_item *item; nghttp2_settings_entry iv[4]; nghttp2_stream *stream; - my_user_data ud; + nghttp2_frame ack_frame; + + nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; iv[0].value = 16*1024; @@ -2342,9 +2383,9 @@ void test_nghttp2_submit_settings_update_local_window_size(void) iv[1].value = 1; memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); - callbacks.send_callback = block_count_send_callback; + callbacks.send_callback = null_send_callback; - nghttp2_session_server_new(&session, &callbacks, &ud); + nghttp2_session_server_new(&session, &callbacks, NULL); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE, NGHTTP2_PRI_DEFAULT, @@ -2358,6 +2399,8 @@ void test_nghttp2_submit_settings_update_local_window_size(void) stream->local_flow_control = 0; CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); stream = nghttp2_session_get_stream(session, 1); CU_ASSERT(0 == stream->recv_window_size); @@ -2366,9 +2409,6 @@ void test_nghttp2_submit_settings_update_local_window_size(void) stream = nghttp2_session_get_stream(session, 3); CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == stream->local_window_size); - /* Setting block_count = 0 will pop first entry SETTINGS */ - ud.block_count = 0; - CU_ASSERT(0 == nghttp2_session_send(session)); item = nghttp2_session_get_next_ob_item(session); CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item)); CU_ASSERT(32768 == OB_CTRL(item)->window_update.window_size_increment); @@ -2376,28 +2416,35 @@ void test_nghttp2_submit_settings_update_local_window_size(void) nghttp2_session_del(session); /* Check flow control disabled case */ - nghttp2_session_server_new(&session, &callbacks, &ud); + nghttp2_session_server_new(&session, &callbacks, NULL); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE, NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_OPENED, NULL); CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == stream->local_window_size); nghttp2_session_del(session); /* Check overflow case */ iv[0].value = 128*1024; - nghttp2_session_server_new(&session, &callbacks, &ud); + nghttp2_session_server_new(&session, &callbacks, NULL); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE, NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_OPENED, NULL); stream->local_window_size = NGHTTP2_MAX_WINDOW_SIZE; CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + CU_ASSERT(NGHTTP2_STREAM_CLOSING == stream->state); nghttp2_session_del(session); + nghttp2_frame_settings_free(&ack_frame.settings); } void test_nghttp2_submit_push_promise(void) @@ -3068,8 +3115,9 @@ void test_nghttp2_session_flow_control(void) iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; iv[0].value = 32*1024; - nghttp2_frame_settings_init(&settings_frame.settings, dup_iv(iv, 1), 1); - nghttp2_session_on_settings_received(session, &settings_frame); + nghttp2_frame_settings_init(&settings_frame.settings, NGHTTP2_FLAG_NONE, + dup_iv(iv, 1), 1); + nghttp2_session_on_settings_received(session, &settings_frame, 1); nghttp2_frame_settings_free(&settings_frame.settings); /* Sends another 8KiB data */ @@ -3119,8 +3167,9 @@ void test_nghttp2_session_flow_control_disable_remote(void) CU_ASSERT(data_size - NGHTTP2_INITIAL_WINDOW_SIZE == ud.data_source_length); /* Disable flow control entirely */ - nghttp2_frame_settings_init(&frame.settings, dup_iv(&iv, 1), 1); - nghttp2_session_on_settings_received(session, &frame); + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, + dup_iv(&iv, 1), 1); + nghttp2_session_on_settings_received(session, &frame, 1); /* Check both connection and stream-level remote_flow_control is disabled */ @@ -3141,7 +3190,9 @@ void test_nghttp2_session_flow_control_disable_local(void) nghttp2_session_callbacks callbacks; nghttp2_stream *stream; nghttp2_settings_entry iv[1]; + nghttp2_frame ack_frame; + nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.send_callback = null_send_callback; @@ -3155,11 +3206,15 @@ void test_nghttp2_session_flow_control_disable_local(void) iv[0].value = 1; CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, - sizeof(iv)/sizeof(iv[0]))); + ARRLEN(iv))); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + CU_ASSERT(0 == stream->local_flow_control); CU_ASSERT(0 == session->local_flow_control); nghttp2_session_del(session); + nghttp2_frame_settings_free(&ack_frame.settings); } void test_nghttp2_session_flow_control_data_recv(void)