From c7b0e04498d566a15020ccd86fc49dfb3faf5631 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 15 Jun 2016 00:00:30 +0900 Subject: [PATCH] Add nghttp2_option_set_max_send_header_block_length API function This function sets the maximum length of header block (a set of header fields per HEADERS frame) to send. The length of given set of header fields is calculated using nghttp2_hd_deflate_bound(). Previously, this is hard-coded, and is 64KiB. --- doc/Makefile.am | 1 + lib/includes/nghttp2/nghttp2.h | 15 ++++++++++++ lib/nghttp2_frame.h | 8 +++--- lib/nghttp2_option.c | 6 +++++ lib/nghttp2_option.h | 7 +++++- lib/nghttp2_session.c | 42 +++++++++++++++++++++---------- lib/nghttp2_session.h | 3 +++ tests/main.c | 2 ++ tests/nghttp2_session_test.c | 45 ++++++++++++++++++++++++++++++++++ tests/nghttp2_session_test.h | 1 + 10 files changed, 111 insertions(+), 19 deletions(-) diff --git a/doc/Makefile.am b/doc/Makefile.am index de438968..b24cede6 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -57,6 +57,7 @@ APIDOCS= \ nghttp2_option_new.rst \ nghttp2_option_set_builtin_recv_extension_type.rst \ nghttp2_option_set_max_reserved_remote_streams.rst \ + nghttp2_option_set_max_send_header_block_length.rst \ nghttp2_option_set_no_auto_ping_ack.rst \ nghttp2_option_set_no_auto_window_update.rst \ nghttp2_option_set_no_http_messaging.rst \ diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index c545c31f..a17ff120 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -2412,6 +2412,21 @@ nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option, NGHTTP2_EXTERN void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val); +/** + * @function + * + * This option sets the maximum length of header block (a set of + * header fields per one HEADERS frame) to send. The length of a + * given set of header fields is calculated using + * `nghttp2_hd_deflate_bound()`. The default value is 64KiB. If + * application attempts to send header fields larger than this limit, + * the transmission of the frame fails with error code + * :enum:`NGHTTP2_ERR_FRAME_SIZE_ERROR`. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_max_send_header_block_length(nghttp2_option *option, + size_t val); + /** * @function * diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index 77d8c087..58d3d134 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -52,14 +52,12 @@ #define NGHTTP2_FRAMEBUF_CHUNKLEN \ (NGHTTP2_FRAME_HDLEN + 1 + NGHTTP2_MAX_PAYLOADLEN) -/* Number of inbound buffer */ -#define NGHTTP2_FRAMEBUF_MAX_NUM 5 - /* The default length of DATA frame payload. */ #define NGHTTP2_DATA_PAYLOADLEN NGHTTP2_MAX_FRAME_SIZE_MIN -/* Maximum headers payload length, calculated in compressed form. - This applies to transmission only. */ +/* Maximum headers block size to send, calculated using + nghttp2_hd_deflate_bound(). This is the default value, and can be + overridden by nghttp2_option_set_max_send_header_block_size(). */ #define NGHTTP2_MAX_HEADERSLEN 65536 /* The number of bytes for each SETTINGS entry */ diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c index e8d6ed16..860c9f17 100644 --- a/lib/nghttp2_option.c +++ b/lib/nghttp2_option.c @@ -95,3 +95,9 @@ void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val) { option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_PING_ACK; option->no_auto_ping_ack = val; } + +void nghttp2_option_set_max_send_header_block_length(nghttp2_option *option, + size_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH; + option->max_send_header_block_length = val; +} diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h index 87ef4e04..fb79b920 100644 --- a/lib/nghttp2_option.h +++ b/lib/nghttp2_option.h @@ -62,13 +62,18 @@ typedef enum { NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4, NGHTTP2_OPT_USER_RECV_EXT_TYPES = 1 << 5, NGHTTP2_OPT_NO_AUTO_PING_ACK = 1 << 6, - NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES = 1 << 7 + NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES = 1 << 7, + NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH = 1 << 8, } nghttp2_option_flag; /** * Struct to store option values for nghttp2_session. */ struct nghttp2_option { + /** + * NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH + */ + size_t max_send_header_block_length; /** * Bitwise OR of nghttp2_option_flag to determine that which fields * are specified. diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index b498d968..da105819 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -389,6 +389,7 @@ static int session_new(nghttp2_session **session_ptr, void *user_data, int server, const nghttp2_option *option, nghttp2_mem *mem) { int rv; + size_t nbuffer; if (mem == NULL) { mem = nghttp2_mem_default(); @@ -441,16 +442,6 @@ static int session_new(nghttp2_session **session_ptr, (*session_ptr)->server = 1; } - /* 1 for Pad Field. */ - rv = nghttp2_bufs_init3(&(*session_ptr)->aob.framebufs, - NGHTTP2_FRAMEBUF_CHUNKLEN, NGHTTP2_FRAMEBUF_MAX_NUM, - 1, NGHTTP2_FRAME_HDLEN + 1, mem); - if (rv != 0) { - goto fail_aob_framebuf; - } - - active_outbound_item_reset(&(*session_ptr)->aob, mem); - init_settings(&(*session_ptr)->remote_settings); init_settings(&(*session_ptr)->local_settings); @@ -460,6 +451,8 @@ static int session_new(nghttp2_session **session_ptr, /* Limit max outgoing concurrent streams to sensible value */ (*session_ptr)->remote_settings.max_concurrent_streams = 100; + (*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN; + if (option) { if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) && option->no_auto_window_update) { @@ -504,8 +497,31 @@ static int session_new(nghttp2_session **session_ptr, option->no_auto_ping_ack) { (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_PING_ACK; } + + if (option->opt_set_mask & NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH) { + (*session_ptr)->max_send_header_block_length = + option->max_send_header_block_length; + } } + nbuffer = ((*session_ptr)->max_send_header_block_length + + NGHTTP2_FRAMEBUF_CHUNKLEN - 1) / + NGHTTP2_FRAMEBUF_CHUNKLEN; + + if (nbuffer == 0) { + nbuffer = 1; + } + + /* 1 for Pad Field. */ + rv = nghttp2_bufs_init3(&(*session_ptr)->aob.framebufs, + NGHTTP2_FRAMEBUF_CHUNKLEN, nbuffer, 1, + NGHTTP2_FRAME_HDLEN + 1, mem); + if (rv != 0) { + goto fail_aob_framebuf; + } + + active_outbound_item_reset(&(*session_ptr)->aob, mem); + (*session_ptr)->callbacks = *callbacks; (*session_ptr)->user_data = user_data; @@ -1951,7 +1967,7 @@ static int session_prep_frame(nghttp2_session *session, session, frame->headers.nva, frame->headers.nvlen, NGHTTP2_PRIORITY_SPECLEN); - if (estimated_payloadlen > NGHTTP2_MAX_HEADERSLEN) { + if (estimated_payloadlen > session->max_send_header_block_length) { return NGHTTP2_ERR_FRAME_SIZE_ERROR; } @@ -1970,7 +1986,7 @@ static int session_prep_frame(nghttp2_session *session, session, frame->headers.nva, frame->headers.nvlen, NGHTTP2_PRIORITY_SPECLEN); - if (estimated_payloadlen > NGHTTP2_MAX_HEADERSLEN) { + if (estimated_payloadlen > session->max_send_header_block_length) { return NGHTTP2_ERR_FRAME_SIZE_ERROR; } @@ -2089,7 +2105,7 @@ static int session_prep_frame(nghttp2_session *session, estimated_payloadlen = session_estimate_headers_payload( session, frame->push_promise.nva, frame->push_promise.nvlen, 0); - if (estimated_payloadlen > NGHTTP2_MAX_HEADERSLEN) { + if (estimated_payloadlen > session->max_send_header_block_length) { return NGHTTP2_ERR_FRAME_SIZE_ERROR; } diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index 58f1aba4..6dfa0a3f 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -256,6 +256,9 @@ struct nghttp2_session { size_t nvbuflen; /* Counter for detecting flooding in outbound queue */ size_t obq_flood_counter_; + /* The maximum length of header block to send. Calculated by the + same way as nghttp2_hd_deflate_bound() does. */ + size_t max_send_header_block_length; /* Next Stream ID. Made unsigned int to detect >= (1 << 31). */ uint32_t next_stream_id; /* The last stream ID this session initiated. For client session, diff --git a/tests/main.c b/tests/main.c index 02fbe415..b4e638b1 100644 --- a/tests/main.c +++ b/tests/main.c @@ -183,6 +183,8 @@ int main(int argc _U_, char *argv[] _U_) { !CU_add_test(pSuite, "submit_headers", test_nghttp2_submit_headers) || !CU_add_test(pSuite, "submit_headers_continuation", test_nghttp2_submit_headers_continuation) || + !CU_add_test(pSuite, "submit_headers_continuation_extra_large", + test_nghttp2_submit_headers_continuation_extra_large) || !CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) || !CU_add_test(pSuite, "session_submit_settings", test_nghttp2_submit_settings) || diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 6af7f536..35b51435 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -4842,6 +4842,51 @@ void test_nghttp2_submit_headers_continuation(void) { nghttp2_session_del(session); } +void test_nghttp2_submit_headers_continuation_extra_large(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv nv[] = { + MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""), + MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""), + }; + nghttp2_outbound_item *item; + uint8_t data[16384]; + size_t i; + my_user_data ud; + nghttp2_option *opt; + + memset(data, '0', sizeof(data)); + for (i = 0; i < ARRLEN(nv); ++i) { + nv[i].valuelen = sizeof(data); + nv[i].value = data; + } + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + /* The default size of max send header block length is too small to + send these header fields. Expand it. */ + nghttp2_option_new(&opt); + nghttp2_option_set_max_send_header_block_length(opt, 102400); + + CU_ASSERT(0 == nghttp2_session_client_new2(&session, &callbacks, &ud, opt)); + CU_ASSERT(1 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, + NULL, nv, ARRLEN(nv), NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == + item->frame.hd.flags); + CU_ASSERT(0 == (item->frame.hd.flags & NGHTTP2_FLAG_PRIORITY)); + + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_session_del(session); + nghttp2_option_del(opt); +} + void test_nghttp2_submit_priority(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index a1898a27..3e2e7738 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -87,6 +87,7 @@ void test_nghttp2_submit_headers_reply(void); void test_nghttp2_submit_headers_push_reply(void); void test_nghttp2_submit_headers(void); void test_nghttp2_submit_headers_continuation(void); +void test_nghttp2_submit_headers_continuation_extra_large(void); void test_nghttp2_submit_priority(void); void test_nghttp2_submit_settings(void); void test_nghttp2_submit_settings_update_local_window_size(void);