From 204f9a3ec705d86174efd8470ce7b2a715e2e086 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 29 May 2016 23:13:11 +0900 Subject: [PATCH] Add nghttp2_session_set_local_window_size() API function --- lib/includes/nghttp2/nghttp2.h | 45 +++++++++++++++- lib/nghttp2_helper.c | 32 +++++++++++ lib/nghttp2_helper.h | 16 ++++++ lib/nghttp2_submit.c | 69 ++++++++++++++++++++++++ tests/main.c | 2 + tests/nghttp2_session_test.c | 98 ++++++++++++++++++++++++++++++++++ tests/nghttp2_session_test.h | 1 + 7 files changed, 261 insertions(+), 2 deletions(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index bf2bebd8..c545c31f 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -4071,14 +4071,17 @@ nghttp2_session_check_server_session(nghttp2_session *session); * 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. + * difference. If the sole intention is to increase the local window + * size, consider to use `nghttp2_session_set_local_window_size()`. * * If the |window_size_increment| is negative, the local window size * is decreased by -|window_size_increment|. If automatic * WINDOW_UPDATE is enabled * (`nghttp2_option_set_no_auto_window_update()`), and the library * decided that the WINDOW_UPDATE should be submitted, then - * WINDOW_UPDATE is queued with the current received bytes count. + * WINDOW_UPDATE is queued with the current received bytes count. If + * the sole intention is to decrease the local window size, consider + * to use `nghttp2_session_set_local_window_size()`. * * If the |window_size_increment| is 0, the function does nothing and * returns 0. @@ -4096,6 +4099,44 @@ NGHTTP2_EXTERN int nghttp2_submit_window_update(nghttp2_session *session, int32_t stream_id, int32_t window_size_increment); +/** + * @function + * + * Set local window size (local endpoints's window size) to the given + * |window_size| for the given stream denoted by |stream_id|. To + * change connection level window size, specify 0 to |stream_id|. To + * increase window size, this function may submit WINDOW_UPDATE frame + * to transmission queue. + * + * The |flags| is currently ignored and should be + * :enum:`NGHTTP2_FLAG_NONE`. + * + * This sounds similar to `nghttp2_submit_window_update()`, but there + * are 2 differences. The first difference is that this function + * takes the absolute value of window size to set, rather than the + * delta. To change the window size, this may be easier to use since + * the application just declares the intended window size, rather than + * calculating delta. The second difference is that + * `nghttp2_submit_window_update()` affects the received bytes count + * which has not acked yet. By the specification of + * `nghttp2_submit_window_update()`, to strictly increase the local + * window size, we have to submit delta including all received bytes + * count, which might not be desirable in some cases. On the other + * hand, this function does not affect the received bytes count. It + * just sets the local window size to the given value. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is negative. + * :enum:`NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_set_local_window_size(nghttp2_session *session, uint8_t flags, + int32_t stream_id, int32_t window_size); + /** * @function * diff --git a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c index 9f208519..e133d0b0 100644 --- a/lib/nghttp2_helper.c +++ b/lib/nghttp2_helper.c @@ -213,6 +213,38 @@ int nghttp2_adjust_local_window_size(int32_t *local_window_size_ptr, return 0; } +int nghttp2_increase_local_window_size(int32_t *local_window_size_ptr, + int32_t *recv_window_size_ptr, + int32_t *recv_reduction_ptr, + int32_t *delta_ptr) { + int32_t recv_reduction_delta; + int32_t delta; + + delta = *delta_ptr; + + assert(delta >= 0); + + /* The delta size is strictly more than received bytes. Increase + local_window_size by that difference |delta|. */ + if (*local_window_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - delta) { + return NGHTTP2_ERR_FLOW_CONTROL; + } + + *local_window_size_ptr += delta; + /* If there is recv_reduction due to earlier window_size + reduction, we have to adjust it too. */ + recv_reduction_delta = nghttp2_min(*recv_reduction_ptr, delta); + *recv_reduction_ptr -= recv_reduction_delta; + + *recv_window_size_ptr += recv_reduction_delta; + + /* recv_reduction_delta must be paied from *delta_ptr, since it was + added in window size reduction (see below). */ + *delta_ptr -= recv_reduction_delta; + + return 0; +} + int nghttp2_should_send_window_update(int32_t local_window_size, int32_t recv_window_size) { return recv_window_size > 0 && recv_window_size >= local_window_size / 2; diff --git a/lib/nghttp2_helper.h b/lib/nghttp2_helper.h index 1fff65c0..4a32564f 100644 --- a/lib/nghttp2_helper.h +++ b/lib/nghttp2_helper.h @@ -89,6 +89,22 @@ int nghttp2_adjust_local_window_size(int32_t *local_window_size_ptr, int32_t *recv_reduction_ptr, int32_t *delta_ptr); +/* + * This function works like nghttp2_adjust_local_window_size(). The + * difference is that this function assumes *delta_ptr >= 0, and + * *recv_window_size_ptr is not decreased by *delta_ptr. + * + * 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_increase_local_window_size(int32_t *local_window_size_ptr, + int32_t *recv_window_size_ptr, + int32_t *recv_reduction_ptr, + int32_t *delta_ptr); + /* * Returns non-zero if the function decided that WINDOW_UPDATE should * be sent. diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index 18149d2d..cf9a7c0c 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -410,6 +410,75 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags, return 0; } +int nghttp2_session_set_local_window_size(nghttp2_session *session, + uint8_t flags, int32_t stream_id, + int32_t window_size) { + int32_t window_size_increment; + nghttp2_stream *stream; + int rv; + + if (window_size < 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + flags = 0; + + if (stream_id == 0) { + window_size_increment = window_size - session->local_window_size; + + if (window_size_increment == 0) { + return 0; + } + + if (window_size_increment < 0) { + return nghttp2_adjust_local_window_size( + &session->local_window_size, &session->recv_window_size, + &session->recv_reduction, &window_size_increment); + } + + rv = nghttp2_increase_local_window_size( + &session->local_window_size, &session->recv_window_size, + &session->recv_reduction, &window_size_increment); + + if (rv != 0) { + return rv; + } + } else { + stream = nghttp2_session_get_stream(session, stream_id); + + if (stream == NULL) { + return 0; + } + + window_size_increment = window_size - stream->local_window_size; + + if (window_size_increment == 0) { + return 0; + } + + if (window_size_increment < 0) { + return nghttp2_adjust_local_window_size( + &stream->local_window_size, &stream->recv_window_size, + &stream->recv_reduction, &window_size_increment); + } + + rv = nghttp2_increase_local_window_size( + &stream->local_window_size, &stream->recv_window_size, + &stream->recv_reduction, &window_size_increment); + + if (rv != 0) { + return rv; + } + } + + if (window_size_increment > 0) { + return nghttp2_session_add_window_update(session, flags, stream_id, + window_size_increment); + } + + return 0; +} + int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags _U_, int32_t stream_id, const uint8_t *origin, size_t origin_len, const uint8_t *field_value, diff --git a/tests/main.c b/tests/main.c index 0b17dcd7..02fbe415 100644 --- a/tests/main.c +++ b/tests/main.c @@ -302,6 +302,8 @@ int main(int argc _U_, char *argv[] _U_) { test_nghttp2_session_repeated_priority_change) || !CU_add_test(pSuite, "session_repeated_priority_submission", test_nghttp2_session_repeated_priority_submission) || + !CU_add_test(pSuite, "session_set_local_window_size", + test_nghttp2_session_set_local_window_size) || !CU_add_test(pSuite, "http_mandatory_headers", test_nghttp2_http_mandatory_headers) || !CU_add_test(pSuite, "http_content_length", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 1589b4df..6af7f536 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -9405,6 +9405,104 @@ void test_nghttp2_session_repeated_priority_submission(void) { nghttp2_session_del(session); } +void test_nghttp2_session_set_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 = open_sent_stream(session, 1); + stream->recv_window_size = 4096; + + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 65536)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 == + stream->local_window_size); + CU_ASSERT(4096 == stream->recv_window_size); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(1 == item->frame.window_update.hd.stream_id); + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Go decrement part */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 32768)); + CU_ASSERT(32768 == stream->local_window_size); + CU_ASSERT(-28672 == stream->recv_window_size); + CU_ASSERT(32768 == stream->recv_reduction); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item == NULL); + + /* Increase local window size */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 49152)); + CU_ASSERT(49152 == stream->local_window_size); + CU_ASSERT(-12288 == stream->recv_window_size); + CU_ASSERT(16384 == stream->recv_reduction); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + /* Increase local window again */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 65537)); + CU_ASSERT(65537 == stream->local_window_size); + CU_ASSERT(4096 == stream->recv_window_size); + CU_ASSERT(0 == stream->recv_reduction); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Check connection-level flow control */ + session->recv_window_size = 4096; + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 65536)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 == + session->local_window_size); + CU_ASSERT(4096 == session->recv_window_size); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(0 == item->frame.window_update.hd.stream_id); + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Go decrement part */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 32768)); + CU_ASSERT(32768 == session->local_window_size); + CU_ASSERT(-28672 == session->recv_window_size); + CU_ASSERT(32768 == session->recv_reduction); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item == NULL); + + /* Increase local window size */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 49152)); + CU_ASSERT(49152 == session->local_window_size); + CU_ASSERT(-12288 == session->recv_window_size); + CU_ASSERT(16384 == session->recv_reduction); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + /* Increase local window again */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 65537)); + CU_ASSERT(65537 == session->local_window_size); + CU_ASSERT(4096 == session->recv_window_size); + CU_ASSERT(0 == session->recv_reduction); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_session_del(session); +} + static void check_nghttp2_http_recv_headers_fail( nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id, int stream_state, const nghttp2_nv *nva, size_t nvlen) { diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 3787b6e4..a1898a27 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -149,6 +149,7 @@ void test_nghttp2_session_change_stream_priority(void); void test_nghttp2_session_create_idle_stream(void); void test_nghttp2_session_repeated_priority_change(void); void test_nghttp2_session_repeated_priority_submission(void); +void test_nghttp2_session_set_local_window_size(void); void test_nghttp2_http_mandatory_headers(void); void test_nghttp2_http_content_length(void); void test_nghttp2_http_content_length_mismatch(void);