From b979d2e8d2660947d20e44df2ec8cb420f7aa99d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Fri, 9 Aug 2013 00:58:52 +0900 Subject: [PATCH] Support increment/reduction of local window size by WINDOW_UPDATE --- lib/includes/nghttp2/nghttp2.h | 28 ++++-- lib/nghttp2_helper.c | 31 ++++++ lib/nghttp2_helper.h | 25 +++++ lib/nghttp2_session.c | 167 +++++++++++++++++++++++++-------- lib/nghttp2_session.h | 21 ++++- lib/nghttp2_stream.c | 29 +++++- lib/nghttp2_stream.h | 15 ++- lib/nghttp2_submit.c | 60 ++++++++++-- tests/Makefile.am | 6 +- tests/main.c | 9 +- tests/nghttp2_helper_test.c | 75 +++++++++++++++ tests/nghttp2_helper_test.h | 30 ++++++ tests/nghttp2_session_test.c | 151 +++++++++++++++++++++++++++++ tests/nghttp2_session_test.h | 2 + 14 files changed, 578 insertions(+), 71 deletions(-) create mode 100644 tests/nghttp2_helper_test.c create mode 100644 tests/nghttp2_helper_test.h diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 0c246ba2..83311285 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -1615,6 +1615,10 @@ int nghttp2_submit_rst_stream(nghttp2_session *session, int32_t stream_id, * This function does not take ownership of the |iv|. This function * copies all the elements in the |iv|. * + * While updating individual stream's local window size, if the window + * size becomes strictly larger than NGHTTP2_MAX_WINDOW_SIZE, + * RST_STREAM is issued against such a stream. + * * This function returns 0 if it succeeds, or one of the following * negative error codes: * @@ -1711,21 +1715,29 @@ int nghttp2_submit_goaway(nghttp2_session *session, /** * @function * - * Submits WINDOW_UPDATE frame. The effective range of the - * |window_size_increment| is [1, (1 << 31)-1], inclusive. But the - * application must be responsible to keep the resulting window size - * <= (1 << 31)-1. If :enum:`NGHTTP2_FLAG_END_FLOW_CONTROL` bit set in - * the |flags|, 0 can be specified in the |window_size_increment|. In - * fact, if this flag is set, the value specified in the - * |window_size_increment| is ignored. + * Submits WINDOW_UPDATE frame. + * + * If the |window_size_increment| is positive, the WINDOW_UPDATE with + * that value as window_size_increment is queued. If the + * |window_size_increment| is larger than the received bytes from the + * remote endpoint, the local window size is increased by that + * difference. + * + * If the |window_size_increment| is negative, the local window size + * is decreased by -|window_size_increment|. If + * :enum:`NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE` is not set and the + * library decided that the WINDOW_UPDATE should be submitted, then + * WINDOW_UPDATE is queued with the current received bytes count. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` - * The |delta_window_size| is 0 or negative and + * The |delta_window_size| is 0 and * :enum:`NGHTTP2_FLAG_END_FLOW_CONTROL` bit is not set in * |flags|. + * :enum:`NGHTTP2_ERR_FLOW_CONTROL` + * The local window size overflow or gets negative. * :enum:`NGHTTP2_ERR_STREAM_CLOSED` * The stream is already closed or does not exist. * :enum:`NGHTTP2_ERR_NOMEM` diff --git a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c index d0fa867a..e11dac73 100644 --- a/lib/nghttp2_helper.c +++ b/lib/nghttp2_helper.c @@ -91,6 +91,37 @@ void nghttp2_downcase(uint8_t *s, size_t len) } } +int nghttp2_adjust_local_window_size(int32_t *local_window_size_ptr, + int32_t *recv_window_size_ptr, + int32_t delta) +{ + if(delta > 0) { + int32_t new_recv_window_size = *recv_window_size_ptr - delta; + if(new_recv_window_size < 0) { + if(*local_window_size_ptr > + NGHTTP2_MAX_WINDOW_SIZE + new_recv_window_size) { + return NGHTTP2_ERR_FLOW_CONTROL; + } + *local_window_size_ptr -= new_recv_window_size; + new_recv_window_size = 0; + } + *recv_window_size_ptr = new_recv_window_size; + return 0; + } else { + if(*local_window_size_ptr + delta < 0) { + return NGHTTP2_ERR_FLOW_CONTROL; + } + *local_window_size_ptr += delta; + } + return 0; +} + +int nghttp2_should_send_window_update(int32_t local_window_size, + int32_t recv_window_size) +{ + return recv_window_size >= local_window_size / 2; +} + const char* nghttp2_strerror(int error_code) { switch(error_code) { diff --git a/lib/nghttp2_helper.h b/lib/nghttp2_helper.h index ea4a996c..3439dcf5 100644 --- a/lib/nghttp2_helper.h +++ b/lib/nghttp2_helper.h @@ -91,4 +91,29 @@ void* nghttp2_memdup(const void* src, size_t n); void nghttp2_downcase(uint8_t *s, size_t len); +/* + * Adjusts |*local_window_size_ptr| and |*recv_window_size_ptr| with + * |delta| which is the WINDOW_UPDATE's window_size_increment sent + * from local side. If |delta| is strictly larger than + * |*recv_window_size_ptr|, |*local_window_size_ptr| is increased by + * delta - *recv_window_size_ptr. If |delta| is negative, + * |*local_window_size_ptr| is decreased by delta. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_FLOW_CONTROL + * local_window_size overflow or gets negative. + */ +int nghttp2_adjust_local_window_size(int32_t *local_window_size_ptr, + int32_t *recv_window_size_ptr, + int32_t delta); + +/* + * Returns non-zero if the function decided that WINDOW_UPDATE should + * be sent. + */ +int nghttp2_should_send_window_update(int32_t local_window_size, + int32_t recv_window_size); + #endif /* NGHTTP2_HELPER_H */ diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 14459118..39e4a56f 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -146,6 +146,7 @@ static int nghttp2_session_new(nghttp2_session **session_ptr, (*session_ptr)->local_flow_control = 1; (*session_ptr)->window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE; (*session_ptr)->recv_window_size = 0; + (*session_ptr)->local_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE; (*session_ptr)->goaway_flags = NGHTTP2_GOAWAY_NONE; (*session_ptr)->last_stream_id = 0; @@ -1856,8 +1857,9 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session, return 0; } -static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry, - void *ptr) +static int nghttp2_update_remote_initial_window_size_func +(nghttp2_map_entry *entry, + void *ptr) { int rv; nghttp2_update_window_size_arg *arg; @@ -1868,7 +1870,8 @@ static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry, arg->new_window_size, arg->old_window_size); if(rv != 0) { - return NGHTTP2_ERR_FLOW_CONTROL; + return nghttp2_session_add_rst_stream(arg->session, stream->stream_id, + NGHTTP2_FLOW_CONTROL_ERROR); } /* If window size gets positive, push deferred DATA frame to outbound queue. */ @@ -1890,8 +1893,8 @@ static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry, } /* - * Updates the initial window size of all active streams. - * If error occurs, all streams may not be updated. + * Updates the remote initial window size of all active streams. If + * error occurs, all streams may not be updated. * * This function returns 0 if it succeeds, or one of the following * negative error codes: @@ -1899,7 +1902,7 @@ static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry, * NGHTTP2_ERR_NOMEM * Out of memory. */ -static int nghttp2_session_update_initial_window_size +static int nghttp2_session_update_remote_initial_window_size (nghttp2_session *session, int32_t new_initial_window_size) { @@ -1909,7 +1912,66 @@ static int nghttp2_session_update_initial_window_size arg.old_window_size = session->remote_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]; return nghttp2_map_each(&session->streams, - nghttp2_update_initial_window_size_func, + nghttp2_update_remote_initial_window_size_func, + &arg); +} + +static int nghttp2_update_local_initial_window_size_func +(nghttp2_map_entry *entry, + void *ptr) +{ + int rv; + nghttp2_update_window_size_arg *arg; + nghttp2_stream *stream; + arg = (nghttp2_update_window_size_arg*)ptr; + stream = (nghttp2_stream*)entry; + if(!stream->local_flow_control) { + return 0; + } + rv = nghttp2_stream_update_local_initial_window_size(stream, + arg->new_window_size, + arg->old_window_size); + if(rv != 0) { + return nghttp2_session_add_rst_stream(arg->session, stream->stream_id, + NGHTTP2_FLOW_CONTROL_ERROR); + } + if(!(arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + if(nghttp2_should_send_window_update(stream->local_window_size, + stream->recv_window_size)) { + rv = nghttp2_session_add_window_update(arg->session, + NGHTTP2_FLAG_NONE, + stream->stream_id, + stream->recv_window_size); + if(rv != 0) { + return rv; + } + stream->recv_window_size = 0; + } + } + return 0; +} + +/* + * Updates the local initial window size of all active streams. If + * error occurs, all streams may not be updated. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int nghttp2_session_update_local_initial_window_size +(nghttp2_session *session, + int32_t new_initial_window_size, + int32_t old_initial_window_size) +{ + nghttp2_update_window_size_arg arg; + arg.session = session; + arg.new_window_size = new_initial_window_size; + arg.old_window_size = old_initial_window_size; + return nghttp2_map_each(&session->streams, + nghttp2_update_local_initial_window_size_func, &arg); } @@ -1973,14 +2035,32 @@ static void nghttp2_session_disable_local_flow_control assert(rv == 0); } -void nghttp2_session_update_local_settings(nghttp2_session *session, - nghttp2_settings_entry *iv, - size_t niv) +int nghttp2_session_update_local_settings(nghttp2_session *session, + nghttp2_settings_entry *iv, + size_t niv) { + int rv; size_t i; uint8_t old_flow_control = session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]; - + uint8_t new_flow_control = old_flow_control; + int32_t new_initial_window_size = -1; + for(i = 0; i < niv; ++i) { + if(iv[i].settings_id == NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE) { + new_initial_window_size = iv[i].value; + } else if(iv[i].settings_id == NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS) { + new_flow_control = iv[i].value; + } + } + if(!old_flow_control && !new_flow_control && new_initial_window_size != -1) { + rv = nghttp2_session_update_local_initial_window_size + (session, + new_initial_window_size, + session->local_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]); + if(rv != 0) { + return rv; + } + } for(i = 0; i < niv; ++i) { assert(iv[i].settings_id > 0 && iv[i].settings_id <= NGHTTP2_SETTINGS_MAX); session->local_settings[iv[i].settings_id] = iv[i].value; @@ -1989,6 +2069,7 @@ void nghttp2_session_update_local_settings(nghttp2_session *session, session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]) { nghttp2_session_disable_local_flow_control(session); } + return 0; } int nghttp2_session_on_settings_received(nghttp2_session *session, @@ -2017,7 +2098,8 @@ int nghttp2_session_on_settings_received(nghttp2_session *session, /* Update the initial window size of the all active streams */ /* Check that initial_window_size < (1u << 31) */ if(entry->value <= NGHTTP2_MAX_WINDOW_SIZE) { - rv = nghttp2_session_update_initial_window_size(session, entry->value); + rv = nghttp2_session_update_remote_initial_window_size + (session, entry->value); if(rv != 0) { if(nghttp2_is_fatal(rv)) { return rv; @@ -2201,7 +2283,7 @@ int nghttp2_session_on_window_update_received(nghttp2_session *session, } return 0; } - if(INT32_MAX - frame->window_update.window_size_increment < + if(NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < session->window_size) { return nghttp2_session_handle_invalid_connection (session, frame, NGHTTP2_FLOW_CONTROL_ERROR); @@ -2552,18 +2634,18 @@ static int nghttp2_session_process_data_frame(nghttp2_session *session) } } -static int32_t adjust_recv_window_size(int32_t recv_window_size, int32_t delta) +/* + * If the resulting recv_window_size is strictly larger than + * NGHTTP2_MAX_WINDOW_SIZE, return NGHTTP2_ERR_FLOW_CONTROL. + */ +static int adjust_recv_window_size(int32_t *recv_window_size_ptr, + int32_t delta) { - /* If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set and the application - does not send WINDOW_UPDATE and the remote endpoint keeps - sending data, stream->recv_window_size will eventually - overflow. */ - if(recv_window_size > INT32_MAX - delta) { - recv_window_size = INT32_MAX; - } else { - recv_window_size += delta; + if(*recv_window_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - delta) { + return NGHTTP2_ERR_FLOW_CONTROL; } - return recv_window_size; + *recv_window_size_ptr += delta; + return 0; } /* @@ -2583,23 +2665,25 @@ static int nghttp2_session_update_recv_stream_window_size nghttp2_stream *stream, int32_t delta_size) { - stream->recv_window_size = adjust_recv_window_size - (stream->recv_window_size, delta_size); + int rv; + rv = adjust_recv_window_size(&stream->recv_window_size, delta_size); + if(rv != 0) { + return nghttp2_session_add_rst_stream(session, stream->stream_id, + NGHTTP2_ERR_FLOW_CONTROL); + } if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { - /* This is just a heuristics. */ /* We have to use local_settings here because it is the constraint the remote endpoint should honor. */ - if((size_t)stream->recv_window_size*2 >= - session->local_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]) { - int r; - r = nghttp2_session_add_window_update(session, + if(nghttp2_should_send_window_update(stream->local_window_size, + stream->recv_window_size)) { + rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, stream->stream_id, stream->recv_window_size); - if(r == 0) { + if(rv == 0) { stream->recv_window_size = 0; } else { - return r; + return rv; } } } @@ -2622,23 +2706,24 @@ static int nghttp2_session_update_recv_connection_window_size (nghttp2_session *session, int32_t delta_size) { - session->recv_window_size = adjust_recv_window_size - (session->recv_window_size, delta_size); + int rv; + rv = adjust_recv_window_size(&session->recv_window_size, delta_size); + if(rv != 0) { + return nghttp2_session_fail_session(session, NGHTTP2_ERR_FLOW_CONTROL); + } if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { - /* Same heuristics above */ - if((size_t)session->recv_window_size*2 >= - NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE) { - int r; + if(nghttp2_should_send_window_update(session->local_window_size, + session->recv_window_size)) { /* Use stream ID 0 to update connection-level flow control window */ - r = nghttp2_session_add_window_update(session, + rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0, session->recv_window_size); - if(r == 0) { + if(rv == 0) { session->recv_window_size = 0; } else { - return r; + return rv; } } } diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index d67ff864..7eaf87a4 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -174,6 +174,11 @@ struct nghttp2_session { /* Keep track of the number of bytes received without WINDOW_UPDATE. */ int32_t recv_window_size; + /* window size for local flow control. It is initially set to + NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE and could be + increased/decreased by submitting WINDOW_UPDATE. See + nghttp2_submit_window_update(). */ + int32_t local_window_size; /* Settings value received from the remote endpoint. We just use ID as index. The index = 0 is unused. */ @@ -517,9 +522,19 @@ nghttp2_outbound_item* nghttp2_session_get_next_ob_item * array pointed by the |iv| is given by the |niv|. This function * assumes that the all settings_id member in |iv| are in range 1 to * NGHTTP2_SETTINGS_MAX, inclusive. + * + * While updating individual stream's local window size, if the window + * size becomes strictly larger than NGHTTP2_MAX_WINDOW_SIZE, + * RST_STREAM is issued against such a stream. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory */ -void nghttp2_session_update_local_settings(nghttp2_session *session, - nghttp2_settings_entry *iv, - size_t niv); +int nghttp2_session_update_local_settings(nghttp2_session *session, + nghttp2_settings_entry *iv, + size_t niv); #endif /* NGHTTP2_SESSION_H */ diff --git a/lib/nghttp2_stream.c b/lib/nghttp2_stream.c index 757ba0d9..24bc9334 100644 --- a/lib/nghttp2_stream.c +++ b/lib/nghttp2_stream.c @@ -77,22 +77,41 @@ void nghttp2_stream_detach_deferred_data(nghttp2_stream *stream) stream->deferred_flags = NGHTTP2_DEFERRED_NONE; } -int nghttp2_stream_update_remote_initial_window_size -(nghttp2_stream *stream, +static int update_initial_window_size +(int32_t *window_size_ptr, int32_t new_initial_window_size, int32_t old_initial_window_size) { - int64_t new_window_size = (int64_t)stream->remote_window_size + + int64_t new_window_size = (int64_t)(*window_size_ptr) + new_initial_window_size - old_initial_window_size; if(INT32_MIN > new_window_size || new_window_size > NGHTTP2_MAX_WINDOW_SIZE) { return -1; } - stream->remote_window_size += - new_initial_window_size - old_initial_window_size; + *window_size_ptr += new_initial_window_size - old_initial_window_size; return 0; } +int nghttp2_stream_update_remote_initial_window_size +(nghttp2_stream *stream, + int32_t new_initial_window_size, + int32_t old_initial_window_size) +{ + return update_initial_window_size(&stream->remote_window_size, + new_initial_window_size, + old_initial_window_size); +} + +int nghttp2_stream_update_local_initial_window_size +(nghttp2_stream *stream, + int32_t new_initial_window_size, + int32_t old_initial_window_size) +{ + return update_initial_window_size(&stream->local_window_size, + new_initial_window_size, + old_initial_window_size); +} + void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream) { stream->state = NGHTTP2_STREAM_OPENED; diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h index f0613891..12877c6a 100644 --- a/lib/nghttp2_stream.h +++ b/lib/nghttp2_stream.h @@ -115,7 +115,7 @@ typedef struct { /* Keep track of the number of bytes received without WINDOW_UPDATE. */ int32_t recv_window_size; - /* window size for local window control. It is initially set to + /* window size for local flow control. It is initially set to NGHTTP2_INITIAL_WINDOW_SIZE and could be increased/decreased by submitting WINDOW_UPDATE. See nghttp2_submit_window_update(). */ int32_t local_window_size; @@ -166,6 +166,19 @@ int nghttp2_stream_update_remote_initial_window_size int32_t new_initial_window_size, int32_t old_initial_window_size); +/* + * Updates the local window size with the new value + * |new_initial_window_size|. The |old_initial_window_size| is used to + * calculate the current window size. + * + * This function returns 0 if it succeeds or -1. The failure is due to + * overflow. + */ +int nghttp2_stream_update_local_initial_window_size +(nghttp2_stream *stream, + int32_t new_initial_window_size, + int32_t old_initial_window_size); + /* * Call this function if promised stream |stream| is replied with * HEADERS. This function makes the state of the |stream| to diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index edd22d8a..343d68c1 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -25,6 +25,7 @@ #include "nghttp2_submit.h" #include +#include #include "nghttp2_session.h" #include "nghttp2_frame.h" @@ -168,10 +169,17 @@ int nghttp2_submit_settings(nghttp2_session *session, } nghttp2_frame_iv_sort(iv_copy, niv); 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) { - nghttp2_session_update_local_settings(session, iv_copy, niv); - } else { + if(r != 0) { + /* The only expected error is fatal one */ + assert(r < NGHTTP2_ERR_FATAL); nghttp2_frame_settings_free(&frame->settings); free(frame); } @@ -215,27 +223,59 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t window_size_increment) { + int rv; nghttp2_stream *stream; flags &= NGHTTP2_FLAG_END_FLOW_CONTROL; if(flags & NGHTTP2_FLAG_END_FLOW_CONTROL) { window_size_increment = 0; - } else if(window_size_increment <= 0) { + } else if(window_size_increment == 0) { return NGHTTP2_ERR_INVALID_ARGUMENT; } if(stream_id == 0) { - return nghttp2_session_add_window_update(session, flags, stream_id, - window_size_increment); + if(!session->local_flow_control) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + rv = nghttp2_adjust_local_window_size(&session->local_window_size, + &session->recv_window_size, + window_size_increment); + if(rv != 0) { + return rv; + } + if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) && + window_size_increment < 0 && + nghttp2_should_send_window_update(session->local_window_size, + session->recv_window_size)) { + window_size_increment = session->recv_window_size; + session->recv_window_size = 0; + } } else { stream = nghttp2_session_get_stream(session, stream_id); if(stream) { - stream->recv_window_size -= nghttp2_min(window_size_increment, - stream->recv_window_size); - return nghttp2_session_add_window_update(session, flags, stream_id, - window_size_increment); + if(!stream->local_flow_control) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + rv = nghttp2_adjust_local_window_size(&stream->local_window_size, + &stream->recv_window_size, + window_size_increment); + if(rv != 0) { + return rv; + } + if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) && + window_size_increment < 0 && + nghttp2_should_send_window_update(stream->local_window_size, + stream->recv_window_size)) { + window_size_increment = stream->recv_window_size; + stream->recv_window_size = 0; + } } else { return NGHTTP2_ERR_STREAM_CLOSED; } } + if(window_size_increment > 0 || (flags & NGHTTP2_FLAG_END_FLOW_CONTROL)) { + return nghttp2_session_add_window_update(session, flags, stream_id, + window_size_increment); + } + return 0; } int nghttp2_submit_request(nghttp2_session *session, int32_t pri, diff --git a/tests/Makefile.am b/tests/Makefile.am index 19a36bbf..0b06d5c9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -35,12 +35,14 @@ OBJECTS = main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c \ nghttp2_session_test.c \ nghttp2_hd_test.c \ nghttp2_npn_test.c \ - nghttp2_gzip_test.c + nghttp2_gzip_test.c \ + nghttp2_helper_test.c HFILES = nghttp2_pq_test.h nghttp2_map_test.h nghttp2_queue_test.h \ nghttp2_buffer_test.h nghttp2_session_test.h \ nghttp2_frame_test.h nghttp2_stream_test.h nghttp2_hd_test.h \ - nghttp2_npn_test.h nghttp2_gzip_test.h nghttp2_test_helper.h + nghttp2_npn_test.h nghttp2_gzip_test.h nghttp2_helper_test.h \ + nghttp2_test_helper.h main_SOURCES = $(HFILES) $(OBJECTS) diff --git a/tests/main.c b/tests/main.c index 725f863f..e25dd81f 100644 --- a/tests/main.c +++ b/tests/main.c @@ -36,6 +36,7 @@ #include "nghttp2_hd_test.h" #include "nghttp2_npn_test.h" #include "nghttp2_gzip_test.h" +#include "nghttp2_helper_test.h" static int init_suite1(void) { @@ -142,10 +143,14 @@ int main(int argc, char* argv[]) !CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) || !CU_add_test(pSuite, "session_submit_settings", test_nghttp2_submit_settings) || + !CU_add_test(pSuite, "session_submit_settings_update_local_window_size", + test_nghttp2_submit_settings_update_local_window_size) || !CU_add_test(pSuite, "session_submit_push_promise", test_nghttp2_submit_push_promise) || !CU_add_test(pSuite, "submit_window_update", test_nghttp2_submit_window_update) || + !CU_add_test(pSuite, "submit_window_update_local_window_size", + test_nghttp2_submit_window_update_local_window_size) || !CU_add_test(pSuite, "submit_invalid_nv", test_nghttp2_submit_invalid_nv) || !CU_add_test(pSuite, "session_open_stream", @@ -231,7 +236,9 @@ int main(int argc, char* argv[]) test_nghttp2_hd_inflate_newname_subst) || !CU_add_test(pSuite, "hd_deflate_inflate", test_nghttp2_hd_deflate_inflate) || - !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) + !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) || + !CU_add_test(pSuite, "adjust_local_window_size", + test_nghttp2_adjust_local_window_size) ) { CU_cleanup_registry(); return CU_get_error(); diff --git a/tests/nghttp2_helper_test.c b/tests/nghttp2_helper_test.c new file mode 100644 index 00000000..83ecfb71 --- /dev/null +++ b/tests/nghttp2_helper_test.c @@ -0,0 +1,75 @@ +/* + * nghttp2 - HTTP/2.0 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_helper_test.h" + +#include + +#include "nghttp2_helper.h" + +void test_nghttp2_adjust_local_window_size(void) +{ + int32_t local_window_size = 100; + int32_t recv_window_size = 50; + + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, 0)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(50 == recv_window_size); + + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, 49)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(1 == recv_window_size); + + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, 1)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(0 == recv_window_size); + + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, 1)); + CU_ASSERT(101 == local_window_size); + CU_ASSERT(0 == recv_window_size); + + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, -1)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(0 == recv_window_size); + + recv_window_size = 50; + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + INT32_MAX)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(50 == recv_window_size); + + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + INT32_MIN)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(50 == recv_window_size); +} diff --git a/tests/nghttp2_helper_test.h b/tests/nghttp2_helper_test.h new file mode 100644 index 00000000..c4940d2c --- /dev/null +++ b/tests/nghttp2_helper_test.h @@ -0,0 +1,30 @@ +/* + * nghttp2 - HTTP/2.0 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_HELPER_TEST_H +#define NGHTTP2_HELPER_TEST_H + +void test_nghttp2_adjust_local_window_size(void); + +#endif /* NGHTTP2_HELPER_TEST_H */ diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 4aab5292..ebe92ed5 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -1927,6 +1927,80 @@ void test_nghttp2_submit_settings(void) nghttp2_session_del(session); } +void test_nghttp2_submit_settings_update_local_window_size(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_settings_entry iv[4]; + nghttp2_stream *stream; + my_user_data ud; + + iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[0].value = 16*1024; + + iv[1].settings_id = NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS; + iv[1].value = 1; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE, + NGHTTP2_PRI_DEFAULT, + NGHTTP2_STREAM_OPENED, NULL); + stream->local_window_size = NGHTTP2_INITIAL_WINDOW_SIZE + 100; + stream->recv_window_size = 32768; + + stream = nghttp2_session_open_stream(session, 3, NGHTTP2_FLAG_NONE, + NGHTTP2_PRI_DEFAULT, + NGHTTP2_STREAM_OPENED, NULL); + stream->local_flow_control = 0; + + CU_ASSERT(0 == nghttp2_submit_settings(session, iv, 1)); + + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(16*1024 + 100 == stream->local_window_size); + + 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); + + nghttp2_session_del(session); + + /* Check flow control disabled case */ + nghttp2_session_server_new(&session, &callbacks, &ud); + stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE, + NGHTTP2_PRI_DEFAULT, + NGHTTP2_STREAM_OPENED, NULL); + + CU_ASSERT(0 == nghttp2_submit_settings(session, iv, 2)); + 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); + 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, iv, 1)); + CU_ASSERT(NGHTTP2_STREAM_CLOSING == stream->state); + + nghttp2_session_del(session); +} + void test_nghttp2_submit_push_promise(void) { nghttp2_session *session; @@ -2025,6 +2099,83 @@ void test_nghttp2_submit_window_update(void) nghttp2_session_del(session); } +void test_nghttp2_submit_window_update_local_window_size(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + stream = nghttp2_session_open_stream(session, 2, + NGHTTP2_FLAG_NONE, + NGHTTP2_PRI_DEFAULT, + NGHTTP2_STREAM_OPENED, NULL); + stream->recv_window_size = 4096; + + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, + stream->recv_window_size + 1)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1 == stream->local_window_size); + CU_ASSERT(0 == stream->recv_window_size); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item)); + CU_ASSERT(4097 == OB_CTRL(item)->window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Let's decrement local window size */ + stream->recv_window_size = 32768; + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, + -stream->local_window_size / 2)); + CU_ASSERT(32768 == stream->local_window_size); + CU_ASSERT(0 == stream->recv_window_size); + 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); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, + NGHTTP2_MAX_WINDOW_SIZE)); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Check connection-level flow control */ + session->recv_window_size = 4096; + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, + session->recv_window_size + 1)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 == + session->local_window_size); + CU_ASSERT(0 == session->recv_window_size); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item)); + CU_ASSERT(4097 == OB_CTRL(item)->window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Go decrement part */ + session->recv_window_size = 32768; + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, + -session->local_window_size/2)); + CU_ASSERT(32768 == session->local_window_size); + CU_ASSERT(0 == session->recv_window_size); + 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); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, + NGHTTP2_MAX_WINDOW_SIZE)); + + nghttp2_session_del(session); +} + void test_nghttp2_submit_invalid_nv(void) { nghttp2_session *session; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index bc23763b..3a5584cf 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -62,8 +62,10 @@ void test_nghttp2_submit_headers_push_reply(void); void test_nghttp2_submit_headers(void); void test_nghttp2_submit_priority(void); void test_nghttp2_submit_settings(void); +void test_nghttp2_submit_settings_update_local_window_size(void); void test_nghttp2_submit_push_promise(void); void test_nghttp2_submit_window_update(void); +void test_nghttp2_submit_window_update_local_window_size(void); void test_nghttp2_submit_invalid_nv(void); void test_nghttp2_session_open_stream(void); void test_nghttp2_session_get_next_ob_item(void);