From 45c2245bfb6a2031bc612f5a32e804baaa2566f2 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Fri, 19 Jul 2013 16:50:31 +0900 Subject: [PATCH] Implement header compression draft 01 --- lib/Makefile.am | 6 +- lib/includes/nghttp2/nghttp2.h | 29 + lib/nghttp2_frame.c | 12 + lib/nghttp2_frame.h | 11 + lib/nghttp2_hd.c | 1088 ++++++++++++++++++++++++++++++++ lib/nghttp2_hd.h | 236 +++++++ lib/nghttp2_helper.c | 10 + lib/nghttp2_helper.h | 12 + tests/Makefile.am | 5 +- tests/main.c | 16 + tests/nghttp2_hd_test.c | 285 +++++++++ tests/nghttp2_hd_test.h | 37 ++ 12 files changed, 1743 insertions(+), 4 deletions(-) create mode 100644 lib/nghttp2_hd.c create mode 100644 lib/nghttp2_hd.h create mode 100644 tests/nghttp2_hd_test.c create mode 100644 tests/nghttp2_hd_test.h diff --git a/lib/Makefile.am b/lib/Makefile.am index a0f0a0cd..5f459a22 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -36,14 +36,16 @@ OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \ nghttp2_stream.c nghttp2_outbound_item.c \ nghttp2_session.c nghttp2_submit.c \ nghttp2_helper.c \ - nghttp2_npn.c nghttp2_gzip.c + nghttp2_npn.c nghttp2_gzip.c \ + nghttp2_hd.c HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \ nghttp2_buffer.h nghttp2_frame.h nghttp2_zlib.h \ nghttp2_session.h nghttp2_helper.h nghttp2_stream.h nghttp2_int.h \ nghttp2_npn.h nghttp2_gzip.h \ nghttp2_submit.h nghttp2_outbound_item.h \ - nghttp2_net.h + nghttp2_net.h \ + nghttp2_hd.h libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS) libnghttp2_la_LDFLAGS = -no-undefined \ diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index c8e12c84..3f6d2680 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -155,6 +155,10 @@ typedef enum { * The length of the frame is too large. */ NGHTTP2_ERR_FRAME_TOO_LARGE = -522, + /** + * Header block inflate/deflate error. + */ + NGHTTP2_ERR_HEADER_COMP = -523, /** * The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is * under unexpected condition and cannot process any further data @@ -175,6 +179,31 @@ typedef enum { NGHTTP2_MSG_MORE } nghttp2_io_flag; +/** + * @struct + * + * The name/value pair, which mainly used to represent header fields. + */ +typedef struct { + /** + * The |name| byte string, which is not necessarily NULL terminated. + */ + uint8_t *name; + /** + * The |value| byte string, which is not necessarily NULL + * terminated. + */ + uint8_t *value; + /** + * The length of the |name|. + */ + uint16_t namelen; + /** + * The length of the |value|. + */ + uint16_t valuelen; +} nghttp2_nv; + /** * @enum * The control frame types in HTTP/2.0. diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 49b09204..7edf2be1 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -839,3 +839,15 @@ int nghttp2_frame_nv_check_null(const char **nv) } return 1; } + +int nghttp2_nv_equal(const nghttp2_nv *a, const nghttp2_nv *b) +{ + return a->namelen == b->namelen && a->valuelen == b->valuelen && + memcmp(a->name, b->name, a->namelen) == 0 && + memcmp(a->value, b->value, a->valuelen) == 0; +} + +void nghttp2_nv_array_free(nghttp2_nv *nva) +{ + free(nva); +} diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index c092215e..8283165c 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -40,6 +40,8 @@ #define NGHTTP2_PRI_DEFAULT (1 << 30) #define NGHTTP2_PRI_LOWEST ((1U << 31) - 1) +#define NGHTTP2_MAX_FRAME_SIZE ((1 << 16) - 1) + #define NGHTTP2_STREAM_ID_MASK 0x7fffffff #define NGHTTP2_PRIORITY_MASK 0x7fffffff #define NGHTTP2_WINDOW_SIZE_INCREMENT_MASK 0x7fffffff @@ -571,4 +573,13 @@ ssize_t nghttp2_frame_nv_offset(const uint8_t *head); */ int nghttp2_frame_nv_check_null(const char **nv); +/* + * Returns nonzero if the name/value pair |a| equals to |b|. The name + * is compared in case-sensitive, because we ensure that this function + * is called after the name is lower-cased. + */ +int nghttp2_nv_equal(const nghttp2_nv *a, const nghttp2_nv *b); + +void nghttp2_nv_array_free(nghttp2_nv *nva); + #endif /* NGHTTP2_FRAME_H */ diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c new file mode 100644 index 00000000..b7870ead --- /dev/null +++ b/lib/nghttp2_hd.c @@ -0,0 +1,1088 @@ +/* + * 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_hd.h" + +#include +#include +#include + +#include "nghttp2_frame.h" +#include "nghttp2_helper.h" + +static const char *reqhd_table[] = { + ":scheme", "http", + ":scheme", "https", + ":host", "", + ":path", "/", + ":method", "GET", + "accept", "", + "accept-charset", "", + "accept-encoding", "", + "accept-language", "", + "cookie", "", + "if-modified-since", "", + "keep-alive", "", + "user-agent", "", + "proxy-connection", "", + "referer", "", + "accept-datetime", "", + "authorization", "", + "allow", "", + "cache-control", "", + "connection", "", + "content-length", "", + "content-md5", "", + "content-type", "", + "date", "", + "expect", "", + "from", "", + "if-match", "", + "if-none-match", "", + "if-range", "", + "if-unmodified-since", "", + "max-forwards", "", + "pragma", "", + "proxy-authorization", "", + "range", "", + "te", "", + "upgrade", "", + "via", "", + "warning", "", + NULL +}; + +static const char *reshd_table[] = { + ":status", "200", + "age", "", + "cache-control", "", + "content-length", "", + "content-type", "", + "date", "", + "etag", "", + "expires", "", + "last-modified", "", + "server", "", + "set-cookie", "", + "vary", "", + "via", "", + "access-control-allow-origin", "", + "accept-ranges", "", + "allow", "", + "connection", "", + "content-disposition", "", + "content-encoding", "", + "content-language", "", + "content-location", "", + "content-md5", "", + "content-range", "", + "link", "", + "location", "", + "p3p", "", + "pragma", "", + "proxy-authenticate", "", + "refresh", "", + "retry-after", "", + "strict-transport-security", "", + "trailer", "", + "transfer-encoding", "", + "warning", "", + "www-authenticate", "", + NULL +}; + +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) +{ + int rv = 0; + if(flags & NGHTTP2_HD_FLAG_NAME_ALLOC) { + ent->nv.name = nghttp2_memdup(name, namelen); + if(ent->nv.name == NULL) { + rv = NGHTTP2_ERR_NOMEM; + goto fail; + } + } else { + ent->nv.name = name; + } + if(flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) { + ent->nv.value = nghttp2_memdup(value, valuelen); + if(ent->nv.value == NULL) { + rv = NGHTTP2_ERR_NOMEM; + goto fail2; + } + } else { + ent->nv.value = value; + } + ent->nv.namelen = namelen; + ent->nv.valuelen = valuelen; + ent->ref = 1; + ent->index = index; + ent->flags = flags; + return 0; + + fail2: + if(flags & NGHTTP2_HD_FLAG_NAME_ALLOC) { + free(ent->nv.name); + } + fail: + return rv; +} + +void nghttp2_hd_entry_free(nghttp2_hd_entry *ent) +{ + assert(ent->ref == 0); + if(ent->flags & NGHTTP2_HD_FLAG_NAME_ALLOC) { + free(ent->nv.name); + } + if(ent->flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) { + free(ent->nv.value); + } +} + +static int nghttp2_hd_context_init(nghttp2_hd_context *context, + nghttp2_hd_side side) +{ + int i; + const char **ini_table; + context->hd_table = malloc(sizeof(nghttp2_hd_entry*)* + NGHTTP2_INITIAL_HD_TABLE_SIZE); + memset(context->hd_table, 0, sizeof(nghttp2_hd_entry*)* + NGHTTP2_INITIAL_HD_TABLE_SIZE); + context->hd_table_capacity = NGHTTP2_INITIAL_HD_TABLE_SIZE; + context->hd_tablelen = 0; + + context->refset = malloc(sizeof(nghttp2_hd_entry*)* + NGHTTP2_INITIAL_REFSET_SIZE); + context->refset_capacity = NGHTTP2_INITIAL_REFSET_SIZE; + context->refsetlen = 0; + + context->ws = malloc(sizeof(nghttp2_hd_ws_entry)*NGHTTP2_INITIAL_WS_SIZE); + context->ws_capacity = NGHTTP2_INITIAL_WS_SIZE; + context->wslen = 0; + + if(side == NGHTTP2_HD_SIDE_CLIENT) { + ini_table = reqhd_table; + } else { + ini_table = reshd_table; + } + context->capacity = 0; + for(i = 0; ini_table[i]; i += 2) { + nghttp2_hd_entry *p = malloc(sizeof(nghttp2_hd_entry)); + nghttp2_hd_entry_init(p, i / 2, NGHTTP2_HD_FLAG_NONE, + (uint8_t*)ini_table[i], strlen(ini_table[i]), + (uint8_t*)ini_table[i + 1], + strlen(ini_table[i+1])); + context->hd_table[context->hd_tablelen++] = p; + context->capacity += NGHTTP2_HD_ENTRY_OVERHEAD + + p->nv.namelen + p->nv.valuelen; + } + return 0; +} + +int nghttp2_hd_deflate_init(nghttp2_hd_context *deflater, nghttp2_hd_side side) +{ + return nghttp2_hd_context_init(deflater, side); +} + +int nghttp2_hd_inflate_init(nghttp2_hd_context *inflater, nghttp2_hd_side side) +{ + return nghttp2_hd_context_init(inflater, side^1); +} + +static void nghttp2_hd_context_free(nghttp2_hd_context *context) +{ + size_t i; + for(i = 0; i < context->wslen; ++i) { + nghttp2_hd_ws_entry *ent = &context->ws[i]; + switch(ent->cat) { + case NGHTTP2_HD_CAT_INDEXED: + --ent->entry->ref; + if(ent->entry->ref == 0) { + nghttp2_hd_entry_free(ent->entry); + } + break; + case NGHTTP2_HD_CAT_LITERAL_IDXNAME: + --ent->entv.entry->ref; + if(ent->entv.entry->ref == 0) { + nghttp2_hd_entry_free(ent->entv.entry); + } + break; + default: + break; + } + } + for(i = 0; i < context->refsetlen; ++i) { + nghttp2_hd_entry *ent = context->refset[i]; + --ent->ref; + if(ent->ref == 0) { + nghttp2_hd_entry_free(ent); + } + } + for(i = 0; i < context->hd_tablelen; ++i) { + nghttp2_hd_entry *ent = context->hd_table[i]; + --ent->ref; + nghttp2_hd_entry_free(ent); + free(ent); + } + free(context->ws); + free(context->refset); + free(context->hd_table); +} + +void nghttp2_hd_deflate_free(nghttp2_hd_context *deflater) +{ + nghttp2_hd_context_free(deflater); +} + +void nghttp2_hd_inflate_free(nghttp2_hd_context *inflater) +{ + nghttp2_hd_context_free(inflater); +} + +static size_t entry_room(size_t namelen, size_t valuelen) +{ + return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + valuelen; +} + +static nghttp2_hd_entry* add_hd_table_incremental(nghttp2_hd_context *context, + nghttp2_nv *nv) +{ + int rv; + size_t i; + nghttp2_hd_entry *new_ent; + size_t room = entry_room(nv->namelen, nv->valuelen); + if(context->hd_tablelen == context->hd_table_capacity || + room > NGHTTP2_MAX_HD_TABLE_CAPACITY) { + return NULL; + } + context->capacity += room; + for(i = 0; i < context->hd_tablelen && + context->capacity > NGHTTP2_MAX_HD_TABLE_CAPACITY; ++i) { + nghttp2_hd_entry *ent = context->hd_table[i]; + --ent->ref; + context->capacity -= entry_room(ent->nv.namelen, ent->nv.valuelen); + if(ent->ref == 0) { + nghttp2_hd_entry_free(ent); + free(ent); + } + } + if(i > 0) { + size_t j; + for(j = 0; i < context->hd_tablelen; ++i, ++j) { + context->hd_table[j] = context->hd_table[i]; + context->hd_table[j]->index = j; + } + context->hd_tablelen = j; + } + new_ent = malloc(sizeof(nghttp2_hd_entry)); + if(new_ent == NULL) { + return NULL; + } + rv = nghttp2_hd_entry_init(new_ent, context->hd_tablelen, + NGHTTP2_HD_FLAG_NAME_ALLOC | + NGHTTP2_HD_FLAG_VALUE_ALLOC, + nv->name, nv->namelen, nv->value, nv->valuelen); + if(rv < 0) { + return NULL; + } + context->hd_table[context->hd_tablelen++] = new_ent; + return new_ent; +} + +static nghttp2_hd_entry* add_hd_table_subst(nghttp2_hd_context *context, + nghttp2_nv *nv, size_t subindex) +{ + int rv; + size_t i; + int k; + nghttp2_hd_entry *new_ent; + size_t room = entry_room(nv->namelen, nv->valuelen); + if(room > NGHTTP2_MAX_HD_TABLE_CAPACITY || + context->hd_tablelen <= subindex) { + return NULL; + } + context->capacity -= entry_room(context->hd_table[subindex]->nv.namelen, + context->hd_table[subindex]->nv.valuelen); + context->capacity += room; + k = subindex; + for(i = 0; i < context->hd_tablelen && + context->capacity > NGHTTP2_MAX_HD_TABLE_CAPACITY; ++i, --k) { + nghttp2_hd_entry *ent = context->hd_table[i]; + --ent->ref; + if(i != subindex) { + context->capacity -= entry_room(ent->nv.namelen, ent->nv.valuelen); + } + if(ent->ref == 0) { + nghttp2_hd_entry_free(ent); + free(ent); + } + } + if(i > 0) { + size_t j; + if(k < 0) { + j = 1; + } else { + j = 0; + } + for(; i < context->hd_tablelen; ++i, ++j) { + context->hd_table[j] = context->hd_table[i]; + context->hd_table[j]->index = j; + } + context->hd_tablelen = j; + } + new_ent = malloc(sizeof(nghttp2_hd_entry)); + if(new_ent == NULL) { + return NULL; + } + if(k >= 0) { + nghttp2_hd_entry *ent = context->hd_table[k]; + if(--ent->ref == 0) { + nghttp2_hd_entry_free(ent); + free(ent); + } + } else { + k = 0; + } + rv = nghttp2_hd_entry_init(new_ent, k, + NGHTTP2_HD_FLAG_NAME_ALLOC | + NGHTTP2_HD_FLAG_VALUE_ALLOC, + nv->name, nv->namelen, nv->value, nv->valuelen); + if(rv < 0) { + return NULL; + } + context->hd_table[new_ent->index] = new_ent; + return new_ent; +} + +static int add_workingset(nghttp2_hd_context *context, nghttp2_hd_entry *ent) +{ + nghttp2_hd_ws_entry *ws_ent; + if(context->wslen == context->ws_capacity) { + return NGHTTP2_ERR_HEADER_COMP; + } + ws_ent = &context->ws[context->wslen++]; + ws_ent->cat = NGHTTP2_HD_CAT_INDEXED; + ws_ent->entry = ent; + ws_ent->checked = 1; + ++ent->ref; + return 0; +} + +static int add_workingset_literal(nghttp2_hd_context *context, + nghttp2_nv *nv) +{ + nghttp2_hd_ws_entry *ws_ent; + if(context->wslen == context->ws_capacity) { + return NGHTTP2_ERR_HEADER_COMP; + } + ws_ent = &context->ws[context->wslen++]; + ws_ent->cat = NGHTTP2_HD_CAT_LITERAL; + ws_ent->nv = *nv; + return 0; +} + +static int add_workingset_literal_indname(nghttp2_hd_context *context, + nghttp2_hd_entry *ent, + uint8_t *value, size_t valuelen) +{ + nghttp2_hd_ws_entry *ws_ent; + if(context->wslen == context->ws_capacity) { + return NGHTTP2_ERR_HEADER_COMP; + } + ws_ent = &context->ws[context->wslen++]; + ws_ent->cat = NGHTTP2_HD_CAT_LITERAL_IDXNAME; + ws_ent->entv.entry = ent; + ++ent->ref; + ws_ent->entv.value = value; + ws_ent->entv.valuelen = valuelen; + return 0; +} + +static nghttp2_hd_ws_entry* find_in_workingset(nghttp2_hd_context *context, + nghttp2_nv *nv) +{ + size_t i; + for(i = 0; i < context->wslen; ++i) { + nghttp2_hd_ws_entry *ent = &context->ws[i]; + switch(ent->cat) { + case NGHTTP2_HD_CAT_INDEXED: + if(nghttp2_nv_equal(&ent->entry->nv, nv)) { + return ent; + } + break; + case NGHTTP2_HD_CAT_LITERAL_IDXNAME: + if(ent->entv.entry->nv.namelen == nv->namelen && + ent->entv.valuelen == nv->valuelen && + memcmp(ent->entv.entry->nv.name, nv->name, nv->namelen) == 0 && + memcmp(ent->entv.value, nv->value, nv->valuelen) == 0) { + return ent; + } + break; + case NGHTTP2_HD_CAT_LITERAL: + if(nghttp2_nv_equal(&ent->nv, nv)) { + return ent; + } + default: + break; + } + } + return NULL; +} + +static nghttp2_hd_ws_entry* find_in_workingset_by_index +(nghttp2_hd_context *context, size_t index) +{ + size_t i; + for(i = 0; i < context->wslen; ++i) { + nghttp2_hd_ws_entry *ent = &context->ws[i]; + /* Compare against *frozen* index, not the current header table + index. */ + if(ent->cat == NGHTTP2_HD_CAT_INDEXED && ent->index == index) { + return ent; + } + } + return NULL; +} + +static nghttp2_hd_entry* find_in_hd_table(nghttp2_hd_context *context, + nghttp2_nv *nv) +{ + size_t i; + for(i = 0; i < context->hd_tablelen; ++i) { + nghttp2_hd_entry *ent = context->hd_table[i]; + if(nghttp2_nv_equal(&ent->nv, nv)) { + return ent; + } + } + return NULL; +} + +static nghttp2_hd_entry* find_name_in_hd_table(nghttp2_hd_context *context, + nghttp2_nv *nv) +{ + size_t i; + for(i = 0; i < context->hd_tablelen; ++i) { + nghttp2_hd_entry *ent = context->hd_table[i]; + if(ent->nv.namelen == nv->namelen && + memcmp(ent->nv.name, nv->name, nv->namelen) == 0) { + return ent; + } + } + return NULL; +} + +static int ensure_write_buffer(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t offset, size_t need) +{ + int rv; + if(need + offset > NGHTTP2_MAX_FRAME_SIZE) { + return NGHTTP2_ERR_HEADER_COMP; + } + rv = nghttp2_reserve_buffer(buf_ptr, buflen_ptr, offset + need); + if(rv != 0) { + return NGHTTP2_ERR_NOMEM; + } + return 0; +} + +static size_t count_encoded_length(size_t n, int prefix) +{ + size_t k = (1 << prefix) - 1; + size_t len = 0; + if(n > k) { + n -= k; + ++len; + } else { + return 1; + } + while(n) { + ++len; + if(n >= 128) { + n >>= 7; + } else { + break; + } + } + return len; +} + +static size_t encode_length(uint8_t *buf, size_t n, int prefix) +{ + size_t k = (1 << prefix) - 1; + size_t len = 0; + if(n > k) { + *buf++ = k; + n -= k; + ++len; + } else { + *buf++ = n; + return 1; + } + while(n) { + ++len; + if(n >= 128) { + *buf++ = (1 << 7) | (n & 0x7f); + n >>= 7; + } else { + *buf++ = n; + break; + } + } + return len; +} + +/* + * Decodes |prefx| prefixed integer stored from |in|. The |last| + * represents the 1 beyond the last of the valid contiguous memory + * region from |in|. The decoded integer must be strictly less than 1 + * << 16. + * + * This function returns the next byte of read byte. This function + * stores the decoded integer in |*res| if it succeeds, or stores -1 + * in |*res|, indicating decoding error. + */ +static uint8_t* decode_length(ssize_t *res, uint8_t *in, uint8_t *last, + int prefix) +{ + int k = (1 << prefix) - 1, r; + if(in == last) { + *res = -1; + return in; + } + if(*in == k) { + *res = k; + } else { + *res = (*in) & k; + return in + 1; + } + ++in; + for(r = 0; in != last; ++in, r += 7) { + *res += (*in & 0x7f) << r; + if(*res >= (1 << 16)) { + *res = -1; + return in + 1; + } + if((*in & (1 << 7)) == 0) { + break; + } + } + if(*in & (1 << 7)) { + *res = -1; + return 0; + } else { + return in + 1; + } +} + +static int emit_index_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, nghttp2_hd_entry *ent) +{ + int rv; + uint8_t *bufp; + size_t blocklen = count_encoded_length(ent->index, 7); + rv = ensure_write_buffer(buf_ptr, buflen_ptr, *offset_ptr, blocklen); + if(rv != 0) { + return rv; + } + bufp = *buf_ptr + *offset_ptr; + encode_length(bufp, ent->index, 7); + (*buf_ptr)[*offset_ptr] |= 0x80u; + *offset_ptr += blocklen; + return 0; +} + +static int emit_literal_indname_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, nghttp2_hd_entry *ent, + const uint8_t *value, size_t valuelen, + int inc_indexing) +{ + int rv; + uint8_t *bufp; + size_t blocklen = count_encoded_length(ent->index, 5) + + count_encoded_length(valuelen, 8) + valuelen; + rv = ensure_write_buffer(buf_ptr, buflen_ptr, *offset_ptr, blocklen); + if(rv != 0) { + return rv; + } + bufp = *buf_ptr + *offset_ptr; + bufp += encode_length(bufp, ent->index + 1, 5); + bufp += encode_length(bufp, valuelen, 8); + memcpy(bufp, value, valuelen); + (*buf_ptr)[*offset_ptr] |= inc_indexing ? 0x40u : 0x60u; + *offset_ptr += blocklen; + return 0; +} + +static int emit_literal_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, nghttp2_nv *nv, + int inc_indexing) +{ + int rv; + uint8_t *bufp; + size_t blocklen = 1 + count_encoded_length(nv->namelen, 8) + nv->namelen + + count_encoded_length(nv->valuelen, 8) + nv->valuelen; + rv = ensure_write_buffer(buf_ptr, buflen_ptr, *offset_ptr, blocklen); + if(rv != 0) { + return rv; + } + bufp = *buf_ptr + *offset_ptr; + *bufp++ = inc_indexing ? 0x40u : 0x60u; + bufp += encode_length(bufp, nv->namelen, 8); + memcpy(bufp, nv->name, nv->namelen); + bufp += nv->namelen; + bufp += encode_length(bufp, nv->valuelen, 8); + memcpy(bufp, nv->value, nv->valuelen); + *offset_ptr += blocklen; + return 0; +} + +static int emit_subst_indname_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, nghttp2_hd_entry *ent, + const uint8_t *value, size_t valuelen, + size_t index) +{ + int rv; + uint8_t *bufp; + size_t blocklen = count_encoded_length(ent->index + 1, 5) + + count_encoded_length(index, 8) + + count_encoded_length(valuelen, 8) + valuelen; + rv = ensure_write_buffer(buf_ptr, buflen_ptr, *offset_ptr, blocklen); + if(rv != 0) { + return rv; + } + bufp = *buf_ptr + *offset_ptr; + bufp += encode_length(bufp, ent->index + 1, 5); + bufp += encode_length(bufp, index, 8); + bufp += encode_length(bufp, valuelen, 8); + memcpy(bufp, value, valuelen); + *offset_ptr += blocklen; + return 0; +} + +static int emit_subst_literal_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, nghttp2_nv *nv, + size_t index) +{ + int rv; + uint8_t *bufp; + size_t blocklen = 1 + count_encoded_length(nv->namelen, 8) + nv->namelen + + count_encoded_length(index, 8) + + count_encoded_length(nv->valuelen, 8) + nv->valuelen; + rv = ensure_write_buffer(buf_ptr, buflen_ptr, *offset_ptr, blocklen); + if(rv != 0) { + return rv; + } + bufp = *buf_ptr + *offset_ptr; + *bufp++ = 0; + bufp += encode_length(bufp, nv->namelen, 8); + memcpy(bufp, nv->name, nv->namelen); + bufp += nv->namelen; + bufp += encode_length(bufp, index, 8); + bufp += encode_length(bufp, nv->valuelen, 8); + memcpy(bufp, nv->value, nv->valuelen); + *offset_ptr += blocklen; + return 0; +} + +static void create_workingset(nghttp2_hd_context *context) +{ + int i; + for(i = 0; i < context->refsetlen; ++i) { + nghttp2_hd_ws_entry *ent = &context->ws[i]; + ent->cat = NGHTTP2_HD_CAT_INDEXED; + ent->entry = context->refset[i]; + ent->index = ent->entry->index; + ent->checked = 0; + context->refset[i] = NULL; + } + context->wslen = context->refsetlen; + context->refsetlen = 0; +} + +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; + int rv; + create_workingset(deflater); + offset = nv_offset; + for(i = 0; i < nvlen; ++i) { + nghttp2_hd_ws_entry *ws_ent; + ws_ent = find_in_workingset(deflater, &nv[i]); + if(ws_ent) { + ws_ent->checked = 1; + } else { + nghttp2_hd_entry *ent; + ent = find_in_hd_table(deflater, &nv[i]); + if(ent) { + /* If nv[i] is found in hd_table, use Indexed Header repr */ + rv = add_workingset(deflater, ent); + if(rv < 0) { + return rv; + } + rv = emit_index_block(buf_ptr, buflen_ptr, &offset, ent); + if(rv < 0) { + return rv; + } + } else { + /* Check name exists in hd_table */ + ent = find_name_in_hd_table(deflater, &nv[i]); + if(ent) { + rv = emit_literal_indname_block(buf_ptr, buflen_ptr, &offset, ent, + nv[i].value, nv[i].valuelen, 0); + if(rv < 0) { + return rv; + } + } else { + rv = emit_literal_block(buf_ptr, buflen_ptr, &offset, &nv[i], 0); + if(rv < 0) { + return rv; + } + } + } + } + } + for(i = 0; i < deflater->wslen; ++i) { + nghttp2_hd_ws_entry *ws_ent = &deflater->ws[i]; + if(!ws_ent->checked) { + assert(ws_ent->cat == NGHTTP2_HD_CAT_INDEXED); + rv = emit_index_block(buf_ptr, buflen_ptr, &offset, ws_ent->entry); + if(rv < 0) { + return rv; + } + } + } + return offset - nv_offset; +} + +static ssize_t build_nv_array(nghttp2_hd_context *inflater, + nghttp2_nv **nva_ptr) +{ + int nvlen = 0, i; + nghttp2_nv *nv; + for(i = 0; i < inflater->wslen; ++i) { + nghttp2_hd_ws_entry *ent = &inflater->ws[i]; + if(ent->cat != NGHTTP2_HD_CAT_NONE) { + ++nvlen; + } + } + *nva_ptr = malloc(sizeof(nghttp2_nv)*nvlen); + if(*nva_ptr == NULL) { + return NGHTTP2_ERR_NOMEM; + } + nv = *nva_ptr; + for(i = 0; i < inflater->wslen; ++i) { + nghttp2_hd_ws_entry *ent = &inflater->ws[i]; + switch(ent->cat) { + case NGHTTP2_HD_CAT_INDEXED: + *nv = ent->entry->nv; + ent->checked = 1; + ++nv; + break; + case NGHTTP2_HD_CAT_LITERAL_IDXNAME: + nv->name = ent->entv.entry->nv.name; + nv->namelen = ent->entv.entry->nv.namelen; + nv->value = ent->entv.value; + nv->valuelen = ent->entv.valuelen; + ++nv; + break; + case NGHTTP2_HD_CAT_LITERAL: + *nv = ent->nv; + ++nv; + break; + default: + break; + } + } + return nvlen; +} + +ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, + nghttp2_nv **nva_ptr, + uint8_t *in, size_t inlen) +{ + int rv; + uint8_t *last = in + inlen; + create_workingset(inflater); + for(; in != last;) { + uint8_t c = *in; + if(c & 0x80u) { + /* Indexed Header Repr */ + nghttp2_hd_ws_entry *ws_ent; + ssize_t index; + in = decode_length(&index, in, last, 7); + if(index < 0) { + return NGHTTP2_ERR_HEADER_COMP; + } + ws_ent = find_in_workingset_by_index(inflater, index); + if(ws_ent) { + assert(ws_ent->cat == NGHTTP2_HD_CAT_INDEXED); + --ws_ent->entry->ref; + if(ws_ent->entry->ref == 0) { + nghttp2_hd_entry_free(ws_ent->entry); + free(ws_ent->entry); + } + ws_ent->cat = NGHTTP2_HD_CAT_NONE; + } else { + nghttp2_hd_entry *ent; + if(inflater->hd_tablelen <= index) { + return NGHTTP2_ERR_HEADER_COMP; + } + ent = inflater->hd_table[index]; + rv = add_workingset(inflater, ent); + if(rv < 0) { + return rv; + } + } + } else if(c == 0x60u || c == 0x40u) { + /* Literal Header without Indexing - new name or Literal Header + with incremental indexing - new name */ + nghttp2_nv nv; + ssize_t namelen, valuelen; + if(++in == last) { + return NGHTTP2_ERR_HEADER_COMP; + } + in = decode_length(&namelen, in, last, 8); + if(namelen < 0 || in + namelen > last) { + return NGHTTP2_ERR_HEADER_COMP; + } + nv.name = in; + in += namelen; + in = decode_length(&valuelen, in, last, 8); + if(valuelen < 0 || in + valuelen > last) { + return NGHTTP2_ERR_HEADER_COMP; + } + nv.namelen = namelen; + nv.value = in; + nv.valuelen = valuelen; + in += valuelen; + if(c == 0x60u) { + rv = add_workingset_literal(inflater, &nv); + } else { + nghttp2_hd_entry *ent = add_hd_table_incremental(inflater, &nv); + if(ent) { + rv = add_workingset(inflater, ent); + } else { + return NGHTTP2_ERR_HEADER_COMP; + } + } + if(rv < 0) { + return rv; + } + } else if((c & 0x60u) == 0x60u || (c & 0x40) == 0x40u) { + /* Literal Header without Indexing - indexed name or Literal + Header with incremental indexing - indexed name */ + nghttp2_hd_entry *ent; + uint8_t *value; + ssize_t valuelen; + ssize_t index; + in = decode_length(&index, in, last, 5); + if(index < 0) { + return NGHTTP2_ERR_HEADER_COMP; + } + --index; + if(inflater->hd_tablelen <= index) { + return NGHTTP2_ERR_HEADER_COMP; + } + ent = inflater->hd_table[index]; + in = decode_length(&valuelen, in , last, 8); + if(valuelen < 0 || in + valuelen > last) { + return NGHTTP2_ERR_HEADER_COMP; + } + value = in; + in += valuelen; + if((c & 0x60u) == 0x60u) { + rv = add_workingset_literal_indname(inflater, ent, value, valuelen); + } else { + nghttp2_nv nv; + nghttp2_hd_entry *new_ent; + ++ent->ref; + nv.name = ent->nv.name; + nv.namelen = ent->nv.namelen; + nv.value = value; + nv.valuelen = valuelen; + new_ent = add_hd_table_incremental(inflater, &nv); + if(--ent->ref == 0) { + nghttp2_hd_entry_free(ent); + free(ent); + } + if(new_ent) { + rv = add_workingset(inflater, new_ent); + } else { + return NGHTTP2_ERR_HEADER_COMP; + } + } + if(rv < 0) { + return rv; + } + } else if(c == 0) { + /* Literal Header with substitution indexing - new name */ + nghttp2_hd_entry *new_ent; + nghttp2_nv nv; + ssize_t namelen, valuelen, subindex; + if(++in == last) { + return NGHTTP2_ERR_HEADER_COMP; + } + in = decode_length(&namelen, in, last, 8); + if(namelen < 0 || in + namelen > last) { + return NGHTTP2_ERR_HEADER_COMP; + } + nv.name = in; + in += namelen; + in = decode_length(&subindex, in, last, 8); + if(subindex < 0) { + return NGHTTP2_ERR_HEADER_COMP; + } + in = decode_length(&valuelen, in, last, 8); + if(valuelen < 0 || in + valuelen > last) { + return NGHTTP2_ERR_HEADER_COMP; + } + nv.value = in; + nv.namelen = namelen; + nv.valuelen = valuelen; + in += valuelen; + new_ent = add_hd_table_subst(inflater, &nv, subindex); + if(new_ent) { + rv = add_workingset(inflater, new_ent); + if(rv < 0) { + return rv; + } + } else { + return NGHTTP2_ERR_HEADER_COMP; + } + } else { + /* Literal Header with substitution indexing - indexed name */ + nghttp2_hd_entry *ent, *new_ent; + ssize_t valuelen; + ssize_t index, subindex; + nghttp2_nv nv; + in = decode_length(&index, in, last, 6); + if(index < 0) { + return NGHTTP2_ERR_HEADER_COMP; + } + --index; + if(inflater->hd_tablelen <= index) { + return NGHTTP2_ERR_HEADER_COMP; + } + ent = inflater->hd_table[index]; + in = decode_length(&subindex, in, last, 8); + if(subindex < 0) { + return NGHTTP2_ERR_HEADER_COMP; + } + in = decode_length(&valuelen, in, last, 8); + if(valuelen < 0 || in + valuelen > last) { + return NGHTTP2_ERR_HEADER_COMP; + } + ++ent->ref; + nv.name = ent->nv.name; + nv.namelen = ent->nv.namelen; + nv.value = in; + nv.valuelen = valuelen; + in += valuelen; + new_ent = add_hd_table_subst(inflater, &nv, subindex); + if(--ent->ref == 0) { + nghttp2_hd_entry_free(ent); + free(ent); + } + if(new_ent) { + rv = add_workingset(inflater, new_ent); + if(rv < 0) { + return rv; + } + } else { + return NGHTTP2_ERR_HEADER_COMP; + } + } + } + return build_nv_array(inflater, nva_ptr); +} + +int nghttp2_hd_end_headers(nghttp2_hd_context *context) +{ + int i; + assert(context->refsetlen == 0); + for(i = 0; i < context->wslen; ++i) { + nghttp2_hd_ws_entry *ws_ent = &context->ws[i]; + switch(ws_ent->cat) { + case NGHTTP2_HD_CAT_INDEXED: + if(ws_ent->checked == 0 || ws_ent->entry->ref == 1) { + --ws_ent->entry->ref; + if(ws_ent->entry->ref == 0) { + nghttp2_hd_entry_free(ws_ent->entry); + free(ws_ent->entry); + } + } else { + context->refset[context->refsetlen++] = ws_ent->entry; + } + break; + case NGHTTP2_HD_CAT_LITERAL_IDXNAME: + --ws_ent->entv.entry->ref; + if(ws_ent->entv.entry->ref == 0) { + nghttp2_hd_entry_free(ws_ent->entv.entry); + free(ws_ent->entv.entry); + } + break; + default: + break; + } + } + context->wslen = 0; + return 0; +} + +int nghttp2_hd_emit_indname_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, nghttp2_hd_entry *ent, + const uint8_t *value, size_t valuelen, + int inc_indexing) +{ + return emit_literal_indname_block(buf_ptr, buflen_ptr, offset_ptr, + ent, value, valuelen, inc_indexing); +} + +int nghttp2_hd_emit_literal_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, nghttp2_nv *nv, + int inc_indexing) +{ + return emit_literal_block(buf_ptr, buflen_ptr, offset_ptr, nv, inc_indexing); +} + +int nghttp2_hd_emit_subst_indname_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, + nghttp2_hd_entry *ent, + const uint8_t *value, size_t valuelen, + size_t index) +{ + return emit_subst_indname_block(buf_ptr, buflen_ptr, offset_ptr, + ent, value, valuelen, index); +} + +int nghttp2_hd_emit_subst_literal_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, nghttp2_nv *nv, + size_t index) +{ + return emit_subst_literal_block(buf_ptr, buflen_ptr, offset_ptr, nv, index); +} diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h new file mode 100644 index 00000000..eed147c0 --- /dev/null +++ b/lib/nghttp2_hd.h @@ -0,0 +1,236 @@ +/* + * 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_HD_COMP_H +#define NGHTTP2_HD_COMP_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#define NGHTTP2_INITIAL_HD_TABLE_SIZE 128 +#define NGHTTP2_INITIAL_REFSET_SIZE 128 +#define NGHTTP2_INITIAL_WS_SIZE 128 + +#define NGHTTP2_MAX_HD_TABLE_CAPACITY 4096 +#define NGHTTP2_HD_ENTRY_OVERHEAD 32 + +typedef enum { + NGHTTP2_HD_SIDE_CLIENT = 0, + NGHTTP2_HD_SIDE_SERVER = 1 +} nghttp2_hd_side; + +typedef enum { + NGHTTP2_HD_FLAG_NONE = 0, + /* Indicates name was dynamically allocated and must be freed */ + NGHTTP2_HD_FLAG_NAME_ALLOC = 1, + /* Indicates value was dynamically allocated and must be freed */ + NGHTTP2_HD_FLAG_VALUE_ALLOC = 1 << 1, +} nghttp2_hd_flags; + +typedef struct { + nghttp2_nv nv; + /* Reference count in workingset */ + uint8_t ref; + uint8_t index; + uint8_t flags; +} nghttp2_hd_entry; + +typedef enum { + NGHTTP2_HD_CAT_NONE, + NGHTTP2_HD_CAT_INDEXED, + NGHTTP2_HD_CAT_LITERAL_IDXNAME, + NGHTTP2_HD_CAT_LITERAL +} nghttp2_hd_entry_cat; + +typedef struct nghttp2_hd_ws_entry { + nghttp2_hd_entry_cat cat; + union { + /* For NGHTTP2_HD_CAT_INDEXED */ + nghttp2_hd_entry *entry; + /* For NGHTTP2_HD_CAT_LITERAL */ + nghttp2_nv nv; + /* For NGHTTP2_HD_CAT_LITERAL_IDXNAME */ + struct { + /* The entry in header table the name stored */ + nghttp2_hd_entry *entry; + uint8_t *value; + uint16_t valuelen; + } entv; + }; + /* TODO Only usable with NGHTTP2_HD_CAT_INDEXED */ + uint8_t index; + uint8_t checked; +} nghttp2_hd_ws_entry; + +typedef struct { + /* Header table */ + nghttp2_hd_entry **hd_table; + /* Reference set */ + nghttp2_hd_entry **refset; + /* Working set */ + nghttp2_hd_ws_entry *ws; + /* The capacity of the |hd_table| */ + uint16_t hd_table_capacity; + /* the number of entry the |hd_table| contains */ + uint16_t hd_tablelen; + /* The capacity of the |refset| */ + uint16_t refset_capacity; + /* The number of entry the |refset| contains */ + uint16_t refsetlen; + /* The capacity of the |ws| */ + uint16_t ws_capacity; + /* The number of entry the |ws| contains */ + uint16_t wslen; + /* Abstract capacity of hd_table as described in the spec. This is + the sum of length of name/value in hd_table + + NGHTTP2_HD_ENTRY_OVERHEAD bytes overhead per each entry. */ + uint16_t capacity; +} nghttp2_hd_context; + +/* + * Initializes the |ent| members. If NGHTTP2_HD_FLAG_NAME_ALLOC bit + * set in the |flags|, the content pointed by the |name| with length + * |namelen| is copied. Likewise, if NGHTTP2_HD_FLAG_VALUE_ALLOC bit + * set in the |flags|, the content pointed by the |value| with length + * |valuelen| is copied. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +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); + +void nghttp2_hd_entry_free(nghttp2_hd_entry *ent); + +/* + * Initializes |deflater| for deflating name/values pairs. + * + * 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_init(nghttp2_hd_context *deflater, + nghttp2_hd_side side); + +/* + * Initializes |inflater| for inflating name/values pairs. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_hd_inflate_init(nghttp2_hd_context *inflater, + nghttp2_hd_side side); + +/* + * Deallocates any resources allocated for |deflater|. + */ +void nghttp2_hd_deflate_free(nghttp2_hd_context *deflater); + +/* + * Deallocates any resources allocated for |inflater|. + */ +void nghttp2_hd_inflate_free(nghttp2_hd_context *inflater); + +/* + * Deflates the |nv|, which has the |nvlen| name/value pairs, into the + * buffer pointed by the |*buf_ptr| with the length |*buflen_ptr|. + * + * This function expands |*buf_ptr| as necessary to store the + * result. When expansion occurred, memory previously pointed by + * |*buf_ptr| is freed. |*buf_ptr| and |*buflen_ptr| are updated + * accordingly. + * + * This function returns the number of bytes outputted if it succeeds, + * or one of the following negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +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); + +/* + * Inflates name/value block stored in |in| with length |inlen|. This + * function performs decompression. The |*nv_ptr| points to the final + * result on succesful decompression. The caller must free |*nv_ptr| + * using nghttp2_nv_free(). + * + * This function returns the number of bytes outputted if it succeeds, + * or one of the following negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, + nghttp2_nv **nva_ptr, + uint8_t *in, size_t inlen); + + +/* + * Signals the end of processing one header block. This function + * creates new reference set from working set. + * + * This function returns 0 if it succeeds. Currently this function + * always succeeds. + */ +int nghttp2_hd_end_headers(nghttp2_hd_context *deflater_or_inflater); + +/* For unittesting purpose */ +int nghttp2_hd_emit_indname_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, nghttp2_hd_entry *ent, + const uint8_t *value, size_t valuelen, + int inc_indexing); + +/* For unittesting purpose */ +int nghttp2_hd_emit_literal_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, nghttp2_nv *nv, + int inc_indexing); + +/* For unittesting purpose */ +int nghttp2_hd_emit_subst_indname_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, + nghttp2_hd_entry *ent, + const uint8_t *value, size_t valuelen, + size_t index); + +/* For unittesting purpose */ +int nghttp2_hd_emit_subst_literal_block(uint8_t **buf_ptr, size_t *buflen_ptr, + size_t *offset_ptr, nghttp2_nv *nv, + size_t index); + +#endif /* NGHTTP2_HD_COMP_H */ diff --git a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c index 10be1646..f2d65b50 100644 --- a/lib/nghttp2_helper.c +++ b/lib/nghttp2_helper.c @@ -71,6 +71,16 @@ int nghttp2_reserve_buffer(uint8_t **buf_ptr, size_t *buflen_ptr, return 0; } +void* nghttp2_memdup(const void* src, size_t n) +{ + void* dest = malloc(n); + if(dest == NULL) { + return NULL; + } + memcpy(dest, src, n); + return dest; +} + const char* nghttp2_strerror(int error_code) { switch(error_code) { diff --git a/lib/nghttp2_helper.h b/lib/nghttp2_helper.h index cb3f3691..5874e895 100644 --- a/lib/nghttp2_helper.h +++ b/lib/nghttp2_helper.h @@ -77,4 +77,16 @@ uint32_t nghttp2_get_uint32(const uint8_t *data); int nghttp2_reserve_buffer(uint8_t **buf_ptr, size_t *buflen_ptr, size_t min_length); +/* + * Allocates |n| bytes of memory and copy the meory region pointed by + * |src| with the length |n| bytes into it. Returns the allocated memory. + * + * This function returns pointer to allocated memory, or one of the + * following negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +void* nghttp2_memdup(const void* src, size_t n); + #endif /* NGHTTP2_HELPER_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 46ce6734..1cd12a69 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -33,13 +33,14 @@ OBJECTS = main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c \ nghttp2_frame_test.c \ nghttp2_stream_test.c \ nghttp2_session_test.c \ + nghttp2_hd_test.c \ nghttp2_npn_test.c \ nghttp2_gzip_test.c HFILES = nghttp2_pq_test.h nghttp2_map_test.h nghttp2_queue_test.h \ nghttp2_buffer_test.h nghttp2_zlib_test.h nghttp2_session_test.h \ - nghttp2_frame_test.h nghttp2_stream_test.h nghttp2_npn_test.h \ - nghttp2_gzip_test.h nghttp2_test_helper.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 main_SOURCES = $(HFILES) $(OBJECTS) diff --git a/tests/main.c b/tests/main.c index 0a1b2be9..fa9a6966 100644 --- a/tests/main.c +++ b/tests/main.c @@ -34,6 +34,7 @@ #include "nghttp2_session_test.h" #include "nghttp2_frame_test.h" #include "nghttp2_stream_test.h" +#include "nghttp2_hd_test.h" #include "nghttp2_npn_test.h" #include "nghttp2_gzip_test.h" @@ -199,6 +200,21 @@ int main(int argc, char* argv[]) /* !CU_add_test(pSuite, "stream_add_pushed_stream", */ /* test_nghttp2_stream_add_pushed_stream) || */ + !CU_add_test(pSuite, "hd_deflate", test_nghttp2_hd_deflate) || + !CU_add_test(pSuite, "hd_inflate_indname_inc", + test_nghttp2_hd_inflate_indname_inc) || + !CU_add_test(pSuite, "hd_inflate_indname_inc_eviction", + test_nghttp2_hd_inflate_indname_inc_eviction) || + !CU_add_test(pSuite, "hd_inflate_newname_inc", + test_nghttp2_hd_inflate_newname_inc) || + !CU_add_test(pSuite, "hd_inflate_indname_subst", + test_nghttp2_hd_inflate_newname_inc) || + !CU_add_test(pSuite, "hd_inflate_indname_subst_eviction", + test_nghttp2_hd_inflate_indname_subst_eviction) || + !CU_add_test(pSuite, "hd_inflate_indname_subst_eviction_neg", + test_nghttp2_hd_inflate_indname_subst_eviction_neg) || + !CU_add_test(pSuite, "hd_inflate_newname_subst", + test_nghttp2_hd_inflate_newname_subst) || !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ) { CU_cleanup_registry(); diff --git a/tests/nghttp2_hd_test.c b/tests/nghttp2_hd_test.c new file mode 100644 index 00000000..0d33aff1 --- /dev/null +++ b/tests/nghttp2_hd_test.c @@ -0,0 +1,285 @@ +/* + * 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_hd_test.h" + +#include +#include + +#include + +#include "nghttp2_hd.h" +#include "nghttp2_frame.h" + +#define MAKE_NV(NAME, VALUE) \ + { (uint8_t*)NAME, (uint8_t*)VALUE, strlen(NAME), strlen(VALUE) } + +static void assert_nv_equal(nghttp2_nv *a, nghttp2_nv *b, size_t len) +{ + size_t i; + for(i = 0; i < len; ++i, ++a, ++b) { + CU_ASSERT(nghttp2_nv_equal(a, b)); + } +} + +void test_nghttp2_hd_deflate(void) +{ + nghttp2_hd_context deflater, inflater; + nghttp2_nv nva1[] = {MAKE_NV(":path", "/my-example/index.html"), + MAKE_NV(":scheme", "https"), + MAKE_NV("hello", "world")}; + nghttp2_nv nva2[] = {MAKE_NV(":path", "/script.js"), + MAKE_NV(":scheme", "https")}; + nghttp2_nv nvtemp; + size_t nv_offset = 12; + 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)); + + blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, nv_offset, nva1, + sizeof(nva1)/sizeof(nghttp2_nv)); + CU_ASSERT((1+1+22)+(1)+(1+1+5+1+5) == blocklen); + nghttp2_hd_end_headers(&deflater); + + CU_ASSERT(3 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf + nv_offset, + blocklen)); + + assert_nv_equal(nva1, resnva, 3); + + nghttp2_nv_array_free(resnva); + nghttp2_hd_end_headers(&inflater); + + CU_ASSERT(1 == inflater.refsetlen); + + /* Second headers */ + blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, nv_offset, nva2, + sizeof(nva2)/sizeof(nghttp2_nv)); + CU_ASSERT((1+1+10) == blocklen); + nghttp2_hd_end_headers(&deflater); + + CU_ASSERT(2 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf + nv_offset, + blocklen)); + + /* First and second header fields are interchanged their positions. */ + nvtemp = nva2[0]; + nva2[0] = nva2[1]; + nva2[1] = nvtemp; + assert_nv_equal(nva2, resnva, 2); + + nghttp2_nv_array_free(resnva); + nghttp2_hd_end_headers(&inflater); + + free(buf); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_hd_inflate_indname_inc(void) +{ + nghttp2_hd_context inflater; + uint8_t *buf = NULL; + size_t buflen = 0; + size_t offset = 0; + nghttp2_nv nv = MAKE_NV("user-agent", "nghttp2"); + nghttp2_nv *resnva; + nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_SERVER); + + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&buf, &buflen, &offset, + inflater.hd_table[12], + nv.value, nv.valuelen, + 1)); + CU_ASSERT(1 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf, offset)); + assert_nv_equal(&nv, resnva, 1); + CU_ASSERT(39 == inflater.hd_tablelen); + assert_nv_equal(&nv, &inflater.hd_table[inflater.hd_tablelen-1]->nv, 1); + + nghttp2_nv_array_free(resnva); + free(buf); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_indname_inc_eviction(void) +{ + nghttp2_hd_context inflater; + uint8_t *buf = NULL; + size_t buflen = 0; + size_t offset = 0; + /* Default header table capacity is 1592. Adding 2547 bytes, + including overhead, to the table evicts first entry. + use name ":host" which index 2 and value length 2510. */ + uint8_t value[2510]; + nghttp2_nv *resnva; + nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_SERVER); + + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&buf, &buflen, &offset, + inflater.hd_table[2], + value, sizeof(value), 1)); + CU_ASSERT(1 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf, offset)); + CU_ASSERT(5 == resnva[0].namelen); + CU_ASSERT(0 == memcmp(":host", resnva[0].name, resnva[0].namelen)); + CU_ASSERT(sizeof(value) == resnva[0].valuelen); + + nghttp2_nv_array_free(resnva); + nghttp2_hd_end_headers(&inflater); + + CU_ASSERT(38 == inflater.hd_tablelen); + CU_ASSERT(37 == inflater.refset[0]->index); + + free(buf); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_newname_inc(void) +{ + nghttp2_hd_context inflater; + uint8_t *buf = NULL; + size_t buflen = 0; + size_t offset = 0; + nghttp2_nv nv = MAKE_NV("x-rel", "nghttp2"); + nghttp2_nv *resnva; + nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_SERVER); + + CU_ASSERT(0 == nghttp2_hd_emit_literal_block(&buf, &buflen, &offset, + &nv, 1)); + CU_ASSERT(1 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf, offset)); + assert_nv_equal(&nv, resnva, 1); + CU_ASSERT(39 == inflater.hd_tablelen); + assert_nv_equal(&nv, &inflater.hd_table[inflater.hd_tablelen-1]->nv, 1); + + nghttp2_nv_array_free(resnva); + free(buf); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_indname_subst(void) +{ + nghttp2_hd_context inflater; + uint8_t *buf = NULL; + size_t buflen = 0; + size_t offset = 0; + nghttp2_nv nv = MAKE_NV("user-agent", "nghttp2"); + nghttp2_nv *resnva; + nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_SERVER); + + CU_ASSERT(0 == nghttp2_hd_emit_subst_indname_block(&buf, &buflen, &offset, + inflater.hd_table[12], + nv.value, nv.valuelen, + 12)); + CU_ASSERT(1 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf, offset)); + assert_nv_equal(&nv, resnva, 1); + CU_ASSERT(38 == inflater.hd_tablelen); + assert_nv_equal(&nv, &inflater.hd_table[12]->nv, 1); + + nghttp2_nv_array_free(resnva); + free(buf); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_indname_subst_eviction(void) +{ + nghttp2_hd_context inflater; + uint8_t *buf = NULL; + size_t buflen = 0; + size_t offset = 0; + /* Default header table capacity is 1592. Adding 2547 bytes, + including overhead, to the table evicts first entry. + use name ":host" which index 2 and value length 2510. */ + uint8_t value[2510]; + nghttp2_nv *resnva; + nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_SERVER); + + CU_ASSERT(0 == nghttp2_hd_emit_subst_indname_block(&buf, &buflen, &offset, + inflater.hd_table[2], + value, sizeof(value), 2)); + CU_ASSERT(1 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf, offset)); + CU_ASSERT(5 == resnva[0].namelen); + CU_ASSERT(0 == memcmp(":host", resnva[0].name, resnva[0].namelen)); + CU_ASSERT(sizeof(value) == resnva[0].valuelen); + + nghttp2_nv_array_free(resnva); + nghttp2_hd_end_headers(&inflater); + + CU_ASSERT(37 == inflater.hd_tablelen); + CU_ASSERT(1 == inflater.refset[0]->index); + + free(buf); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_indname_subst_eviction_neg(void) +{ + nghttp2_hd_context inflater; + uint8_t *buf = NULL; + size_t buflen = 0; + size_t offset = 0; + /* Default header table capacity is 1592. Adding 2548 bytes, + including overhead, to the table evicts first entry. + use name ":host" which index 2 and value length 2511. */ + uint8_t value[2511]; + nghttp2_nv *resnva; + nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_SERVER); + /* Try to substitute index 0, but it will be evicted */ + CU_ASSERT(0 == nghttp2_hd_emit_subst_indname_block(&buf, &buflen, &offset, + inflater.hd_table[2], + value, sizeof(value), 0)); + CU_ASSERT(1 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf, offset)); + CU_ASSERT(5 == resnva[0].namelen); + CU_ASSERT(0 == memcmp(":host", resnva[0].name, resnva[0].namelen)); + CU_ASSERT(sizeof(value) == resnva[0].valuelen); + + nghttp2_nv_array_free(resnva); + nghttp2_hd_end_headers(&inflater); + + CU_ASSERT(37 == inflater.hd_tablelen); + CU_ASSERT(0 == inflater.refset[0]->index); + + free(buf); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_newname_subst(void) +{ + nghttp2_hd_context inflater; + uint8_t *buf = NULL; + size_t buflen = 0; + size_t offset = 0; + nghttp2_nv nv = MAKE_NV("x-rel", "nghttp2"); + nghttp2_nv *resnva; + nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_SERVER); + + CU_ASSERT(0 == nghttp2_hd_emit_subst_literal_block(&buf, &buflen, &offset, + &nv, 1)); + CU_ASSERT(1 == nghttp2_hd_inflate_hd(&inflater, &resnva, buf, offset)); + assert_nv_equal(&nv, resnva, 1); + CU_ASSERT(38 == inflater.hd_tablelen); + assert_nv_equal(&nv, &inflater.hd_table[1]->nv, 1); + + nghttp2_nv_array_free(resnva); + free(buf); + nghttp2_hd_inflate_free(&inflater); +} diff --git a/tests/nghttp2_hd_test.h b/tests/nghttp2_hd_test.h new file mode 100644 index 00000000..31b486a2 --- /dev/null +++ b/tests/nghttp2_hd_test.h @@ -0,0 +1,37 @@ +/* + * 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_HD_TEST_H +#define NGHTTP2_HD_TEST_H + +void test_nghttp2_hd_deflate(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_indname_subst(void); +void test_nghttp2_hd_inflate_indname_subst_eviction(void); +void test_nghttp2_hd_inflate_indname_subst_eviction_neg(void); +void test_nghttp2_hd_inflate_newname_subst(void); + +#endif /* NGHTTP2_HD_TEST_H */