From 005e60a5ed634a33559f37456877b9d2503cd000 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Fri, 23 Aug 2013 23:38:28 +0900 Subject: [PATCH] Rework header compression --- lib/nghttp2_hd.c | 359 +++++++++++++++++++++++----------------- lib/nghttp2_hd.h | 1 + tests/main.c | 2 + tests/nghttp2_hd_test.c | 57 ++++++- tests/nghttp2_hd_test.h | 1 + 5 files changed, 262 insertions(+), 158 deletions(-) diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c index 14aaa89a..d3f4bee3 100644 --- a/lib/nghttp2_hd.c +++ b/lib/nghttp2_hd.c @@ -99,6 +99,12 @@ static const char *reshd_table[] = { NULL }; +typedef struct { + nghttp2_nv *nva; + size_t nvacap; + size_t nvlen; +} nghttp2_nva_out; + int nghttp2_hd_entry_init(nghttp2_hd_entry *ent, uint8_t index, uint8_t flags, uint8_t *name, uint16_t namelen, uint8_t *value, uint16_t valuelen) @@ -158,7 +164,8 @@ void nghttp2_hd_entry_free(nghttp2_hd_entry *ent) } static int nghttp2_hd_context_init(nghttp2_hd_context *context, - nghttp2_hd_side side) + nghttp2_hd_side side, + uint8_t inflater) { int i; const char **ini_table; @@ -173,16 +180,22 @@ static int nghttp2_hd_context_init(nghttp2_hd_context *context, context->hd_table_capacity = NGHTTP2_INITIAL_HD_TABLE_SIZE; context->hd_tablelen = 0; - context->emit_set = malloc(sizeof(nghttp2_hd_entry*)* - NGHTTP2_INITIAL_EMIT_SET_SIZE); - if(context->emit_set == NULL) { - free(context->hd_table); - return NGHTTP2_ERR_NOMEM; + if(inflater) { + context->emit_set = malloc(sizeof(nghttp2_hd_entry*)* + NGHTTP2_INITIAL_EMIT_SET_SIZE); + if(context->emit_set == NULL) { + free(context->hd_table); + return NGHTTP2_ERR_NOMEM; + } + memset(context->emit_set, 0, sizeof(nghttp2_hd_entry*)* + NGHTTP2_INITIAL_EMIT_SET_SIZE); + context->emit_set_capacity = NGHTTP2_INITIAL_EMIT_SET_SIZE; + context->emit_setlen = 0; + } else { + context->emit_set = NULL; + context->emit_set_capacity = 0; + context->emit_setlen = 0; } - memset(context->emit_set, 0, sizeof(nghttp2_hd_entry*)* - NGHTTP2_INITIAL_EMIT_SET_SIZE); - context->emit_set_capacity = NGHTTP2_INITIAL_EMIT_SET_SIZE; - context->emit_setlen = 0; if(side == NGHTTP2_HD_SIDE_CLIENT) { ini_table = reqhd_table; @@ -214,12 +227,12 @@ 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, side); + return nghttp2_hd_context_init(deflater, side, 0); } int nghttp2_hd_inflate_init(nghttp2_hd_context *inflater, nghttp2_hd_side side) { - return nghttp2_hd_context_init(inflater, side^1); + return nghttp2_hd_context_init(inflater, side^1, 1); } static void nghttp2_hd_context_free(nghttp2_hd_context *context) @@ -257,7 +270,76 @@ static size_t entry_room(size_t namelen, size_t valuelen) return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + valuelen; } +static int add_nva(nghttp2_nva_out *nva_out_ptr, + uint8_t *name, uint16_t namelen, + uint8_t *value, uint16_t valuelen) +{ + nghttp2_nv *nv; + if(nva_out_ptr->nvacap == nva_out_ptr->nvlen) { + size_t newcap = nva_out_ptr->nvacap == 0 ? 16 : nva_out_ptr->nvacap * 2; + nghttp2_nv *new_nva = realloc(nva_out_ptr->nva, sizeof(nghttp2_nv)*newcap); + if(new_nva == NULL) { + return NGHTTP2_ERR_NOMEM; + } + nva_out_ptr->nva = new_nva; + nva_out_ptr->nvacap = newcap; + } + nv = &nva_out_ptr->nva[nva_out_ptr->nvlen++]; + nv->name = name; + nv->namelen = namelen; + nv->value = value; + nv->valuelen = valuelen; + return 0; +} + +static int add_emit_set(nghttp2_hd_context *context, nghttp2_hd_entry *ent) +{ + if(context->emit_setlen == context->emit_set_capacity) { + return NGHTTP2_ERR_HEADER_COMP; + } + context->emit_set[context->emit_setlen++] = ent; + ++ent->ref; + return 0; +} + +static int emit_indexed_header(nghttp2_hd_context *context, + nghttp2_nva_out *nva_out_ptr, + nghttp2_hd_entry *ent) +{ + int rv; + rv = add_emit_set(context, ent); + if(rv != 0) { + return rv; + } + ent->flags |= NGHTTP2_HD_FLAG_EMIT; + return add_nva(nva_out_ptr, + ent->nv.name, ent->nv.namelen, + ent->nv.value, ent->nv.valuelen); +} + +static int emit_newname_header(nghttp2_hd_context *context, + nghttp2_nva_out *nva_out_ptr, + nghttp2_nv *nv) +{ + return add_nva(nva_out_ptr, + nv->name, nv->namelen, nv->value, nv->valuelen); +} + +static int emit_indname_header(nghttp2_hd_context *context, + nghttp2_nva_out *nva_out_ptr, + nghttp2_hd_entry *ent, + uint8_t *value, size_t valuelen) +{ + int rv; + rv = add_emit_set(context, ent); + if(rv != 0) { + return rv; + } + return add_nva(nva_out_ptr, ent->nv.name, ent->nv.namelen, value, valuelen); +} + static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context, + nghttp2_nva_out *nva_out_ptr, nghttp2_nv *nv) { int rv; @@ -274,6 +356,14 @@ static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context, nghttp2_hd_entry *ent = context->hd_table[i]; context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen); ent->index = NGHTTP2_HD_INVALID_INDEX; + if(nva_out_ptr && + (ent->flags & NGHTTP2_HD_FLAG_REFSET) && + (ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) { + rv = emit_indexed_header(context, nva_out_ptr, ent); + if(rv != 0) { + return NULL; + } + } if(--ent->ref == 0) { nghttp2_hd_entry_free(ent); free(ent); @@ -304,6 +394,7 @@ static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context, } static nghttp2_hd_entry* add_hd_table_subst(nghttp2_hd_context *context, + nghttp2_nva_out *nva_out_ptr, nghttp2_nv *nv, size_t subindex) { int rv; @@ -328,6 +419,14 @@ static nghttp2_hd_entry* add_hd_table_subst(nghttp2_hd_context *context, ent->nv.valuelen); } ent->index = NGHTTP2_HD_INVALID_INDEX; + if(nva_out_ptr && + (ent->flags & NGHTTP2_HD_FLAG_REFSET) && + (ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) { + rv = emit_indexed_header(context, nva_out_ptr, ent); + if(rv != 0) { + return NULL; + } + } if(--ent->ref == 0) { nghttp2_hd_entry_free(ent); free(ent); @@ -353,6 +452,14 @@ static nghttp2_hd_entry* add_hd_table_subst(nghttp2_hd_context *context, if(k >= 0) { nghttp2_hd_entry *ent = context->hd_table[k]; ent->index = NGHTTP2_HD_INVALID_INDEX; + if(nva_out_ptr && + (ent->flags & NGHTTP2_HD_FLAG_REFSET) && + (ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) { + rv = emit_indexed_header(context, nva_out_ptr, ent); + if(rv != 0) { + return NULL; + } + } if(--ent->ref == 0) { nghttp2_hd_entry_free(ent); free(ent); @@ -372,82 +479,6 @@ static nghttp2_hd_entry* add_hd_table_subst(nghttp2_hd_context *context, return new_ent; } -static int add_nva(nghttp2_nv **nva_ptr, size_t *nvacap_ptr, - ssize_t *nvlen_ptr, - uint8_t *name, uint16_t namelen, - uint8_t *value, uint16_t valuelen) -{ - nghttp2_nv *nv; - if((ssize_t)*nvacap_ptr == *nvlen_ptr) { - size_t newcap = *nvacap_ptr == 0 ? 16 : *nvacap_ptr * 2; - nghttp2_nv *new_nva = realloc(*nva_ptr, sizeof(nghttp2_nv)*newcap); - if(new_nva == NULL) { - return NGHTTP2_ERR_NOMEM; - } - *nva_ptr = new_nva; - *nvacap_ptr = newcap; - } - nv = &(*nva_ptr)[(*nvlen_ptr)++]; - nv->name = name; - nv->namelen = namelen; - nv->value = value; - nv->valuelen = valuelen; - return 0; -} - -static int add_emit_set(nghttp2_hd_context *context, nghttp2_hd_entry *ent) -{ - if(context->emit_setlen == context->emit_set_capacity) { - return NGHTTP2_ERR_HEADER_COMP; - } - context->emit_set[context->emit_setlen++] = ent; - ++ent->ref; - return 0; -} - -static int emit_indexed_header(nghttp2_hd_context *context, - nghttp2_nv **nva_ptr, - size_t *nvacap_ptr, - ssize_t *nvlen_ptr, - nghttp2_hd_entry *ent) -{ - int rv; - rv = add_emit_set(context, ent); - if(rv != 0) { - return rv; - } - ent->flags |= NGHTTP2_HD_FLAG_EMIT; - return add_nva(nva_ptr, nvacap_ptr, nvlen_ptr, - ent->nv.name, ent->nv.namelen, - ent->nv.value, ent->nv.valuelen); -} - -static int emit_newname_header(nghttp2_hd_context *context, - nghttp2_nv **nva_ptr, - size_t *nvacap_ptr, - ssize_t *nvlen_ptr, - nghttp2_nv *nv) -{ - return add_nva(nva_ptr, nvacap_ptr, nvlen_ptr, - nv->name, nv->namelen, nv->value, nv->valuelen); -} - -static int emit_indname_header(nghttp2_hd_context *context, - nghttp2_nv **nva_ptr, - size_t *nvacap_ptr, - ssize_t *nvlen_ptr, - nghttp2_hd_entry *ent, - uint8_t *value, size_t valuelen) -{ - int rv; - rv = add_emit_set(context, ent); - if(rv != 0) { - return rv; - } - return add_nva(nva_ptr, nvacap_ptr, nvlen_ptr, - ent->nv.name, ent->nv.namelen, value, valuelen); -} - static nghttp2_hd_entry* find_in_hd_table(nghttp2_hd_context *context, nghttp2_nv *nv) { @@ -691,30 +722,88 @@ static int emit_subst_newname_block(uint8_t **buf_ptr, size_t *buflen_ptr, return 0; } +static int intcompar(const void *lhs, const void *rhs) +{ + return *(int*)lhs - *(int*)rhs; +} + ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_context *deflater, uint8_t **buf_ptr, size_t *buflen_ptr, size_t nv_offset, nghttp2_nv *nv, size_t nvlen) { - size_t i, offset; + size_t i, j, k, offset; int rv = 0; + int common_hd[NGHTTP2_INITIAL_HD_TABLE_SIZE]; + size_t common_hdlen = 0; if(deflater->bad) { return NGHTTP2_ERR_HEADER_COMP; } offset = nv_offset; - for(i = 0; i < nvlen; ++i) { + /* First emit indexed repr to remove deleted header fields */ + for(i = 0; i < deflater->hd_tablelen; ++i) { + nghttp2_hd_entry *ent = deflater->hd_table[i]; + if((ent->flags & NGHTTP2_HD_FLAG_REFSET) == 0) { + continue; + } + for(j = 0; j < nvlen; ++j) { + if(nghttp2_nv_equal(&ent->nv, &nv[j])) { + break; + } + } + if(j < nvlen) { + /* Assuming the encoder does not put duplicated entry in the + header table, this entry is common header field between + previous header set. So it is "implicitly emitted". */ + ent->flags |= NGHTTP2_HD_FLAG_IMPLICIT_EMIT; + common_hd[common_hdlen++] = j; + continue; + } + ent->flags ^= NGHTTP2_HD_FLAG_REFSET; + rv = emit_indexed_block(buf_ptr, buflen_ptr, &offset, ent->index); + if(rv < 0) { + goto fail; + } + } + qsort(common_hd, common_hdlen, sizeof(int), intcompar); + for(i = 0, k = 0; i < nvlen; ++i) { nghttp2_hd_entry *ent; + if(k < common_hdlen) { + /* Skip implicitly emitted name/value pair */ + if((uint8_t)common_hd[k] == i) { + ++k; + continue; + } + } ent = find_in_hd_table(deflater, &nv[i]); if(ent) { if((ent->flags & NGHTTP2_HD_FLAG_REFSET) == 0) { ent->flags |= NGHTTP2_HD_FLAG_REFSET | NGHTTP2_HD_FLAG_EMIT; rv = emit_indexed_block(buf_ptr, buflen_ptr, &offset, ent->index); } else { - /* The common header in reference set could be removed on - eviction. In that case, we have to add it again. The bad - thing is that we could not know it happens here. So defer - its processing after all headers are processed. */ - rv = add_emit_set(deflater, ent); + size_t num_emits = 0; + if(ent->flags & NGHTTP2_HD_FLAG_EMIT) { + /* occurrences of the same indexed representation. Emit index + twice. */ + num_emits = 2; + } else if(ent->flags & NGHTTP2_HD_FLAG_IMPLICIT_EMIT) { + /* ent was implicitly emitted because it is the common + header field. To support occurrences of the same indexed + representation, we have to emit 4 times. This is because + "implicitly emitted" means actually not emitted at + all. So first 2 emits performs 1st header appears in the + reference set. And another 2 emits are done for 2nd + (current) header. */ + ent->flags ^= NGHTTP2_HD_FLAG_IMPLICIT_EMIT; + ent->flags |= NGHTTP2_HD_FLAG_EMIT; + num_emits = 4; + } + for(j = 0; j < num_emits; ++j) { + rv = emit_indexed_block(buf_ptr, buflen_ptr, &offset, ent->index); + if(rv != 0) { + break; + } + } } if(rv < 0) { goto fail; @@ -729,7 +818,7 @@ ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_context *deflater, if(entry_room(nv[i].namelen, nv[i].valuelen) < NGHTTP2_HD_MAX_ENTRY_SIZE) { nghttp2_hd_entry *new_ent; - new_ent = add_hd_table_incremental(deflater, &nv[i]); + new_ent = add_hd_table_incremental(deflater, NULL, &nv[i]); if(!new_ent) { rv = NGHTTP2_ERR_HEADER_COMP; goto fail; @@ -749,47 +838,9 @@ ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_context *deflater, } } } - - for(i = 0; i < deflater->emit_setlen; ++i) { - nghttp2_hd_entry *ent = deflater->emit_set[i]; - if((ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0 && - ent->index == NGHTTP2_HD_INVALID_INDEX) { - /* If common header is removed from the header table, use - incremental indexing. */ - uint8_t index = NGHTTP2_HD_INVALID_INDEX; - nghttp2_hd_entry *new_ent; - nghttp2_hd_entry *name_ent = find_name_in_hd_table(deflater, &ent->nv); - if(name_ent) { - index = name_ent->index; - } - new_ent = add_hd_table_incremental(deflater, &ent->nv); - if(!new_ent) { - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - new_ent->flags |= NGHTTP2_HD_FLAG_EMIT; - if(index == NGHTTP2_HD_INVALID_INDEX) { - rv = emit_newname_block(buf_ptr, buflen_ptr, &offset, &ent->nv, 1); - } else { - rv = emit_indname_block(buf_ptr, buflen_ptr, &offset, index, - ent->nv.value, ent->nv.valuelen, 1); - } - } - ent->flags |= NGHTTP2_HD_FLAG_EMIT; - } - for(i = 0; i < deflater->hd_tablelen; ++i) { nghttp2_hd_entry *ent = deflater->hd_table[i]; - if(ent->flags & NGHTTP2_HD_FLAG_REFSET) { - if((ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) { - ent->flags ^= NGHTTP2_HD_FLAG_REFSET; - rv = emit_indexed_block(buf_ptr, buflen_ptr, &offset, ent->index); - if(rv < 0) { - goto fail; - } - } - } - ent->flags &= ~NGHTTP2_HD_FLAG_EMIT; + ent->flags &= ~(NGHTTP2_HD_FLAG_EMIT | NGHTTP2_HD_FLAG_IMPLICIT_EMIT); } return offset - nv_offset; fail: @@ -803,9 +854,9 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, { size_t i; int rv = 0; - ssize_t nvlen = 0; - size_t nvacap = 0; uint8_t *last = in + inlen; + nghttp2_nva_out nva_out; + memset(&nva_out, 0, sizeof(nva_out)); if(inflater->bad) { return NGHTTP2_ERR_HEADER_COMP; } @@ -828,7 +879,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, ent = inflater->hd_table[index]; ent->flags ^= NGHTTP2_HD_FLAG_REFSET; if(ent->flags & NGHTTP2_HD_FLAG_REFSET) { - rv = emit_indexed_header(inflater, nva_ptr, &nvacap, &nvlen, ent); + rv = emit_indexed_header(inflater, &nva_out, ent); if(rv < 0) { goto fail; } @@ -860,12 +911,12 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, in += valuelen; nghttp2_downcase(nv.name, nv.namelen); if(c == 0x60u) { - rv = emit_newname_header(inflater, nva_ptr, &nvacap, &nvlen, &nv); + rv = emit_newname_header(inflater, &nva_out, &nv); } else { - nghttp2_hd_entry *new_ent = add_hd_table_incremental(inflater, &nv); + nghttp2_hd_entry *new_ent; + new_ent = add_hd_table_incremental(inflater, &nva_out, &nv); if(new_ent) { - rv = emit_indexed_header(inflater, nva_ptr, &nvacap, &nvlen, - new_ent); + rv = emit_indexed_header(inflater, &nva_out, new_ent); } else { rv = NGHTTP2_ERR_HEADER_COMP; goto fail; @@ -900,8 +951,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, value = in; in += valuelen; if((c & 0x60u) == 0x60u) { - rv = emit_indname_header(inflater, nva_ptr, &nvacap, &nvlen, ent, - value, valuelen); + rv = emit_indname_header(inflater, &nva_out, ent, value, valuelen); } else { nghttp2_nv nv; nghttp2_hd_entry *new_ent; @@ -910,14 +960,13 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, nv.namelen = ent->nv.namelen; nv.value = value; nv.valuelen = valuelen; - new_ent = add_hd_table_incremental(inflater, &nv); + new_ent = add_hd_table_incremental(inflater, &nva_out, &nv); if(--ent->ref == 0) { nghttp2_hd_entry_free(ent); free(ent); } if(new_ent) { - rv = emit_indexed_header(inflater, nva_ptr, &nvacap, &nvlen, - new_ent); + rv = emit_indexed_header(inflater, &nva_out, new_ent); } else { rv = NGHTTP2_ERR_HEADER_COMP; goto fail; @@ -957,9 +1006,9 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, nv.valuelen = valuelen; in += valuelen; nghttp2_downcase(nv.name, nv.namelen); - new_ent = add_hd_table_subst(inflater, &nv, subindex); + new_ent = add_hd_table_subst(inflater, &nva_out, &nv, subindex); if(new_ent) { - rv = emit_indexed_header(inflater, nva_ptr, &nvacap, &nvlen, new_ent); + rv = emit_indexed_header(inflater, &nva_out, new_ent); if(rv < 0) { goto fail; } @@ -1000,13 +1049,13 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, nv.value = in; nv.valuelen = valuelen; in += valuelen; - new_ent = add_hd_table_subst(inflater, &nv, subindex); + new_ent = add_hd_table_subst(inflater, &nva_out, &nv, subindex); if(--ent->ref == 0) { nghttp2_hd_entry_free(ent); free(ent); } if(new_ent) { - rv = emit_indexed_header(inflater, nva_ptr, &nvacap, &nvlen, new_ent); + rv = emit_indexed_header(inflater, &nva_out, new_ent); if(rv < 0) { goto fail; } @@ -1020,17 +1069,19 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, nghttp2_hd_entry *ent = inflater->hd_table[i]; if((ent->flags & NGHTTP2_HD_FLAG_REFSET) && (ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) { - rv = emit_indexed_header(inflater, nva_ptr, &nvacap, &nvlen, ent); + rv = emit_indexed_header(inflater, &nva_out, ent); if(rv < 0) { goto fail; } } ent->flags &= ~NGHTTP2_HD_FLAG_EMIT; } - nghttp2_nv_array_sort(*nva_ptr, nvlen); - return nvlen; + nghttp2_nv_array_sort(nva_out.nva, nva_out.nvlen); + *nva_ptr = nva_out.nva; + return nva_out.nvlen; fail: inflater->bad = 1; + free(nva_out.nva); return rv; } diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h index 2d747d71..2d2ea3c6 100644 --- a/lib/nghttp2_hd.h +++ b/lib/nghttp2_hd.h @@ -58,6 +58,7 @@ typedef enum { /* Indicates that the entry is emitted in the current header processing. */ NGHTTP2_HD_FLAG_EMIT = 1 << 3, + NGHTTP2_HD_FLAG_IMPLICIT_EMIT = 1 << 4 } nghttp2_hd_flags; typedef struct { diff --git a/tests/main.c b/tests/main.c index 9b682440..78383991 100644 --- a/tests/main.c +++ b/tests/main.c @@ -220,6 +220,8 @@ int main(int argc, char* argv[]) test_nghttp2_nv_array_from_cstr) || !CU_add_test(pSuite, "iv_check", test_nghttp2_iv_check) || !CU_add_test(pSuite, "hd_deflate", test_nghttp2_hd_deflate) || + !CU_add_test(pSuite, "hd_deflate_same_indexed_repr", + test_nghttp2_hd_deflate_same_indexed_repr) || !CU_add_test(pSuite, "hd_inflate_indname_inc", test_nghttp2_hd_inflate_indname_inc) || !CU_add_test(pSuite, "hd_inflate_indname_inc_eviction", diff --git a/tests/nghttp2_hd_test.c b/tests/nghttp2_hd_test.c index a9800ec9..894f678f 100644 --- a/tests/nghttp2_hd_test.c +++ b/tests/nghttp2_hd_test.c @@ -111,17 +111,16 @@ void test_nghttp2_hd_deflate(void) nghttp2_nv_array_del(resnva); nghttp2_hd_end_headers(&inflater); - /* Fourth headers, including duplicate header fields. We don't - encode duplicates. Only first one is encoded. */ + /* Fourth headers, including duplicate header fields. */ blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, nv_offset, nva4, sizeof(nva4)/sizeof(nghttp2_nv)); CU_ASSERT(blocklen > 0); nghttp2_hd_end_headers(&deflater); - CU_ASSERT(2 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf + nv_offset, + CU_ASSERT(3 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf + nv_offset, blocklen)); - assert_nv_equal(nva4, resnva, 2); + assert_nv_equal(nva4, resnva, 3); nghttp2_nv_array_del(resnva); nghttp2_hd_end_headers(&inflater); @@ -146,6 +145,56 @@ void test_nghttp2_hd_deflate(void) nghttp2_hd_deflate_free(&deflater); } +void test_nghttp2_hd_deflate_same_indexed_repr(void) +{ + nghttp2_hd_context deflater, inflater; + nghttp2_nv nva1[] = {MAKE_NV("cookie", "alpha"), + MAKE_NV("cookie", "alpha")}; + nghttp2_nv nva2[] = {MAKE_NV("cookie", "alpha"), + MAKE_NV("cookie", "alpha"), + MAKE_NV("cookie", "alpha")}; + uint8_t *buf = NULL; + size_t buflen = 0; + nghttp2_nv *resnva; + ssize_t blocklen; + + CU_ASSERT(0 == nghttp2_hd_deflate_init(&deflater, NGHTTP2_HD_SIDE_CLIENT)); + CU_ASSERT(0 == nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_SERVER)); + + /* Encode 2 same headers. cookie:alpha is not in the reference set, + so first emit literal repr and then 2 emits of indexed repr. */ + blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, nva1, + sizeof(nva1)/sizeof(nghttp2_nv)); + CU_ASSERT(blocklen > 0); + nghttp2_hd_end_headers(&deflater); + + CU_ASSERT(2 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf, blocklen)); + + assert_nv_equal(nva1, resnva, 2); + + nghttp2_nv_array_del(resnva); + nghttp2_hd_end_headers(&inflater); + + /* Encode 3 same headers. This time, cookie:alpha is in the + reference set, so the encoder emits indexed repr 6 times */ + blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, nva1, + sizeof(nva2)/sizeof(nghttp2_nv)); + CU_ASSERT(blocklen == 6); + nghttp2_hd_end_headers(&deflater); + + CU_ASSERT(3 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf, blocklen)); + + assert_nv_equal(nva2, resnva, 3); + + nghttp2_nv_array_del(resnva); + nghttp2_hd_end_headers(&inflater); + + /* Cleanup */ + free(buf); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + void test_nghttp2_hd_inflate_indname_inc(void) { nghttp2_hd_context inflater; diff --git a/tests/nghttp2_hd_test.h b/tests/nghttp2_hd_test.h index 133ff1a7..a0a92b84 100644 --- a/tests/nghttp2_hd_test.h +++ b/tests/nghttp2_hd_test.h @@ -26,6 +26,7 @@ #define NGHTTP2_HD_TEST_H void test_nghttp2_hd_deflate(void); +void test_nghttp2_hd_deflate_same_indexed_repr(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);