From cbdd44c4ae5d311964f6deeb2f542df6f88bcfe6 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 26 Oct 2013 01:01:28 +0900 Subject: [PATCH] nghttp2_hd: Implement local header table size limit for encoder --- lib/includes/nghttp2/nghttp2.h | 8 ++ lib/nghttp2_hd.c | 211 +++++++++++++++++++++++++------ lib/nghttp2_hd.h | 63 ++++++++- tests/main.c | 4 + tests/nghttp2_hd_test.c | 225 +++++++++++++++++++++++++++++++++ tests/nghttp2_hd_test.h | 2 + 6 files changed, 469 insertions(+), 44 deletions(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 93c0009a..e006fb94 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -389,6 +389,14 @@ typedef enum { * The SETTINGS ID. */ typedef enum { + /** + * SETTINGS_HEADER_TABLE_SIZE + */ + NGHTTP2_SETTINGS_HEADER_TABLE_SIZE = 1, + /** + * SETTINGS_ENABLE_PUSH + */ + NGHTTP2_SETTINGS_ENABLE_PUSH = 2, /** * SETTINGS_MAX_CONCURRENT_STREAMS */ diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c index 5ab52e39..e0381b49 100644 --- a/lib/nghttp2_hd.c +++ b/lib/nghttp2_hd.c @@ -187,6 +187,31 @@ static nghttp2_hd_entry* nghttp2_hd_ringbuf_get(nghttp2_hd_ringbuf *ringbuf, return ringbuf->buffer[(ringbuf->first + index) & ringbuf->mask]; } +static int nghttp2_hd_ringbuf_reserve(nghttp2_hd_ringbuf *ringbuf, + size_t bufsize) +{ + size_t i; + size_t size; + nghttp2_hd_entry **buffer; + + if(ringbuf->mask + 1 >= bufsize) { + return 0; + } + for(size = 1; size < bufsize; size <<= 1); + buffer = malloc(sizeof(nghttp2_hd_entry*) * size); + if(buffer == NULL) { + return NGHTTP2_ERR_NOMEM; + } + for(i = 0; i < ringbuf->len; ++i) { + buffer[i] = nghttp2_hd_ringbuf_get(ringbuf, i); + } + free(ringbuf->buffer); + ringbuf->buffer = buffer; + ringbuf->mask = size - 1; + ringbuf->first = 0; + return 0; +} + static void nghttp2_hd_ringbuf_free(nghttp2_hd_ringbuf *ringbuf) { size_t i; @@ -220,15 +245,16 @@ static void nghttp2_hd_ringbuf_pop_back(nghttp2_hd_ringbuf *ringbuf) static int nghttp2_hd_context_init(nghttp2_hd_context *context, nghttp2_hd_role role, nghttp2_hd_side side, - size_t hd_table_bufsize_max) + size_t local_hd_table_bufsize_max) { int rv; context->role = role; context->side = side; context->bad = 0; - context->hd_table_bufsize_max = hd_table_bufsize_max; - rv = nghttp2_hd_ringbuf_init(&context->hd_table, - hd_table_bufsize_max/NGHTTP2_HD_ENTRY_OVERHEAD); + context->hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; + rv = nghttp2_hd_ringbuf_init + (&context->hd_table, + context->hd_table_bufsize_max/NGHTTP2_HD_ENTRY_OVERHEAD); if(rv != 0) { return rv; } @@ -255,6 +281,9 @@ static int nghttp2_hd_context_init(nghttp2_hd_context *context, context->buf_track = NULL; context->buf_track_capacity = 0; } + context->local_hd_table_bufsize_max = local_hd_table_bufsize_max; + context->local_hd_table_bufsize = 0; + context->local_hd_tablelen = 0; context->emit_setlen = 0; context->buf_tracklen = 0; context->hd_table_bufsize = 0; @@ -269,29 +298,20 @@ static int nghttp2_hd_context_init(nghttp2_hd_context *context, int nghttp2_hd_deflate_init(nghttp2_hd_context *deflater, nghttp2_hd_side side) { return nghttp2_hd_context_init(deflater, NGHTTP2_HD_ROLE_DEFLATE, side, - NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE); + NGHTTP2_HD_DEFAULT_LOCAL_MAX_BUFFER_SIZE); } int nghttp2_hd_deflate_init2(nghttp2_hd_context *deflater, nghttp2_hd_side side, - size_t hd_table_bufsize_max) + size_t local_hd_table_bufsize_max) { return nghttp2_hd_context_init(deflater, NGHTTP2_HD_ROLE_DEFLATE, side, - hd_table_bufsize_max); + local_hd_table_bufsize_max); } int nghttp2_hd_inflate_init(nghttp2_hd_context *inflater, nghttp2_hd_side side) { - return nghttp2_hd_context_init(inflater, NGHTTP2_HD_ROLE_INFLATE, side, - NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE); -} - -int nghttp2_hd_inflate_init2(nghttp2_hd_context *inflater, - nghttp2_hd_side side, - size_t hd_table_bufsize_max) -{ - return nghttp2_hd_context_init(inflater, NGHTTP2_HD_ROLE_INFLATE, side, - hd_table_bufsize_max); + return nghttp2_hd_context_init(inflater, NGHTTP2_HD_ROLE_INFLATE, side, 0); } static void nghttp2_hd_context_free(nghttp2_hd_context *context) @@ -680,20 +700,25 @@ static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context, int rv; nghttp2_hd_entry *new_ent; size_t room = entry_room(nv->namelen, nv->valuelen); - context->hd_table_bufsize += room; - while(context->hd_table_bufsize > context->hd_table_bufsize_max && + while(context->hd_table_bufsize + room > context->hd_table_bufsize_max && context->hd_table.len > 0) { size_t index = context->hd_table.len - 1; nghttp2_hd_entry* ent = nghttp2_hd_ringbuf_get(&context->hd_table, index); context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen); - if(context->role == NGHTTP2_HD_ROLE_DEFLATE && - (ent->flags & NGHTTP2_HD_FLAG_IMPLICIT_EMIT)) { - /* Emit common header just before it slips away from the - table. If we don't do this, we have to emit it in literal - representation which hurts compression. */ - rv = emit_implicit(buf_ptr, buflen_ptr, offset_ptr, index); - if(rv != 0) { - return NULL; + if(context->role == NGHTTP2_HD_ROLE_DEFLATE) { + if(context->hd_table_bufsize < context->local_hd_table_bufsize) { + context->local_hd_table_bufsize -= entry_room(ent->nv.namelen, + ent->nv.valuelen); + --context->local_hd_tablelen; + } + if(ent->flags & NGHTTP2_HD_FLAG_IMPLICIT_EMIT) { + /* Emit common header just before it slips away from the + table. If we don't do this, we have to emit it in literal + representation which hurts compression. */ + rv = emit_implicit(buf_ptr, buflen_ptr, offset_ptr, index); + if(rv != 0) { + return NULL; + } } } nghttp2_hd_ringbuf_pop_back(&context->hd_table); @@ -702,22 +727,94 @@ static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context, free(ent); } } + if(context->role == NGHTTP2_HD_ROLE_DEFLATE) { + while(context->local_hd_table_bufsize + room > + context->local_hd_table_bufsize_max + && context->local_hd_tablelen > 0) { + size_t index = context->local_hd_tablelen - 1; + nghttp2_hd_entry *ent = + nghttp2_hd_ringbuf_get(&context->hd_table, index); + context->local_hd_table_bufsize -= entry_room(ent->nv.namelen, + ent->nv.valuelen); + --context->local_hd_tablelen; + if(ent->flags & NGHTTP2_HD_FLAG_IMPLICIT_EMIT) { + /* Just like a normal eviction, implicit header must be + emitted twice. */ + rv = emit_implicit(buf_ptr, buflen_ptr, offset_ptr, index); + if(rv != 0) { + return NULL; + } + ent->flags ^= NGHTTP2_HD_FLAG_IMPLICIT_EMIT; + } + if(ent->flags & NGHTTP2_HD_FLAG_REFSET) { + /* We need to drop entry from reference set. */ + rv = emit_indexed_block(buf_ptr, buflen_ptr, offset_ptr, index); + if(rv != 0) { + return NULL; + } + ent->flags ^= NGHTTP2_HD_FLAG_REFSET; + } + /* Release memory. We don't remove entry from the header table + at this moment. */ + if(ent->flags & NGHTTP2_HD_FLAG_NAME_ALLOC) { + free(ent->nv.name); + ent->nv.name = NULL; + ent->flags ^= NGHTTP2_HD_FLAG_NAME_ALLOC; + } + if(ent->flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) { + free(ent->nv.value); + ent->nv.value = NULL; + ent->flags ^= NGHTTP2_HD_FLAG_VALUE_ALLOC; + } + } + } + new_ent = malloc(sizeof(nghttp2_hd_entry)); if(new_ent == NULL) { return NULL; } - rv = nghttp2_hd_entry_init(new_ent, - entry_flags, - nv->name, nv->namelen, nv->value, nv->valuelen); - if(rv != 0) { - free(new_ent); - return NULL; + + if(context->role == NGHTTP2_HD_ROLE_DEFLATE && + room > context->local_hd_table_bufsize_max) { + uint8_t flags = entry_flags & + ~(NGHTTP2_HD_FLAG_NAME_ALLOC | NGHTTP2_HD_FLAG_VALUE_ALLOC | + NGHTTP2_HD_FLAG_NAME_GIFT | NGHTTP2_HD_FLAG_VALUE_GIFT); + rv = nghttp2_hd_entry_init(new_ent, flags, + NULL, nv->namelen, NULL, nv->valuelen); + if(rv != 0) { + free(new_ent); + return NULL; + } + if(flags & NGHTTP2_HD_FLAG_NAME_GIFT) { + free(nv->name); + nv->name = NULL; + } + if(flags & NGHTTP2_HD_FLAG_VALUE_GIFT) { + free(nv->value); + nv->value = NULL; + } + /* caller must emit indexed repr to toggle off new_ent from + reference set. We cannot do it here because it may break the + indexing. */ + } else { + rv = nghttp2_hd_entry_init(new_ent, + entry_flags, + nv->name, nv->namelen, nv->value, nv->valuelen); + if(rv != 0) { + free(new_ent); + return NULL; + } + if(context->role == NGHTTP2_HD_ROLE_DEFLATE) { + context->local_hd_table_bufsize += room; + ++context->local_hd_tablelen; + } } if(room > context->hd_table_bufsize_max) { /* The entry taking more than NGHTTP2_HD_MAX_BUFFER_SIZE is immediately evicted. */ --new_ent->ref; } else { + context->hd_table_bufsize += room; new_ent->flags |= NGHTTP2_HD_FLAG_REFSET; nghttp2_hd_ringbuf_push_front(&context->hd_table, new_ent); } @@ -727,7 +824,9 @@ static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context, static ssize_t find_in_hd_table(nghttp2_hd_context *context, nghttp2_nv *nv) { size_t i; - for(i = 0; i < context->hd_table.len; ++i) { + size_t max = context->role == NGHTTP2_HD_ROLE_DEFLATE ? + context->local_hd_tablelen : context->hd_table.len; + for(i = 0; i < max; ++i) { nghttp2_hd_entry *ent = nghttp2_hd_ringbuf_get(&context->hd_table, i); if(nghttp2_nv_equal(&ent->nv, nv)) { return i; @@ -746,7 +845,9 @@ static ssize_t find_name_in_hd_table(nghttp2_hd_context *context, nghttp2_nv *nv) { size_t i; - for(i = 0; i < context->hd_table.len; ++i) { + size_t max = context->role == NGHTTP2_HD_ROLE_DEFLATE ? + context->local_hd_tablelen : context->hd_table.len; + for(i = 0; i < max; ++i) { nghttp2_hd_entry *ent = nghttp2_hd_ringbuf_get(&context->hd_table, i); if(ent->nv.namelen == nv->namelen && memcmp(ent->nv.name, nv->name, nv->namelen) == 0) { @@ -763,6 +864,37 @@ static ssize_t find_name_in_hd_table(nghttp2_hd_context *context, return -1; } +int nghttp2_hd_change_table_size(nghttp2_hd_context *context, + size_t hd_table_bufsize_max) +{ + int rv; + rv = nghttp2_hd_ringbuf_reserve + (&context->hd_table, hd_table_bufsize_max / NGHTTP2_HD_ENTRY_OVERHEAD); + if(rv != 0) { + return rv; + } + context->hd_table_bufsize_max = hd_table_bufsize_max; + while(context->hd_table_bufsize > context->hd_table_bufsize_max && + context->hd_table.len > 0) { + size_t index = context->hd_table.len - 1; + nghttp2_hd_entry* ent = nghttp2_hd_ringbuf_get(&context->hd_table, index); + context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen); + if(context->role == NGHTTP2_HD_ROLE_DEFLATE) { + if(context->hd_table_bufsize < context->local_hd_table_bufsize) { + context->local_hd_table_bufsize -= entry_room(ent->nv.namelen, + ent->nv.valuelen); + --context->local_hd_tablelen; + } + } + nghttp2_hd_ringbuf_pop_back(&context->hd_table); + if(--ent->ref == 0) { + nghttp2_hd_entry_free(ent); + free(ent); + } + } + return 0; +} + static int check_index_range(nghttp2_hd_context *context, size_t index) { return index < context->hd_table.len + STATIC_TABLE_LENGTH; @@ -804,7 +936,10 @@ static int deflate_nv(nghttp2_hd_context *deflater, if(new_ent->ref == 0) { nghttp2_hd_entry_free(new_ent); free(new_ent); - } else { + new_ent = NULL; + } else if(new_ent->nv.name != NULL) { + /* new_ent->ref > 0 and nv.name is not NULL means that new_ent is + in the reference set and in local_hd_table_bufsize */ new_ent->flags |= NGHTTP2_HD_FLAG_EMIT; } rv = emit_indexed_block(buf_ptr, buflen_ptr, offset_ptr, index); @@ -877,7 +1012,9 @@ static int deflate_nv(nghttp2_hd_context *deflater, if(new_ent->ref == 0) { nghttp2_hd_entry_free(new_ent); free(new_ent); - } else { + } else if(new_ent->nv.name != NULL) { + /* new_ent->ref > 0 and nv.name is not NULL means that new_ent is + in the reference set and in local_hd_table_bufsize */ new_ent->flags |= NGHTTP2_HD_FLAG_EMIT; } incidx = 1; diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h index 113b9c8c..612ac924 100644 --- a/lib/nghttp2_hd.h +++ b/lib/nghttp2_hd.h @@ -38,6 +38,11 @@ #define NGHTTP2_HD_MAX_ENTRY_SIZE 3072 #define NGHTTP2_HD_ENTRY_OVERHEAD 32 +/* Default size of maximum table buffer size for encoder. Even if + remote decoder notifies larger buffer size for its decoding, + encoder only uses the memory up to this value. */ +#define NGHTTP2_HD_DEFAULT_LOCAL_MAX_BUFFER_SIZE (1 << 12) + typedef enum { NGHTTP2_HD_SIDE_REQUEST = 0, NGHTTP2_HD_SIDE_RESPONSE = 1 @@ -85,6 +90,24 @@ typedef struct { typedef struct { /* dynamic header table */ nghttp2_hd_ringbuf hd_table; + /* The header table size for decoding. If the context is initialized + as encoder, this value is advertised by remote endpoint + decoder. */ + size_t hd_table_bufsize_max; + /* The current effective header table size for encoding. This value + is meaningful iff this context is initialized as + encoder. |local_hd_table_bufsize| <= |hd_table_bufsize| must be + hold. */ + size_t local_hd_table_bufsize; + /* The maximum effective header table for encoding. Although header + table size is bounded by |hd_table_bufsize_max|, the encoder can + use smaller buffer by not retaining the header name/values beyond + the |local_hd_table_bufsize_max| and not referencing those + entries. This value is meaningful iff this context is initialized + as encoder. */ + size_t local_hd_table_bufsize_max; + /* The number of effective entry in |hd_table|. */ + size_t local_hd_tablelen; /* Holding emitted entry in deflating header block to retain reference count. */ nghttp2_hd_entry **emit_set; @@ -105,8 +128,6 @@ typedef struct { /* NGHTTP2_HD_SIDE_REQUEST for processing request, otherwise response. */ nghttp2_hd_side side; - /* Maximum header table size */ - size_t hd_table_bufsize_max; /* Keep track of allocated buffers in inflation */ uint8_t **buf_track; /* The capacity of |buf_track| */ @@ -137,6 +158,11 @@ void nghttp2_hd_entry_free(nghttp2_hd_entry *ent); /* * Initializes |deflater| for deflating name/values pairs. * + * The encoder only uses up to + * NGHTTP2_HD_DEFAULT_LOCAL_MAX_BUFFER_SIZE bytes for header table + * even if the larger value is specified later in + * nghttp2_hd_change_table_size(). + * * This function returns 0 if it succeeds, or one of the following * negative error codes: * @@ -146,9 +172,22 @@ void nghttp2_hd_entry_free(nghttp2_hd_entry *ent); int nghttp2_hd_deflate_init(nghttp2_hd_context *deflater, nghttp2_hd_side side); +/* + * Initializes |deflater| for deflating name/values pairs. + * + * The encoder only uses up to |local_hd_table_bufsize_max| bytes for + * header table even if the larger value is specified later in + * nghttp2_hd_change_table_size(). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ int nghttp2_hd_deflate_init2(nghttp2_hd_context *deflater, nghttp2_hd_side side, - size_t hd_table_bufsize_max); + size_t local_hd_table_bufsize_max); /* * Initializes |inflater| for inflating name/values pairs. @@ -162,10 +201,6 @@ int nghttp2_hd_deflate_init2(nghttp2_hd_context *deflater, int nghttp2_hd_inflate_init(nghttp2_hd_context *inflater, nghttp2_hd_side side); -int nghttp2_hd_inflate_init2(nghttp2_hd_context *inflater, - nghttp2_hd_side side, - size_t hd_table_bufsize_max); - /* * Deallocates any resources allocated for |deflater|. */ @@ -176,6 +211,20 @@ void nghttp2_hd_deflate_free(nghttp2_hd_context *deflater); */ void nghttp2_hd_inflate_free(nghttp2_hd_context *inflater); + +/* + * Changes header table size in |context|. This may trigger eviction + * in the dynamic table. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_hd_change_table_size(nghttp2_hd_context *context, + size_t hd_table_bufsize_max); + /* * Deflates the |nva|, which has the |nvlen| name/value pairs, into * the buffer pointed by the |*buf_ptr| with the length |*buflen_ptr|. diff --git a/tests/main.c b/tests/main.c index 73c7b69b..2141c02a 100644 --- a/tests/main.c +++ b/tests/main.c @@ -231,6 +231,8 @@ int main(int argc, char* argv[]) test_nghttp2_hd_deflate_same_indexed_repr) || !CU_add_test(pSuite, "hd_deflate_common_header_eviction", test_nghttp2_hd_deflate_common_header_eviction) || + !CU_add_test(pSuite, "hd_deflate_local_buffer", + test_nghttp2_hd_deflate_local_buffer) || !CU_add_test(pSuite, "hd_inflate_indname_inc", test_nghttp2_hd_inflate_indname_inc) || !CU_add_test(pSuite, "hd_inflate_indname_inc_eviction", @@ -239,6 +241,8 @@ int main(int argc, char* argv[]) test_nghttp2_hd_inflate_newname_inc) || !CU_add_test(pSuite, "hd_inflate_clearall_inc", test_nghttp2_hd_inflate_clearall_inc) || + !CU_add_test(pSuite, "hd_change_table_size", + test_nghttp2_hd_change_table_size) || !CU_add_test(pSuite, "hd_deflate_inflate", test_nghttp2_hd_deflate_inflate) || !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) || diff --git a/tests/nghttp2_hd_test.c b/tests/nghttp2_hd_test.c index 5a578449..0f9bfafa 100644 --- a/tests/nghttp2_hd_test.c +++ b/tests/nghttp2_hd_test.c @@ -247,6 +247,197 @@ void test_nghttp2_hd_deflate_common_header_eviction(void) nghttp2_hd_deflate_free(&deflater); } +void test_nghttp2_hd_deflate_local_buffer(void) +{ + nghttp2_hd_context deflater, inflater; + size_t i; + ssize_t rv, blocklen; + uint8_t *buf = NULL; + size_t buflen = 0; + nghttp2_nv nva1[] = { MAKE_NV("k1", "v1"), /* 36 */ + MAKE_NV("k10", "v10"), /* 38 */ + MAKE_NV("k100", "v100"), /* 40 */ + MAKE_NV("k1000", "v1000") /* 42 */ + }; /* Total: 156 */ + nghttp2_nv nva2[] = { MAKE_NV("k10", "v10"), /* 38 */ + MAKE_NV("k1", "v1") /* 36 */ + }; + nghttp2_nv nv3; + uint8_t val[256]; + nghttp2_nv nva4[] = { MAKE_NV(":method", "GET"), + MAKE_NV(":scheme", "http") + }; + nghttp2_nv *resnva; + nghttp2_hd_entry *ent; + + memset(val, 'a', sizeof(val)); + nv3.name = nv3.value = val; + nv3.namelen = nv3.valuelen = sizeof(val); + + /* Check the case where entry from static table is inserted to + dynamic header table. And it is out of local header table + size. */ + nghttp2_hd_deflate_init2(&deflater, NGHTTP2_HD_SIDE_REQUEST, 32); + nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_REQUEST); + blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, + nva4, ARRLEN(nva4)); + CU_ASSERT(blocklen > 0); + /* Now header table should look like this: + * + * 0: :scheme, http (-) + * 1: :method, GET (-) + * + * name/value of all entries must be NULL. + */ + CU_ASSERT(2 == deflater.hd_table.len); + CU_ASSERT(0 == deflater.local_hd_tablelen); + CU_ASSERT(0 == deflater.local_hd_table_bufsize); + for(i = 0; i < 2; ++i) { + ent = nghttp2_hd_table_get(&deflater, i); + CU_ASSERT(ent->nv.name == NULL); + CU_ASSERT(ent->nv.value == NULL); + CU_ASSERT(0 == (ent->flags & NGHTTP2_HD_FLAG_REFSET)); + } + + rv = nghttp2_hd_inflate_hd(&inflater, &resnva, buf, blocklen); + CU_ASSERT(2 == rv); + assert_nv_equal(nva4, resnva, 2); + nghttp2_hd_end_headers(&inflater); + nghttp2_nv_array_del(resnva); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_hd_inflate_free(&inflater); + + /* 156 buffer size can hold all headers in local region */ + nghttp2_hd_deflate_init2(&deflater, NGHTTP2_HD_SIDE_REQUEST, + 156); + blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, + nva1, ARRLEN(nva1)); + CU_ASSERT(blocklen > 0); + /* Now header table should look like this: + * + * 0: k1000, v100 + * 1: k100, v100 + * 2: k10, v10 + * 3: k1, v1 + */ + CU_ASSERT(4 == deflater.hd_table.len); + CU_ASSERT(4 == deflater.local_hd_tablelen); + CU_ASSERT(156 == deflater.local_hd_table_bufsize); + for(i = 0; i < 4; ++i) { + CU_ASSERT(nghttp2_hd_table_get(&deflater, i)->nv.name != NULL); + CU_ASSERT(nghttp2_hd_table_get(&deflater, i)->nv.value != NULL); + } + + CU_ASSERT(0 == nghttp2_hd_change_table_size(&deflater, 156)); + CU_ASSERT(4 == deflater.hd_table.len); + CU_ASSERT(4 == deflater.local_hd_tablelen); + CU_ASSERT(156 == deflater.local_hd_table_bufsize); + + blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, &nv3, 1); + CU_ASSERT(blocklen > 0); + /* Now header table should be empty */ + CU_ASSERT(0 == deflater.hd_table.len); + CU_ASSERT(0 == deflater.local_hd_tablelen); + CU_ASSERT(0 == deflater.local_hd_table_bufsize); + + nghttp2_hd_deflate_free(&deflater); + + /* Check more complex use case */ + nghttp2_hd_deflate_init2(&deflater, NGHTTP2_HD_SIDE_REQUEST, + 155); + nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_REQUEST); + blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, + nva1, ARRLEN(nva1)); + CU_ASSERT(blocklen > 0); + /* Now header table should look like this: + * + * 0: k1000, v100 (R) + * 1: k100, v100 (R) + * 2: k10, v10 (R) + * 3: k1, v1 (-) <- name, value must be NULL and not in reference set + * + * But due to the local table size limit, name/value of index=3 must + * be NULL. + */ + CU_ASSERT(4 == deflater.hd_table.len); + CU_ASSERT(3 == deflater.local_hd_tablelen); + CU_ASSERT(120 == deflater.local_hd_table_bufsize); + for(i = 0; i < 3; ++i) { + CU_ASSERT(nghttp2_hd_table_get(&deflater, i)->nv.name != NULL); + CU_ASSERT(nghttp2_hd_table_get(&deflater, i)->nv.value != NULL); + } + ent = nghttp2_hd_table_get(&deflater, 3); + CU_ASSERT(ent->nv.name == NULL); + CU_ASSERT(ent->nv.value == NULL); + CU_ASSERT(0 == (ent->flags & NGHTTP2_HD_FLAG_REFSET)); + + rv = nghttp2_hd_inflate_hd(&inflater, &resnva, buf, blocklen); + CU_ASSERT(4 == rv); + assert_nv_equal(nva1, resnva, 4); + nghttp2_hd_end_headers(&inflater); + nghttp2_nv_array_del(resnva); + + blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, + nva2, ARRLEN(nva2)); + CU_ASSERT(blocklen > 0); + /* Now header table should look like this: + * + * 0: k1, v1 (R) + * 1: k1000, v100 (R) + * 2: k100, v100 (R) + * 3: k10, v10 (-) <- name, value must be NULL + * 4: k1, v1 (-) <- name, value must be NULL + */ + CU_ASSERT(5 == deflater.hd_table.len); + CU_ASSERT(3 == deflater.local_hd_tablelen); + CU_ASSERT(118 == deflater.local_hd_table_bufsize); + ent = nghttp2_hd_table_get(&deflater, 3); + CU_ASSERT(0 == (ent->flags & NGHTTP2_HD_FLAG_REFSET)); + ent = nghttp2_hd_table_get(&deflater, 3); + CU_ASSERT(0 == (ent->flags & NGHTTP2_HD_FLAG_REFSET)); + + rv = nghttp2_hd_inflate_hd(&inflater, &resnva, buf, blocklen); + CU_ASSERT(2 == rv); + /* Sort before comparison */ + nghttp2_nv_array_sort(nva2, 2); + assert_nv_equal(nva2, resnva, 2); + nghttp2_hd_end_headers(&inflater); + nghttp2_nv_array_del(resnva); + + blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, &nv3, 1); + CU_ASSERT(blocklen > 0); + /* Now header table should look like this: + * + * 0: a..a, a..a (-) + * 1: k1, v1 (-) + * 2: k1000, v100 (-) + * 3: k100, v100 (-) + * 4: k10, v10 (-) + * 5: k1, v1 (-) + * + * name/value of all entries must be NULL. + */ + CU_ASSERT(6 == deflater.hd_table.len); + CU_ASSERT(0 == deflater.local_hd_tablelen); + CU_ASSERT(0 == deflater.local_hd_table_bufsize); + for(i = 0; i < 6; ++i) { + ent = nghttp2_hd_table_get(&deflater, i); + CU_ASSERT(0 == (ent->flags & NGHTTP2_HD_FLAG_REFSET)); + } + + rv = nghttp2_hd_inflate_hd(&inflater, &resnva, buf, blocklen); + CU_ASSERT(1 == rv); + assert_nv_equal(&nv3, resnva, 1); + nghttp2_hd_end_headers(&inflater); + nghttp2_nv_array_del(resnva); + + free(buf); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + +} + void test_nghttp2_hd_inflate_indname_inc(void) { nghttp2_hd_context inflater; @@ -389,6 +580,40 @@ void test_nghttp2_hd_inflate_clearall_inc(void) nghttp2_hd_inflate_free(&inflater); } +void test_nghttp2_hd_change_table_size(void) +{ + nghttp2_hd_context deflater; + nghttp2_nv nva[] = { MAKE_NV(":method", "GET"), + MAKE_NV(":path", "/") }; + uint8_t *buf = NULL; + size_t buflen = 0; + ssize_t rv; + + nghttp2_hd_deflate_init(&deflater, NGHTTP2_HD_SIDE_REQUEST); + CU_ASSERT(0 == nghttp2_hd_change_table_size(&deflater, 8000)); + CU_ASSERT(255 == deflater.hd_table.mask); + CU_ASSERT(8000 == deflater.hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, nva, 2); + CU_ASSERT(rv > 0); + CU_ASSERT(2 == deflater.hd_table.len); + + CU_ASSERT(0 == nghttp2_hd_change_table_size(&deflater, 16384)); + CU_ASSERT(511 == deflater.hd_table.mask); + CU_ASSERT(2 == deflater.hd_table.len); + CU_ASSERT(2 == deflater.local_hd_tablelen); + CU_ASSERT(5 == + deflater.hd_table.buffer[deflater.hd_table.first]->nv.namelen); + + CU_ASSERT(0 == nghttp2_hd_change_table_size(&deflater, 0)); + CU_ASSERT(511 == deflater.hd_table.mask); + CU_ASSERT(0 == deflater.hd_table.len); + CU_ASSERT(0 == deflater.local_hd_tablelen); + + free(buf); + nghttp2_hd_deflate_free(&deflater); +} + static void check_deflate_inflate(nghttp2_hd_context *deflater, nghttp2_hd_context *inflater, nghttp2_nv *nva, size_t nvlen) diff --git a/tests/nghttp2_hd_test.h b/tests/nghttp2_hd_test.h index eab10001..db52ec93 100644 --- a/tests/nghttp2_hd_test.h +++ b/tests/nghttp2_hd_test.h @@ -28,10 +28,12 @@ void test_nghttp2_hd_deflate(void); void test_nghttp2_hd_deflate_same_indexed_repr(void); void test_nghttp2_hd_deflate_common_header_eviction(void); +void test_nghttp2_hd_deflate_local_buffer(void); void test_nghttp2_hd_inflate_indname_inc(void); void test_nghttp2_hd_inflate_indname_inc_eviction(void); void test_nghttp2_hd_inflate_newname_inc(void); void test_nghttp2_hd_inflate_clearall_inc(void); +void test_nghttp2_hd_change_table_size(void); void test_nghttp2_hd_deflate_inflate(void); #endif /* NGHTTP2_HD_TEST_H */