From 8317559090a994eec534d7da823a2b75ed2ad69f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 25 Jan 2014 18:24:15 +0900 Subject: [PATCH] nghttp2_hd: Implement stream header inflater This stream inflater can inflate incoming header block in streaming fashion. Currently, we buffer up single name/value pair, but we chose far more smaller buffer size than HTTP/2 frame size. --- lib/nghttp2_buffer.c | 225 ++-------- lib/nghttp2_buffer.h | 145 ++----- lib/nghttp2_hd.c | 821 ++++++++++++++++++++++++------------ lib/nghttp2_hd.h | 114 +++-- lib/nghttp2_hd_huffman.c | 66 ++- lib/nghttp2_hd_huffman.h | 14 + lib/nghttp2_int.h | 3 +- lib/nghttp2_session.c | 15 +- src/inflatehd.c | 13 +- tests/main.c | 1 - tests/nghttp2_buffer_test.c | 92 +--- tests/nghttp2_hd_test.c | 21 +- tests/nghttp2_test_helper.c | 11 +- 13 files changed, 785 insertions(+), 756 deletions(-) diff --git a/lib/nghttp2_buffer.c b/lib/nghttp2_buffer.c index 4b1fcf13..291acfb4 100644 --- a/lib/nghttp2_buffer.c +++ b/lib/nghttp2_buffer.c @@ -1,7 +1,7 @@ /* * nghttp2 - HTTP/2.0 C Library * - * Copyright (c) 2012 Tatsuhiro Tsujikawa + * Copyright (c) 2014 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -27,217 +27,68 @@ #include #include -#include "nghttp2_net.h" #include "nghttp2_helper.h" -void nghttp2_buffer_init(nghttp2_buffer *buffer, size_t chunk_capacity) +void nghttp2_buffer_init(nghttp2_buffer *buffer, size_t max_capacity) { - buffer->root.data = NULL; - buffer->root.next = NULL; - buffer->current = &buffer->root; - buffer->capacity = chunk_capacity; + buffer->buf = NULL; buffer->len = 0; - /* - * Set last_offset to maximum so that first append adds new buffer - * buffer. - */ - buffer->last_offset = buffer->capacity; + buffer->capacity = 0; + buffer->max_capacity = max_capacity; } void nghttp2_buffer_free(nghttp2_buffer *buffer) { - nghttp2_buffer_chunk *p = buffer->root.next; - while(p) { - nghttp2_buffer_chunk *next = p->next; - free(p->data); - free(p); - p = next; - } + free(buffer->buf); } -int nghttp2_buffer_alloc(nghttp2_buffer *buffer) +int nghttp2_buffer_reserve(nghttp2_buffer *buffer, size_t len) { - if(buffer->current->next == NULL) { - nghttp2_buffer_chunk *chunk; - uint8_t *buf; - chunk = malloc(sizeof(nghttp2_buffer_chunk)); - if(chunk == NULL) { - return NGHTTP2_ERR_NOMEM; - } - buf = malloc(buffer->capacity); - if(buf == NULL) { - free(chunk); - return NGHTTP2_ERR_NOMEM; - } - chunk->data = buf; - chunk->next = NULL; - buffer->current->next = chunk; - buffer->current = chunk; - } else { - buffer->current = buffer->current->next; + if(len > buffer->max_capacity) { + return NGHTTP2_ERR_BUFFER_ERROR; + } + if(buffer->capacity < len) { + uint8_t *new_buf; + size_t new_cap = buffer->capacity == 0 ? 32 : buffer->capacity * 3 / 2; + new_cap = nghttp2_min(buffer->max_capacity, nghttp2_max(new_cap, len)); + new_buf = realloc(buffer->buf, new_cap); + if(new_buf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + buffer->buf = new_buf; + buffer->capacity = new_cap; } - buffer->len += buffer->capacity-buffer->last_offset; - buffer->last_offset = 0; return 0; } -uint8_t* nghttp2_buffer_get(nghttp2_buffer *buffer) -{ - if(buffer->current->data == NULL) { - return NULL; - } else { - return buffer->current->data+buffer->last_offset; - } -} - -size_t nghttp2_buffer_avail(nghttp2_buffer *buffer) -{ - return buffer->capacity-buffer->last_offset; -} - -void nghttp2_buffer_advance(nghttp2_buffer *buffer, size_t amount) -{ - buffer->last_offset += amount; - buffer->len += amount; - assert(buffer->last_offset <= buffer->capacity); -} - -int nghttp2_buffer_write(nghttp2_buffer *buffer, const uint8_t *data, - size_t len) +int nghttp2_buffer_add(nghttp2_buffer *buffer, + const uint8_t *data, size_t len) { int rv; - while(len) { - size_t writelen; - if(nghttp2_buffer_avail(buffer) == 0) { - if((rv = nghttp2_buffer_alloc(buffer)) != 0) { - return rv; - } - } - writelen = nghttp2_min(nghttp2_buffer_avail(buffer), len); - memcpy(nghttp2_buffer_get(buffer), data, writelen); - data += writelen; - len -= writelen; - nghttp2_buffer_advance(buffer, writelen); + rv = nghttp2_buffer_reserve(buffer, buffer->len + len); + if(rv != 0) { + return rv; } + memcpy(buffer->buf + buffer->len, data, len); + buffer->len += len; return 0; } -size_t nghttp2_buffer_length(nghttp2_buffer *buffer) +int nghttp2_buffer_add_byte(nghttp2_buffer *buffer, uint8_t b) { - return buffer->len; -} - -size_t nghttp2_buffer_capacity(nghttp2_buffer *buffer) -{ - return buffer->capacity; -} - -void nghttp2_buffer_serialize(nghttp2_buffer *buffer, uint8_t *buf) -{ - nghttp2_buffer_chunk *p = buffer->root.next; - for(; p; p = p->next) { - size_t len; - if(p == buffer->current) { - len = buffer->last_offset; - } else { - len = buffer->capacity; - } - memcpy(buf, p->data, len); - buf += len; + int rv; + rv = nghttp2_buffer_reserve(buffer, buffer->len + 1); + if(rv != 0) { + return rv; } + buffer->buf[buffer->len] = b; + ++buffer->len; + return 0; } -void nghttp2_buffer_reset(nghttp2_buffer *buffer) +void nghttp2_buffer_release(nghttp2_buffer *buffer) { - buffer->current = &buffer->root; + buffer->buf = NULL; buffer->len = 0; - buffer->last_offset = buffer->capacity; -} - -void nghttp2_buffer_reader_init(nghttp2_buffer_reader *reader, - nghttp2_buffer *buffer) -{ - reader->buffer = buffer; - reader->current = buffer->root.next; - reader->offset = 0; -} - -uint8_t nghttp2_buffer_reader_uint8(nghttp2_buffer_reader *reader) -{ - uint8_t out; - nghttp2_buffer_reader_data(reader, &out, sizeof(uint8_t)); - return out; -} - -uint16_t nghttp2_buffer_reader_uint16(nghttp2_buffer_reader *reader) -{ - uint16_t out; - nghttp2_buffer_reader_data(reader, (uint8_t*)&out, sizeof(uint16_t)); - return ntohs(out); -} - -uint32_t nghttp2_buffer_reader_uint32(nghttp2_buffer_reader *reader) -{ - uint32_t out; - nghttp2_buffer_reader_data(reader, (uint8_t*)&out, sizeof(uint32_t)); - return ntohl(out); -} - -void nghttp2_buffer_reader_data(nghttp2_buffer_reader *reader, - uint8_t *out, size_t len) -{ - while(len) { - size_t remlen, readlen; - remlen = reader->buffer->capacity - reader->offset; - readlen = nghttp2_min(remlen, len); - memcpy(out, reader->current->data + reader->offset, readlen); - out += readlen; - len -= readlen; - reader->offset += readlen; - if(reader->buffer->capacity == reader->offset) { - reader->current = reader->current->next; - reader->offset = 0; - } - } -} - -int nghttp2_buffer_reader_count(nghttp2_buffer_reader *reader, - size_t len, uint8_t c) -{ - int res = 0; - while(len) { - size_t remlen, readlen, i; - uint8_t *p; - remlen = reader->buffer->capacity - reader->offset; - readlen = nghttp2_min(remlen, len); - p = reader->current->data + reader->offset; - for(i = 0; i < readlen; ++i) { - if(p[i] == c) { - ++res; - } - } - len -= readlen; - reader->offset += readlen; - if(reader->buffer->capacity == reader->offset) { - reader->current = reader->current->next; - reader->offset = 0; - } - } - return res; -} - -void nghttp2_buffer_reader_advance(nghttp2_buffer_reader *reader, - size_t amount) -{ - while(amount) { - size_t remlen, skiplen; - remlen = reader->buffer->capacity - reader->offset; - skiplen = nghttp2_min(remlen, amount); - amount -= skiplen; - reader->offset += skiplen; - if(reader->buffer->capacity == reader->offset) { - reader->current = reader->current->next; - reader->offset = 0; - } - } + buffer->capacity = 0; } diff --git a/lib/nghttp2_buffer.h b/lib/nghttp2_buffer.h index 755f34df..a2f46e27 100644 --- a/lib/nghttp2_buffer.h +++ b/lib/nghttp2_buffer.h @@ -1,7 +1,7 @@ /* * nghttp2 - HTTP/2.0 C Library * - * Copyright (c) 2012 Tatsuhiro Tsujikawa + * Copyright (c) 2014 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -31,140 +31,73 @@ #include -typedef struct nghttp2_buffer_chunk { - uint8_t *data; - struct nghttp2_buffer_chunk *next; -} nghttp2_buffer_chunk; +#include "nghttp2_int.h" /* - * List of fixed sized chunks + * Byte array buffer */ typedef struct { - /* Capacity of each chunk buffer */ + uint8_t *buf; + /* Capacity of this buffer */ size_t capacity; - /* Root of list of chunk buffers. The root is dummy and its data - member is always NULL. */ - nghttp2_buffer_chunk root; - /* Points to the current chunk to write */ - nghttp2_buffer_chunk *current; - /* Total length of this buffer */ + /* How many bytes are written to buf. len <= capacity must hold. */ size_t len; - /* Offset of last chunk buffer */ - size_t last_offset; + /* Maximum capacity this buffer can grow up */ + size_t max_capacity; } nghttp2_buffer; -/* - * Initializes buffer with fixed chunk size chunk_capacity. - */ -void nghttp2_buffer_init(nghttp2_buffer *buffer, size_t chunk_capacity); -/* Releases allocated memory for buffer */ +void nghttp2_buffer_init(nghttp2_buffer *buffer, size_t max_capacity); + void nghttp2_buffer_free(nghttp2_buffer *buffer); -/* Returns buffer pointer */ -uint8_t* nghttp2_buffer_get(nghttp2_buffer *buffer); -/* Returns available buffer length */ -size_t nghttp2_buffer_avail(nghttp2_buffer *buffer); -/* Advances buffer pointer by amount. This reduces available buffer - length. */ -void nghttp2_buffer_advance(nghttp2_buffer *buffer, size_t amount); /* - * Writes the |data| with the |len| bytes starting at the current - * position of the |buffer|. The new chunk buffer will be allocated on - * the course of the write and the current position is updated. If - * this function succeeds, the total length of the |buffer| will be - * increased by |len|. + * Expands capacity so that it can contain at least |len| bytes of + * data. If buffer->capacity >= len, no action is taken. If len > + * buffer->max_capacity, NGHTTP2_ERR_BUFFER_ERROR is returned. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * + * NGHTTP2_ERR_BUFFER_ERROR + * The |len| is strictly larger than buffer->max_capacity * NGHTTP2_ERR_NOMEM - * Out of memory. + * Out of memory */ -int nghttp2_buffer_write(nghttp2_buffer *buffer, const uint8_t *data, - size_t len); +int nghttp2_buffer_reserve(nghttp2_buffer *buffer, size_t len); /* - * Allocate new chunk buffer. This will increase total length of - * buffer (returned by nghttp2_buffer_length) by capacity-last_offset. - * It means untouched buffer is assumued to be written. + * Appends the |data| with |len| bytes to the buffer. The data is + * copied. The |buffer| will be expanded as needed. * * This function returns 0 if it succeeds, or one of the following - * negative eror codes: + * negative error codes: * + * NGHTTP2_ERR_BUFFER_ERROR + * The |len| is strictly larger than buffer->max_capacity * NGHTTP2_ERR_NOMEM - * Out of memory. + * Out of memory */ -int nghttp2_buffer_alloc(nghttp2_buffer *buffer); - -/* Returns total length of buffer */ -size_t nghttp2_buffer_length(nghttp2_buffer *buffer); - -/* Returns capacity of each fixed chunk buffer */ -size_t nghttp2_buffer_capacity(nghttp2_buffer *buffer); - -/* Stores the contents of buffer into |buf|. |buf| must be at least - nghttp2_buffer_length(buffer) bytes long. */ -void nghttp2_buffer_serialize(nghttp2_buffer *buffer, uint8_t *buf); - -/* Reset |buffer| for reuse. Set the total length of buffer to 0. - Next nghttp2_buffer_avail() returns 0. This function does not free - allocated memory space; they are reused. */ -void nghttp2_buffer_reset(nghttp2_buffer *buffer); +int nghttp2_buffer_add(nghttp2_buffer *buffer, + const uint8_t *data, size_t len); /* - * Reader interface to read data from nghttp2_buffer sequentially. + * Appends the a single byte|b| to the buffer. The data is copied. The + * |buffer| will be expanded as needed. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_BUFFER_ERROR + * The |len| is strictly larger than buffer->max_capacity + * NGHTTP2_ERR_NOMEM + * Out of memory */ -typedef struct { - /* The buffer to read */ - nghttp2_buffer *buffer; - /* Pointer to the current chunk to read. */ - nghttp2_buffer_chunk *current; - /* Offset to the current chunk data to read. */ - size_t offset; -} nghttp2_buffer_reader; +int nghttp2_buffer_add_byte(nghttp2_buffer *buffer, uint8_t b); /* - * Initializes the |reader| with the |buffer|. + * Releases the buffer without freeing it. The data members in buffer + * is initialized. */ -void nghttp2_buffer_reader_init(nghttp2_buffer_reader *reader, - nghttp2_buffer *buffer); - -/* - * Reads 1 byte and return it. This function will advance the current - * position by 1. - */ -uint8_t nghttp2_buffer_reader_uint8(nghttp2_buffer_reader *reader); - -/* - * Reads 2 bytes integer in network byte order and returns it in host - * byte order. This function will advance the current position by 2. - */ -uint16_t nghttp2_buffer_reader_uint16(nghttp2_buffer_reader *reader); - -/* - * Reads 4 bytes integer in network byte order and returns it in host - * byte order. This function will advance the current position by 4. - */ -uint32_t nghttp2_buffer_reader_uint32(nghttp2_buffer_reader *reader); - -/* - * Reads |len| bytes and store them in the |out|. This function will - * advance the current position by |len|. - */ -void nghttp2_buffer_reader_data(nghttp2_buffer_reader *reader, - uint8_t *out, size_t len); - -/** - * Reads |len| bytes and count the occurrence of |c| there and return - * it. This function will advance the current position by |len|. - */ -int nghttp2_buffer_reader_count(nghttp2_buffer_reader *reader, - size_t len, uint8_t c); - -/* - * Advances the current position by |amount|. - */ -void nghttp2_buffer_reader_advance(nghttp2_buffer_reader *reader, - size_t amount); +void nghttp2_buffer_release(nghttp2_buffer *buffer); #endif /* NGHTTP2_BUFFER_H */ diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c index 0fbfc039..a054e2cf 100644 --- a/lib/nghttp2_hd.c +++ b/lib/nghttp2_hd.c @@ -331,8 +331,22 @@ int nghttp2_hd_deflate_init2(nghttp2_hd_context *deflater, 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 rv; + rv = nghttp2_hd_context_init(inflater, NGHTTP2_HD_ROLE_INFLATE, side, + NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE); + if(rv != 0) { + return rv; + } + inflater->opcode = NGHTTP2_HD_OPCODE_NONE; + inflater->state = NGHTTP2_HD_STATE_OPCODE; + nghttp2_buffer_init(&inflater->namebuf, NGHTTP2_HD_MAX_NAME); + nghttp2_buffer_init(&inflater->valuebuf, NGHTTP2_HD_MAX_VALUE); + inflater->huffman_encoded = 0; + inflater->index = 0; + inflater->left = 0; + inflater->index_required = 0; + inflater->ent_name = NULL; + return 0; } static void hd_inflate_keep_free(nghttp2_hd_context *inflater) @@ -363,6 +377,8 @@ void nghttp2_hd_deflate_free(nghttp2_hd_context *deflater) void nghttp2_hd_inflate_free(nghttp2_hd_context *inflater) { hd_inflate_keep_free(inflater); + nghttp2_buffer_free(&inflater->namebuf); + nghttp2_buffer_free(&inflater->valuebuf); nghttp2_hd_context_free(inflater); } @@ -494,28 +510,39 @@ static size_t encode_length(uint8_t *buf, size_t n, int prefix) * region from |in|. The decoded integer must be strictly less than 1 * << 16. * + * If the |initial| is nonzero, it is used as a initial value, this + * function assumes the |in| starts with intermediate data. + * + * An entire integer is decoded successfully, decoded, the |*final| is + * set to nonzero. + * * 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. + * stores the decoded integer in |*res| if it succeed, including + * partial decoding, or stores -1 in |*res|, indicating decoding + * error. */ -static uint8_t* decode_length(ssize_t *res, uint8_t *in, uint8_t *last, - int prefix) +static uint8_t* decode_length(ssize_t *res, int *final, ssize_t initial, + uint8_t *in, uint8_t *last, int prefix) { int k = (1 << prefix) - 1, r; - if(in == last) { - *res = -1; + ssize_t n = initial; + *final = 0; + if(n == 0) { + if((*in & k) == k) { + n = k; + } else { + *res = (*in) & k; + *final = 1; + return in + 1; + } + } + if(++in == last) { + *res = n; return in; } - if((*in & k) == 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)) { + n += (*in & 0x7f) << r; + if(n >= (1 << 16)) { *res = -1; return in + 1; } @@ -523,12 +550,17 @@ static uint8_t* decode_length(ssize_t *res, uint8_t *in, uint8_t *last, break; } } - if(in == last || *in & (1 << 7)) { + if(in == last) { + *res = n; + return in; + } + if(*in & (1 << 7)) { *res = -1; - return NULL; - } else { return in + 1; } + *res = n; + *final = 1; + return in + 1; } static int emit_indexed0(uint8_t **buf_ptr, size_t *buflen_ptr, @@ -911,6 +943,11 @@ static int check_index_range(nghttp2_hd_context *context, size_t index) return index < context->hd_table.len + STATIC_TABLE_LENGTH; } +static int get_max_index(nghttp2_hd_context *context) +{ + return context->hd_table.len + STATIC_TABLE_LENGTH - 1; +} + nghttp2_hd_entry* nghttp2_hd_table_get(nghttp2_hd_context *context, size_t index) { @@ -1128,274 +1165,502 @@ ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_context *deflater, return rv; } -ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, - nghttp2_nv *nv_out, int *final, - uint8_t *in, size_t inlen) +static void hd_inflate_set_huffman_encoded(nghttp2_hd_context *inflater, + const uint8_t *in) { - int rv = 0; - uint8_t *first = in; - uint8_t *last = in + inlen; + inflater->huffman_encoded = (*in & (1 << 7)) != 0; +} - DEBUGF(fprintf(stderr, "infalte_hd start\n")); - if(inflater->bad) { +/* + * Decodes the integer from the range [in, last). The result is + * assigned to |inflater->left|. If the |inflater->left| is 0, then + * it performs variable integer decoding from scratch. Otherwise, it + * uses the |inflater->left| as the initial value and continues to + * decode assuming that [in, last) begins with intermediary sequence. + * + * This function returns the number of bytes read if it succeeds, or + * one of the following negative error codes: + * + * NGHTTP2_ERR_HEADER_COMP + * Integer decoding failed + */ +static ssize_t hd_inflate_read_len(nghttp2_hd_context *inflater, + int *rfin, + uint8_t *in, uint8_t *last, + int prefix, size_t maxlen) +{ + uint8_t *nin; + *rfin = 0; + nin = decode_length(&inflater->left, rfin, inflater->left, in, last, prefix); + if(inflater->left == -1) { + DEBUGF(fprintf(stderr, "invalid integer\n")); return NGHTTP2_ERR_HEADER_COMP; } - - *final = 0; - hd_inflate_keep_free(inflater); - - for(; in != last;) { - uint8_t c = *in; - if(c & 0x80u) { - /* Indexed Header Repr */ - ssize_t index; - nghttp2_hd_entry *ent; - in = decode_length(&index, in, last, 7); - DEBUGF(fprintf(stderr, "Indexed repr index=%zd\n", index)); - if(index < 0) { - DEBUGF(fprintf(stderr, "Index out of range index=%zd\n", index)); - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - if(index == 0) { - DEBUGF(fprintf(stderr, "Clearing reference set\n")); - clear_refset(inflater); - continue; - } - --index; - if(!check_index_range(inflater, index)) { - DEBUGF(fprintf(stderr, "Index out of range index=%zd\n", index)); - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - ent = nghttp2_hd_table_get(inflater, index); - if(index >= (ssize_t)inflater->hd_table.len) { - nghttp2_hd_entry *new_ent; - new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, - &ent->nv, NGHTTP2_HD_FLAG_NONE); - if(!new_ent) { - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - /* new_ent->ref == 0 may be hold but emit_indexed_header - tracks new_ent, so there is no leak. */ - emit_indexed_header(inflater, nv_out, new_ent); - inflater->ent_keep = new_ent; - return in - first; - } else { - ent->flags ^= NGHTTP2_HD_FLAG_REFSET; - if(ent->flags & NGHTTP2_HD_FLAG_REFSET) { - emit_indexed_header(inflater, nv_out, ent); - return in - first; - } else { - DEBUGF(fprintf(stderr, "Toggle off item:\n")); - DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr)); - DEBUGF(fprintf(stderr, ": ")); - DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr)); - DEBUGF(fprintf(stderr, "\n")); - } - } - if(rv != 0) { - goto fail; - } - } else if(c == 0x40u || c == 0) { - /* Literal Header Repr - New Name */ - nghttp2_nv nv; - ssize_t namelen, valuelen; - int name_huffman, value_huffman; - uint8_t *decoded_huffman_name = NULL, *decoded_huffman_value = NULL; - DEBUGF(fprintf(stderr, "Literal header repr - new name\n")); - if(++in == last) { - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - name_huffman = *in & (1 << 7); - in = decode_length(&namelen, in, last, 7); - if(namelen < 0 || in + namelen > last) { - DEBUGF(fprintf(stderr, "Invalid namelen=%zd\n", namelen)); - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - if(name_huffman) { - rv = nghttp2_hd_huff_decode(&nv.name, in, namelen, inflater->side); - if(rv < 0) { - DEBUGF(fprintf(stderr, "Name huffman decoding failed\n")); - goto fail; - } - decoded_huffman_name = nv.name; - nv.namelen = rv; - } else { - nv.name = in; - nv.namelen = namelen; - } - in += namelen; - - if(in == last) { - DEBUGF(fprintf(stderr, "No value found\n")); - free(decoded_huffman_name); - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - value_huffman = *in & (1 << 7); - in = decode_length(&valuelen, in, last, 7); - if(valuelen < 0 || in + valuelen > last) { - DEBUGF(fprintf(stderr, "Invalid valuelen=%zd\n", valuelen)); - free(decoded_huffman_name); - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - if(value_huffman) { - rv = nghttp2_hd_huff_decode(&nv.value, in, valuelen, inflater->side); - if(rv < 0) { - DEBUGF(fprintf(stderr, "Value huffman decoding failed\n")); - free(decoded_huffman_name); - goto fail; - } - decoded_huffman_value = nv.value; - nv.valuelen = rv; - } else { - nv.value = in; - nv.valuelen = valuelen; - } - in += valuelen; - - if(c == 0x40u) { - int flags = NGHTTP2_HD_FLAG_NONE; - if(name_huffman) { - flags |= NGHTTP2_HD_FLAG_NAME_GIFT; - } - if(value_huffman) { - flags |= NGHTTP2_HD_FLAG_VALUE_GIFT; - } - emit_newname_header(inflater, nv_out, &nv); - inflater->name_keep = decoded_huffman_name; - inflater->value_keep = decoded_huffman_value; - return in - first; - } else { - nghttp2_hd_entry *new_ent; - uint8_t ent_flags = NGHTTP2_HD_FLAG_NAME_ALLOC | - NGHTTP2_HD_FLAG_VALUE_ALLOC; - if(name_huffman) { - ent_flags |= NGHTTP2_HD_FLAG_NAME_GIFT; - } - if(value_huffman) { - ent_flags |= NGHTTP2_HD_FLAG_VALUE_GIFT; - } - new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv, - ent_flags); - if(new_ent) { - emit_indexed_header(inflater, nv_out, new_ent); - inflater->ent_keep = new_ent; - return in - first; - } else { - free(decoded_huffman_name); - free(decoded_huffman_value); - rv = NGHTTP2_ERR_HEADER_COMP; - } - } - if(rv != 0) { - goto fail; - } - } else { - /* Literal Header Repr - Indexed Name */ - nghttp2_hd_entry *ent; - uint8_t *value; - ssize_t valuelen, index; - int value_huffman; - uint8_t *decoded_huffman_value = NULL; - DEBUGF(fprintf(stderr, "Literal header repr - indexed name\n")); - in = decode_length(&index, in, last, 6); - if(index <= 0) { - DEBUGF(fprintf(stderr, "Index out of range index=%zd\n", index)); - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - --index; - if(!check_index_range(inflater, index)) { - DEBUGF(fprintf(stderr, "Index out of range index=%zd\n", index)); - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - ent = nghttp2_hd_table_get(inflater, index); - if(in == last) { - DEBUGF(fprintf(stderr, "No value found\n")); - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - value_huffman = *in & (1 << 7); - in = decode_length(&valuelen, in , last, 7); - if(valuelen < 0 || in + valuelen > last) { - DEBUGF(fprintf(stderr, "Invalid valuelen=%zd\n", valuelen)); - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; - } - if(value_huffman) { - rv = nghttp2_hd_huff_decode(&value, in, valuelen, inflater->side); - if(rv < 0) { - DEBUGF(fprintf(stderr, "Value huffman decoding failed\n")); - goto fail; - } - decoded_huffman_value = value; - in += valuelen; - valuelen = rv; - } else { - value = in; - in += valuelen; - } - if((c & 0x40u) == 0x40u) { - emit_indname_header(inflater, nv_out, ent, value, valuelen); - inflater->value_keep = decoded_huffman_value; - return in - first; - } else { - nghttp2_nv nv; - nghttp2_hd_entry *new_ent; - uint8_t ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC; - if(value_huffman) { - ent_flags |= NGHTTP2_HD_FLAG_VALUE_GIFT; - } - ++ent->ref; - nv.name = ent->nv.name; - if((size_t)index < inflater->hd_table.len) { - ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC; - } - nv.namelen = ent->nv.namelen; - nv.value = value; - nv.valuelen = valuelen; - new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv, - ent_flags); - if(--ent->ref == 0) { - nghttp2_hd_entry_free(ent); - free(ent); - } - if(new_ent) { - emit_indexed_header(inflater, nv_out, new_ent); - inflater->ent_keep = new_ent; - return in - first; - } else { - free(decoded_huffman_value); - rv = NGHTTP2_ERR_HEADER_COMP; - } - } - if(rv != 0) { - goto fail; - } - } + if((size_t)inflater->left > maxlen) { + DEBUGF(fprintf(stderr, "integer exceeds the maximum value %zu\n", maxlen)); + return NGHTTP2_ERR_HEADER_COMP; } + return nin - in; +} - for(; inflater->end_headers_index < inflater->hd_table.len; - ++inflater->end_headers_index) { - nghttp2_hd_entry *ent; - ent = nghttp2_hd_ringbuf_get(&inflater->hd_table, - inflater->end_headers_index); +/* + * Reads |inflater->left| bytes from the range [in, last) and performs + * huffman decoding against them and pushes the result into the + * |buffer|. + * + * This function returns the number of bytes read if it succeeds, or + * one of the following negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + * NGHTTP2_ERR_HEADER_COMP + * Huffman decoding failed + */ +static ssize_t hd_inflate_read_huff(nghttp2_hd_context *inflater, + nghttp2_buffer *buffer, + uint8_t *in, uint8_t *last) +{ + int rv; + int final = 0; + if(last - in >= inflater->left) { + last = in + inflater->left; + final = 1; + } + rv = nghttp2_hd_huff_decode(&inflater->huff_decode_ctx, buffer, + in, last - in, final); + if(rv == NGHTTP2_ERR_BUFFER_ERROR) { + return NGHTTP2_ERR_HEADER_COMP; + } + if(rv < 0) { + DEBUGF(fprintf(stderr, "huffman decoding failed\n")); + return rv; + } + inflater->left -= rv; + return rv; +} - if((ent->flags & NGHTTP2_HD_FLAG_REFSET) && - (ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) { - emit_indexed_header(inflater, nv_out, ent); +/* + * Reads |inflater->left| bytes from the range [in, last) and copies + * them into the |buffer|. + * + * This function returns the number of bytes read if it succeeds, or + * one of the following negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + * NGHTTP2_ERR_HEADER_COMP + * Header decompression failed + */ +static ssize_t hd_inflate_read(nghttp2_hd_context *inflater, + nghttp2_buffer *buffer, + uint8_t *in, uint8_t *last) +{ + int rv; + size_t len = nghttp2_min(last - in, inflater->left); + rv = nghttp2_buffer_add(buffer, in, len); + if(rv == NGHTTP2_ERR_BUFFER_ERROR) { + return NGHTTP2_ERR_HEADER_COMP; + } + if(rv != 0) { + return rv; + } + inflater->left -= len; + return len; +} + +/* + * Finalize indexed header representation reception. If header is + * emitted, |*nv_out| is filled with that value and 0 is returned. If + * no header is emitted, 1 is returned. + * + * This function returns either 0 or 1 if it succeeds, or one of the + * following negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +static int hd_inflate_commit_indexed(nghttp2_hd_context *inflater, + nghttp2_nv *nv_out) +{ + nghttp2_hd_entry *ent = nghttp2_hd_table_get(inflater, inflater->index); + if(inflater->index >= inflater->hd_table.len) { + nghttp2_hd_entry *new_ent; + new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, + &ent->nv, NGHTTP2_HD_FLAG_NONE); + if(!new_ent) { + return NGHTTP2_ERR_NOMEM; + } + /* new_ent->ref == 0 may be hold */ + emit_indexed_header(inflater, nv_out, new_ent); + inflater->ent_keep = new_ent; + return 0; + } + ent->flags ^= NGHTTP2_HD_FLAG_REFSET; + if(ent->flags & NGHTTP2_HD_FLAG_REFSET) { + emit_indexed_header(inflater, nv_out, ent); + return 0; + } + DEBUGF(fprintf(stderr, "Toggle off item:\n")); + DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr)); + DEBUGF(fprintf(stderr, ": ")); + DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr)); + DEBUGF(fprintf(stderr, "\n")); + return 1; +} + +/* + * Finalize literal header representation - new name- reception. If + * header is emitted, |*nv_out| is filled with that value and 0 is + * returned. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +static int hd_inflate_commit_newname(nghttp2_hd_context *inflater, + nghttp2_nv *nv_out) +{ + nghttp2_nv nv = { + inflater->namebuf.buf, + inflater->valuebuf.buf, + inflater->namebuf.len, + inflater->valuebuf.len + }; + if(inflater->index_required) { + nghttp2_hd_entry *new_ent; + uint8_t ent_flags = + NGHTTP2_HD_FLAG_NAME_ALLOC | NGHTTP2_HD_FLAG_VALUE_ALLOC | + NGHTTP2_HD_FLAG_NAME_GIFT | NGHTTP2_HD_FLAG_VALUE_GIFT; + new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv, + ent_flags); + if(new_ent) { + nghttp2_buffer_release(&inflater->namebuf); + nghttp2_buffer_release(&inflater->valuebuf); + emit_indexed_header(inflater, nv_out, new_ent); + inflater->ent_keep = new_ent; + return 0; + } + return NGHTTP2_ERR_NOMEM; + } + emit_newname_header(inflater, nv_out, &nv); + inflater->name_keep = inflater->namebuf.buf; + nghttp2_buffer_release(&inflater->namebuf); + inflater->value_keep = inflater->valuebuf.buf; + nghttp2_buffer_release(&inflater->valuebuf); + return 0; +} + +/* + * Finalize literal header representation - indexed name- + * reception. If header is emitted, |*nv_out| is filled with that + * value and 0 is returned. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +static int hd_inflate_commit_indname(nghttp2_hd_context *inflater, + nghttp2_nv *nv_out) +{ + if(inflater->index_required) { + nghttp2_nv nv; + nghttp2_hd_entry *new_ent; + uint8_t ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC | + NGHTTP2_HD_FLAG_VALUE_GIFT; + + if(inflater->index < inflater->hd_table.len) { + ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC; + } + ++inflater->ent_name->ref; + nv.name = inflater->ent_name->nv.name; + nv.namelen = inflater->ent_name->nv.namelen; + nv.value = inflater->valuebuf.buf; + nv.valuelen = inflater->valuebuf.len; + new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv, + ent_flags); + if(--inflater->ent_name->ref == 0) { + nghttp2_hd_entry_free(inflater->ent_name); + free(inflater->ent_name); + } + inflater->ent_name = NULL; + if(new_ent) { + nghttp2_buffer_release(&inflater->valuebuf); + emit_indexed_header(inflater, nv_out, new_ent); + inflater->ent_keep = new_ent; + return 0; + } + return NGHTTP2_ERR_NOMEM; + } + emit_indname_header(inflater, nv_out, inflater->ent_name, + inflater->valuebuf.buf, inflater->valuebuf.len); + inflater->value_keep = inflater->valuebuf.buf; + nghttp2_buffer_release(&inflater->valuebuf); + return 0; +} + +static size_t guess_huff_decode_len(size_t encode_len) +{ + return encode_len * 3 / 2; +} + +ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, + nghttp2_nv *nv_out, int *inflate_flags, + uint8_t *in, size_t inlen, int in_final) +{ + ssize_t rv = 0; + uint8_t *first = in; + uint8_t *last = in + inlen; + int rfin = 0; + + DEBUGF(fprintf(stderr, "nghtp2_hd_infalte_hd start state=%d\n", + inflater->state)); + *inflate_flags = NGHTTP2_HD_INFLATE_NONE; + for(; in != last;) { + switch(inflater->state) { + case NGHTTP2_HD_STATE_OPCODE: + if(*in & 0x80u) { + DEBUGF(fprintf(stderr, "Indexed repr\n")); + inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED; + inflater->state = NGHTTP2_HD_STATE_READ_INDEX; + } else { + if(*in == 0x40 || *in == 0) { + DEBUGF(fprintf(stderr, "Literal header repr - new name\n")); + inflater->opcode = NGHTTP2_HD_OPCODE_NEWNAME; + inflater->state = NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN; + } else { + DEBUGF(fprintf(stderr, "Literal header repr - indexed name\n")); + inflater->opcode = NGHTTP2_HD_OPCODE_INDNAME; + inflater->state = NGHTTP2_HD_STATE_READ_INDEX; + } + inflater->index_required = (*in & 0x40) == 0; + DEBUGF(fprintf(stderr, "indexing required=%d\n", + inflater->index_required != 0)); + if(inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { + ++in; + } + } + inflater->left = 0; + break; + case NGHTTP2_HD_STATE_READ_INDEX: + rfin = 0; + rv = hd_inflate_read_len(inflater, &rfin, in, last, + inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED ? + 7 : 6, + get_max_index(inflater) + 1); + if(rv < 0) { + goto fail; + } + in += rv; + if(!rfin) { + return in - first; + } + DEBUGF(fprintf(stderr, "index=%zd\n", inflater->left)); + if(inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) { + inflater->index = inflater->left; + if(inflater->index == 0) { + DEBUGF(fprintf(stderr, "Clearing reference set\n")); + clear_refset(inflater); + inflater->state = NGHTTP2_HD_STATE_OPCODE; + break; + } + --inflater->index; + rv = hd_inflate_commit_indexed(inflater, nv_out); + if(rv < 0) { + goto fail; + } + inflater->state = NGHTTP2_HD_STATE_OPCODE; + /* If rv == 1, no header was emitted */ + if(rv == 0) { + *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; + return in - first; + } + } else { + inflater->index = inflater->left; + --inflater->index; + inflater->ent_name = nghttp2_hd_table_get(inflater, inflater->index); + inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; + } + break; + case NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN: + hd_inflate_set_huffman_encoded(inflater, in); + inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN; + inflater->left = 0; + DEBUGF(fprintf(stderr, "huffman encoded=%d\n", + inflater->huffman_encoded != 0)); + /* Fall through */ + case NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN: + rfin = 0; + rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, + NGHTTP2_HD_MAX_NAME); + if(rv < 0) { + goto fail; + } + in += rv; + if(!rfin) { + DEBUGF(fprintf(stderr, "integer not fully decoded. current=%zd\n", + inflater->left)); + return in - first; + } + rv = 0; + if(inflater->huffman_encoded) { + nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx, + inflater->side); + rv = nghttp2_buffer_reserve(&inflater->namebuf, + guess_huff_decode_len(inflater->left)); + if(rv != 0) { + goto fail; + } + inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF; + } else { + rv = nghttp2_buffer_reserve(&inflater->namebuf, inflater->left); + if(rv != 0) { + goto fail; + } + inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAME; + } + break; + case NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF: + rv = hd_inflate_read_huff(inflater, &inflater->namebuf, in, last); + if(rv < 0) { + goto fail; + } + in += rv; + DEBUGF(fprintf(stderr, "%zd bytes read\n", rv)); + if(inflater->left) { + DEBUGF(fprintf(stderr, "still %zd bytes to go\n", inflater->left)); + return in - first; + } + inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; + break; + case NGHTTP2_HD_STATE_NEWNAME_READ_NAME: + rv = hd_inflate_read(inflater, &inflater->namebuf, in, last); + if(rv < 0) { + goto fail; + } + in += rv; + DEBUGF(fprintf(stderr, "%zd bytes read\n", rv)); + if(inflater->left) { + DEBUGF(fprintf(stderr, "still %zd bytes to go\n", inflater->left)); + return in - first; + } + inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; + break; + case NGHTTP2_HD_STATE_CHECK_VALUELEN: + hd_inflate_set_huffman_encoded(inflater, in); + inflater->state = NGHTTP2_HD_STATE_READ_VALUELEN; + inflater->left = 0; + DEBUGF(fprintf(stderr, "huffman encoded=%d\n", + inflater->huffman_encoded != 0)); + /* Fall through */ + case NGHTTP2_HD_STATE_READ_VALUELEN: + rfin = 0; + rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, + NGHTTP2_HD_MAX_VALUE); + if(rv < 0) { + goto fail; + } + in += rv; + if(!rfin) { + return in - first; + } + DEBUGF(fprintf(stderr, "valuelen=%zd\n", inflater->left)); + if(inflater->left == 0) { + if(inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { + rv = hd_inflate_commit_newname(inflater, nv_out); + } else { + rv = hd_inflate_commit_indname(inflater, nv_out); + } + if(rv != 0) { + goto fail; + } + inflater->state = NGHTTP2_HD_STATE_OPCODE; + *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; + return in - first; + } + if(inflater->huffman_encoded) { + nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx, + inflater->side); + rv = nghttp2_buffer_reserve(&inflater->valuebuf, + guess_huff_decode_len(inflater->left)); + inflater->state = NGHTTP2_HD_STATE_READ_VALUEHUFF; + } else { + rv = nghttp2_buffer_reserve(&inflater->valuebuf, inflater->left); + if(rv != 0) { + goto fail; + } + inflater->state = NGHTTP2_HD_STATE_READ_VALUE; + } + break; + case NGHTTP2_HD_STATE_READ_VALUEHUFF: + rv = hd_inflate_read_huff(inflater, &inflater->valuebuf, in, last); + if(rv < 0) { + goto fail; + } + in += rv; + DEBUGF(fprintf(stderr, "%zd bytes read\n", rv)); + if(inflater->left) { + DEBUGF(fprintf(stderr, "still %zd bytes to go\n", inflater->left)); + return in - first; + } + if(inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { + rv = hd_inflate_commit_newname(inflater, nv_out); + } else { + rv = hd_inflate_commit_indname(inflater, nv_out); + } + if(rv != 0) { + goto fail; + } + inflater->state = NGHTTP2_HD_STATE_OPCODE; + *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; + return in - first; + case NGHTTP2_HD_STATE_READ_VALUE: + rv = hd_inflate_read(inflater, &inflater->valuebuf, in, last); + if(rv < 0) { + DEBUGF(fprintf(stderr, "value read failure %zd: %s\n", + rv, nghttp2_strerror(rv))); + goto fail; + } + in += rv; + DEBUGF(fprintf(stderr, "%zd bytes read\n", rv)); + if(inflater->left) { + DEBUGF(fprintf(stderr, "still %zd bytes to go\n", inflater->left)); + return in - first; + } + if(inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { + rv = hd_inflate_commit_newname(inflater, nv_out); + } else { + rv = hd_inflate_commit_indname(inflater, nv_out); + } + if(rv != 0) { + goto fail; + } + inflater->state = NGHTTP2_HD_STATE_OPCODE; + *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; return in - first; } - ent->flags &= ~NGHTTP2_HD_FLAG_EMIT; } - *final = 1; + assert(in == last); + if(in_final) { + for(; inflater->end_headers_index < inflater->hd_table.len; + ++inflater->end_headers_index) { + nghttp2_hd_entry *ent; + ent = nghttp2_hd_ringbuf_get(&inflater->hd_table, + inflater->end_headers_index); + + if((ent->flags & NGHTTP2_HD_FLAG_REFSET) && + (ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) { + emit_indexed_header(inflater, nv_out, ent); + *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; + return in - first; + } + ent->flags &= ~NGHTTP2_HD_FLAG_EMIT; + } + *inflate_flags |= NGHTTP2_HD_INFLATE_FINAL; + } return in - first; fail: inflater->bad = 1; diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h index fedd88e6..640590ad 100644 --- a/lib/nghttp2_hd.h +++ b/lib/nghttp2_hd.h @@ -31,9 +31,15 @@ #include +#include "nghttp2_hd_huffman.h" +#include "nghttp2_buffer.h" + #define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE (1 << 12) #define NGHTTP2_HD_ENTRY_OVERHEAD 32 +#define NGHTTP2_HD_MAX_NAME 256 +#define NGHTTP2_HD_MAX_VALUE 4096 + /* 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. */ @@ -90,6 +96,26 @@ typedef struct { size_t len; } nghttp2_hd_ringbuf; +typedef enum { + NGHTTP2_HD_OPCODE_NONE, + NGHTTP2_HD_OPCODE_INDEXED, + NGHTTP2_HD_OPCODE_NEWNAME, + NGHTTP2_HD_OPCODE_INDNAME +} nghttp2_hd_opcode; + +typedef enum { + NGHTTP2_HD_STATE_OPCODE, + NGHTTP2_HD_STATE_READ_INDEX, + NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN, + NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN, + NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF, + NGHTTP2_HD_STATE_NEWNAME_READ_NAME, + NGHTTP2_HD_STATE_CHECK_VALUELEN, + NGHTTP2_HD_STATE_READ_VALUELEN, + NGHTTP2_HD_STATE_READ_VALUEHUFF, + NGHTTP2_HD_STATE_READ_VALUE, +} nghttp2_hd_inflate_state; + typedef struct { /* dynamic header table */ nghttp2_hd_ringbuf hd_table; @@ -139,6 +165,17 @@ typedef struct { /* Set to this nonzero to clear reference set on each deflation each time. */ uint8_t no_refset; + /* Decoder specific members */ + nghttp2_buffer namebuf; + nghttp2_buffer valuebuf; + nghttp2_hd_huff_decode_context huff_decode_ctx; + int state; + nghttp2_hd_opcode opcode; + uint8_t huffman_encoded; + uint8_t index_required; + ssize_t left; + size_t index; + nghttp2_hd_entry *ent_name; } nghttp2_hd_context; /* @@ -270,26 +307,34 @@ ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_context *deflater, size_t nv_offset, nghttp2_nv *nva, size_t nvlen); +typedef enum { + NGHTTP2_HD_INFLATE_NONE = 0, + NGHTTP2_HD_INFLATE_FINAL = 1, + NGHTTP2_HD_INFLATE_EMIT = (1 << 1) +} nghttp2_hd_inflate_flag; + /* * Inflates name/value block stored in |in| with length |inlen|. This * function performs decompression. For each successful emission of - * header name/value pair, name/value pair is assigned to the - * |nv_out| and the function returns. The caller must not free - * the members of |nv_out|. + * header name/value pair, NGHTTP2_HD_INFLATE_EMIT is set in + * |*inflate_flags| and name/value pair is assigned to the |nv_out| + * and the function returns. The caller must not free the members of + * |nv_out|. * - * The |nv_out| includes pointers to the memory region in the + * The |nv_out| may include pointers to the memory region in the * |in|. The caller must retain the |in| while the |nv_out| is used. * * The application should call this function repeatedly until the - * |*final| is nonzero and return value is non-negative. This means - * the all input values are processed successfully. If |*final| is - * nonzero, no header name/value is emitted. Then the application must - * call `nghttp2_hd_inflate_end_headers()` to prepare for the next - * header block input. + * |(*inflate_flags) & NGHTTP2_HD_INFLATE_FINAL| is nonzero and return + * value is non-negative. This means the all input values are + * processed successfully. Then the application must call + * `nghttp2_hd_inflate_end_headers()` to prepare for the next header + * block input. * - * Currently, the whole compressed header block must be given in the - * |in| and |inlen|. Otherwise, it may lead to NGHTTP2_ERR_HEADER_COMP - * error. + * The caller can feed complete compressed header block. It also can + * feed it in several chunks. The caller must set |in_final| to + * nonzero if the given input is the last block of the compressed + * header. * * This function returns the number of bytes processed if it succeeds, * or one of the following negative error codes: @@ -300,8 +345,8 @@ ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_context *deflater, * Inflation process has failed. */ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, - nghttp2_nv *nv_out, int *final, - uint8_t *in, size_t inlen); + nghttp2_nv *nv_out, int *inflate_flags, + uint8_t *in, size_t inlen, int in_final); /* * Signals the end of decompression for one header block. @@ -324,17 +369,6 @@ int nghttp2_hd_emit_newname_block(uint8_t **buf_ptr, size_t *buflen_ptr, int inc_indexing, nghttp2_hd_side side); -/* For unittesting purpose */ -int nghttp2_hd_emit_subst_indname_block(uint8_t **buf_ptr, size_t *buflen_ptr, - size_t *offset_ptr, size_t index, - const uint8_t *value, size_t valuelen, - size_t subindex); - -/* For unittesting purpose */ -int nghttp2_hd_emit_subst_newname_block(uint8_t **buf_ptr, size_t *buflen_ptr, - size_t *offset_ptr, nghttp2_nv *nv, - size_t subindex); - /* For unittesting purpose */ nghttp2_hd_entry* nghttp2_hd_table_get(nghttp2_hd_context *context, size_t index); @@ -371,29 +405,33 @@ ssize_t nghttp2_hd_huff_encode(uint8_t *dest, size_t destlen, const uint8_t *src, size_t srclen, nghttp2_hd_side side); +void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx, + nghttp2_hd_side side); + /* - * Decodes the given data |src| with length |srclen|. This function - * allocates memory to store the result and assigns the its pointer to - * |*dest_ptr| on success. The caller is responsible to release the - * memory pointed by |*dest_ptr| if this function succeeds. If |side| - * is NGHTTP2_HD_SIDE_REQUEST, the request huffman code table is - * used. Otherwise, the response code table is used. + * Decodes the given data |src| with length |srclen|. The |ctx| must + * be initialized by nghttp2_hd_huff_decode_context_init(). The result + * will be added to |dest|. This function may expand |dest| as + * needed. The caller is responsible to release the memory of |dest| + * by calling nghttp2_buffer_free(). * - * This function returns the number of written bytes. This return - * value is exactly the same with the return value of - * nghttp2_hd_huff_decode_count() if it is given with the same |src|, - * |srclen|, and |side|. + * The caller must set the |final| to nonzero if the given input is + * the final block. + * + * This function returns the number of read bytes from the |in|. * * If this function fails, it returns one of the following negative * return codes: * * NGHTTP2_ERR_NOMEM * Out of memory. + * NGHTTP2_ERR_BUFFER_ERROR + * Maximum buffer capacity size exceeded. * NGHTTP2_ERR_HEADER_COMP * Decoding process has failed. */ -ssize_t nghttp2_hd_huff_decode(uint8_t **dest_ptr, - const uint8_t *src, size_t srclen, - nghttp2_hd_side side); +ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, + nghttp2_buffer *dest, + const uint8_t *src, size_t srclen, int final); #endif /* NGHTTP2_HD_COMP_H */ diff --git a/lib/nghttp2_hd_huffman.c b/lib/nghttp2_hd_huffman.c index 1734cad3..9c47f0b5 100644 --- a/lib/nghttp2_hd_huffman.c +++ b/lib/nghttp2_hd_huffman.c @@ -114,58 +114,46 @@ ssize_t nghttp2_hd_huff_encode(uint8_t *dest, size_t destlen, return dest - dest_first; } -ssize_t nghttp2_hd_huff_decode(uint8_t **dest_ptr, - const uint8_t *src, size_t srclen, - nghttp2_hd_side side) +void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx, + nghttp2_hd_side side) { - size_t i, j, k; - const huff_decode_table_type *huff_decode_table; - uint8_t *dest = NULL; - size_t destlen = 0; - int rv; - int16_t state = 0; - const nghttp2_huff_decode *t = NULL; + if(side == NGHTTP2_HD_SIDE_REQUEST) { + ctx->huff_decode_table = req_huff_decode_table; + } else { + ctx->huff_decode_table = res_huff_decode_table; + } + ctx->state = 0; + ctx->accept = 1; +} +ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, + nghttp2_buffer *dest, + const uint8_t *src, size_t srclen, int final) +{ + size_t i, j; + int rv; /* We use the decoding algorithm described in http://graphics.ics.uci.edu/pub/Prefix.pdf */ - if(side == NGHTTP2_HD_SIDE_REQUEST) { - huff_decode_table = req_huff_decode_table; - } else { - huff_decode_table = res_huff_decode_table; - } - j = 0; for(i = 0; i < srclen; ++i) { uint8_t in = src[i] >> 4; - for(k = 0; k < 2; ++k) { - t = &huff_decode_table[state][in]; + for(j = 0; j < 2; ++j) { + const nghttp2_huff_decode *t = &ctx->huff_decode_table[ctx->state][in]; if(t->state == -1) { - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; + return NGHTTP2_ERR_HEADER_COMP; } if(t->flags & NGHTTP2_HUFF_SYM) { - if(destlen == j) { - size_t new_len = j == 0 ? 32 : j * 2; - uint8_t *new_dest = realloc(dest, new_len); - if(new_dest == NULL) { - rv = NGHTTP2_ERR_NOMEM; - goto fail; - } - dest = new_dest; - destlen = new_len; + rv = nghttp2_buffer_add_byte(dest, t->sym); + if(rv != 0) { + return rv; } - dest[j++] = t->sym; } - state = t->state; + ctx->state = t->state; + ctx->accept = (t->flags & NGHTTP2_HUFF_ACCEPTED) != 0; in = src[i] & 0xf; } } - if(srclen && (t->flags & NGHTTP2_HUFF_ACCEPTED) == 0) { - rv = NGHTTP2_ERR_HEADER_COMP; - goto fail; + if(final && !ctx->accept) { + return NGHTTP2_ERR_HEADER_COMP; } - *dest_ptr = dest; - return j; - fail: - free(dest); - return rv; + return i; } diff --git a/lib/nghttp2_hd_huffman.h b/lib/nghttp2_hd_huffman.h index 11a38ecb..48efc6e9 100644 --- a/lib/nghttp2_hd_huffman.h +++ b/lib/nghttp2_hd_huffman.h @@ -40,13 +40,27 @@ enum { } nghttp2_huff_decode_flag; typedef struct { + /* huffman decoding state, which is actually the node ID of internal + huffman tree */ int16_t state; + /* bitwise OR of zero or more of the nghttp2_huff_decode_flag */ uint8_t flags; + /* symbol if NGHTTP2_HUFF_SYM flag set */ uint8_t sym; } nghttp2_huff_decode; typedef nghttp2_huff_decode huff_decode_table_type[16]; +typedef struct { + const huff_decode_table_type *huff_decode_table; + /* Current huffman decoding state. We stripped leaf nodes, so the + value range is [0..255], inclusive. */ + uint8_t state; + /* nonzero if we can say that the decoding process succeeds at this + state */ + uint8_t accept; +} nghttp2_hd_huff_decode_context; + typedef struct { /* The number of bits in this code */ uint32_t nbits; diff --git a/lib/nghttp2_int.h b/lib/nghttp2_int.h index 09067d67..c1bd18b8 100644 --- a/lib/nghttp2_int.h +++ b/lib/nghttp2_int.h @@ -44,7 +44,8 @@ typedef int (*nghttp2_compar)(const void *lhs, const void *rhs); /* Internal error code. They must be in the range [-499, -100], inclusive. */ typedef enum { - NGHTTP2_ERR_CREDENTIAL_PENDING = -101 + NGHTTP2_ERR_CREDENTIAL_PENDING = -101, + NGHTTP2_ERR_BUFFER_ERROR = - 102 } nghttp2_internal_error; #endif /* NGHTTP2_INT_H */ diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 688f7b7f..0b16b226 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -1915,14 +1915,15 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, int call_header_cb) { ssize_t rv; - int final; + int inflate_flags; nghttp2_nv nv; for(;;) { + inflate_flags = 0; rv = nghttp2_hd_inflate_hd - (&session->hd_inflater, &nv, &final, + (&session->hd_inflater, &nv, &inflate_flags, session->iframe.buf + session->iframe.inflate_offset, - session->iframe.buflen - session->iframe.inflate_offset); + session->iframe.buflen - session->iframe.inflate_offset, 1); if(nghttp2_is_fatal(rv)) { return rv; } @@ -1942,16 +1943,16 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, return NGHTTP2_ERR_HEADER_COMP; } session->iframe.inflate_offset += rv; - if(final) { - break; - } - if(call_header_cb) { + if(call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) { rv = session_call_on_header(session, frame, &nv); /* This handles NGHTTP2_ERR_PAUSE as well */ if(rv != 0) { return rv; } } + if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + break; + } } nghttp2_hd_inflate_end_headers(&session->hd_inflater); if(call_header_cb) { diff --git a/src/inflatehd.c b/src/inflatehd.c index e8231e2d..4ac27c94 100644 --- a/src/inflatehd.c +++ b/src/inflatehd.c @@ -94,7 +94,7 @@ static int inflate_hd(json_t *obj, nghttp2_hd_context *inflater, int seq) size_t buflen; ssize_t rv; nghttp2_nv nv; - int final; + int inflate_flags; wire = json_object_get(obj, "wire"); if(wire == NULL) { @@ -131,18 +131,21 @@ static int inflate_hd(json_t *obj, nghttp2_hd_context *inflater, int seq) p = buf; for(;;) { - rv = nghttp2_hd_inflate_hd(inflater, &nv, &final, p, buflen); + inflate_flags = 0; + rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, p, buflen, 1); if(rv < 0) { fprintf(stderr, "inflate failed with error code %zd at %d\n", rv, seq); exit(EXIT_FAILURE); } p += rv; buflen -= rv; - if(final) { + if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + json_array_append_new(headers, dump_header(nv.name, nv.namelen, + nv.value, nv.valuelen)); + } + if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { break; } - json_array_append_new(headers, dump_header(nv.name, nv.namelen, - nv.value, nv.valuelen)); } assert(buflen == 0); nghttp2_hd_inflate_end_headers(inflater); diff --git a/tests/main.c b/tests/main.c index 2d40f94e..6ddd70dc 100644 --- a/tests/main.c +++ b/tests/main.c @@ -73,7 +73,6 @@ int main(int argc, char* argv[]) !CU_add_test(pSuite, "map_each_free", test_nghttp2_map_each_free) || !CU_add_test(pSuite, "queue", test_nghttp2_queue) || !CU_add_test(pSuite, "buffer", test_nghttp2_buffer) || - !CU_add_test(pSuite, "buffer_reader", test_nghttp2_buffer_reader) || !CU_add_test(pSuite, "npn", test_nghttp2_npn) || !CU_add_test(pSuite, "session_recv", test_nghttp2_session_recv) || !CU_add_test(pSuite, "session_recv_invalid_stream_id", diff --git a/tests/nghttp2_buffer_test.c b/tests/nghttp2_buffer_test.c index 4dec94ed..a10b1c8f 100644 --- a/tests/nghttp2_buffer_test.c +++ b/tests/nghttp2_buffer_test.c @@ -29,96 +29,28 @@ #include #include "nghttp2_buffer.h" -#include "nghttp2_net.h" void test_nghttp2_buffer(void) { nghttp2_buffer buffer; - uint8_t out[1024]; - nghttp2_buffer_init(&buffer, 8); - CU_ASSERT(0 == nghttp2_buffer_length(&buffer)); - CU_ASSERT(0 == nghttp2_buffer_avail(&buffer)); - CU_ASSERT(NULL == nghttp2_buffer_get(&buffer)); - CU_ASSERT(0 == nghttp2_buffer_alloc(&buffer)); - CU_ASSERT(8 == nghttp2_buffer_avail(&buffer)); - CU_ASSERT(NULL != nghttp2_buffer_get(&buffer)); - memcpy(nghttp2_buffer_get(&buffer), "012", 3); - nghttp2_buffer_advance(&buffer, 3); - CU_ASSERT(3 == nghttp2_buffer_length(&buffer)); + nghttp2_buffer_init(&buffer, 16); - CU_ASSERT(5 == nghttp2_buffer_avail(&buffer)); - memcpy(nghttp2_buffer_get(&buffer), "34567", 5); - nghttp2_buffer_advance(&buffer, 5); - CU_ASSERT(8 == nghttp2_buffer_length(&buffer)); + CU_ASSERT(0 == buffer.len); - CU_ASSERT(0 == nghttp2_buffer_avail(&buffer)); - CU_ASSERT(0 == nghttp2_buffer_alloc(&buffer)); - memcpy(nghttp2_buffer_get(&buffer), "89ABCDE", 7); - nghttp2_buffer_advance(&buffer, 7); - CU_ASSERT(15 == nghttp2_buffer_length(&buffer)); + CU_ASSERT(0 == nghttp2_buffer_add(&buffer, (const uint8_t*)"foo", 3)); + CU_ASSERT(3 == buffer.len); - CU_ASSERT(1 == nghttp2_buffer_avail(&buffer)); + CU_ASSERT(0 == nghttp2_buffer_add_byte(&buffer, '.')); + CU_ASSERT(4 == buffer.len); - nghttp2_buffer_serialize(&buffer, out); - CU_ASSERT(0 == memcmp("0123456789ABCDE", out, 15)); + CU_ASSERT(0 == nghttp2_buffer_add(&buffer, + (const uint8_t*)"012345678901", 12)); + CU_ASSERT(16 == buffer.len); - nghttp2_buffer_reset(&buffer); - - CU_ASSERT(0 == nghttp2_buffer_length(&buffer)); - CU_ASSERT(0 == nghttp2_buffer_avail(&buffer)); - CU_ASSERT(NULL == nghttp2_buffer_get(&buffer)); - CU_ASSERT(0 == nghttp2_buffer_alloc(&buffer)); - - CU_ASSERT(8 == nghttp2_buffer_avail(&buffer)); - memcpy(nghttp2_buffer_get(&buffer), "Hello", 5); - nghttp2_buffer_advance(&buffer, 5); - CU_ASSERT(5 == nghttp2_buffer_length(&buffer)); - - nghttp2_buffer_serialize(&buffer, out); - CU_ASSERT(0 == memcmp("Hello", out, 5)); - - nghttp2_buffer_free(&buffer); -} - -void test_nghttp2_buffer_reader(void) -{ - nghttp2_buffer buffer; - nghttp2_buffer_reader reader; - uint16_t val16; - uint32_t val32; - uint8_t temp[256]; - - nghttp2_buffer_init(&buffer, 3); - nghttp2_buffer_write(&buffer, (const uint8_t*)"hello", 5); - val16 = htons(678); - nghttp2_buffer_write(&buffer, (const uint8_t*)&val16, sizeof(uint16_t)); - val32 = htonl(1000000007); - nghttp2_buffer_write(&buffer, (const uint8_t*)&val32, sizeof(uint32_t)); - nghttp2_buffer_write(&buffer, (const uint8_t*)"world", 5); - - CU_ASSERT(5+2+4+5 == nghttp2_buffer_length(&buffer)); - - nghttp2_buffer_reader_init(&reader, &buffer); - - nghttp2_buffer_reader_data(&reader, temp, 5); - CU_ASSERT(memcmp(temp, "hello", 5) == 0); - CU_ASSERT(678 == nghttp2_buffer_reader_uint16(&reader)); - CU_ASSERT(1000000007 == nghttp2_buffer_reader_uint32(&reader)); - CU_ASSERT('w' == nghttp2_buffer_reader_uint8(&reader)); - CU_ASSERT('o' == nghttp2_buffer_reader_uint8(&reader)); - CU_ASSERT('r' == nghttp2_buffer_reader_uint8(&reader)); - CU_ASSERT('l' == nghttp2_buffer_reader_uint8(&reader)); - CU_ASSERT('d' == nghttp2_buffer_reader_uint8(&reader)); - - nghttp2_buffer_reader_init(&reader, &buffer); - nghttp2_buffer_reader_advance(&reader, 5); - CU_ASSERT(678 == nghttp2_buffer_reader_uint16(&reader)); - nghttp2_buffer_reader_advance(&reader, 1); - nghttp2_buffer_reader_advance(&reader, 1); - nghttp2_buffer_reader_advance(&reader, 1); - nghttp2_buffer_reader_advance(&reader, 1); - CU_ASSERT('w' == nghttp2_buffer_reader_uint8(&reader)); + CU_ASSERT(NGHTTP2_ERR_BUFFER_ERROR == nghttp2_buffer_add_byte(&buffer, '.')); + CU_ASSERT(NGHTTP2_ERR_BUFFER_ERROR == + nghttp2_buffer_add(&buffer, (const uint8_t*)".", 1)); nghttp2_buffer_free(&buffer); } diff --git a/tests/nghttp2_hd_test.c b/tests/nghttp2_hd_test.c index 22dd5615..191448f4 100644 --- a/tests/nghttp2_hd_test.c +++ b/tests/nghttp2_hd_test.c @@ -185,28 +185,29 @@ void test_nghttp2_hd_deflate_same_indexed_repr(void) void test_nghttp2_hd_deflate_common_header_eviction(void) { nghttp2_hd_context deflater, inflater; - nghttp2_nv nva[] = {MAKE_NV(":scheme", "http"), - MAKE_NV("", "")}; + nghttp2_nv nva[] = {MAKE_NV("h1", ""), + MAKE_NV("h2", "")}; uint8_t *buf = NULL; size_t buflen = 0; ssize_t blocklen; /* Default header table capacity is 4096. Adding 2 byte header name and 4060 byte value, which is 4094 bytes including overhead, to the table evicts first entry. */ - uint8_t value[4060]; + uint8_t value[3038]; nva_out out; + size_t i; nva_out_init(&out); memset(value, '0', sizeof(value)); - nva[1].name = (uint8_t*)"hd"; - nva[1].namelen = strlen((const char*)nva[1].name); - nva[1].value = value; - nva[1].valuelen = sizeof(value); + for(i = 0; i < 2; ++i) { + nva[i].value = value; + nva[i].valuelen = sizeof(value); + } nghttp2_hd_deflate_init(&deflater, NGHTTP2_HD_SIDE_REQUEST); nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_REQUEST); - /* First emit ":scheme: http" to put it in the reference set (index + /* First emit "h1: ..." to put it in the reference set (index = 0). */ blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, nva, 1); CU_ASSERT(blocklen > 0); @@ -218,11 +219,11 @@ void test_nghttp2_hd_deflate_common_header_eviction(void) nva_out_reset(&out); - /* Encode with large header */ + /* Encode with second header */ blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, nva, 2); CU_ASSERT(blocklen > 0); - /* Check common header :scheme: http, which is removed from the + /* Check common header "h1: ...:, which is removed from the header table because of eviction, is still emitted by the inflater */ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, buf, blocklen)); diff --git a/tests/nghttp2_test_helper.c b/tests/nghttp2_test_helper.c index 61105f68..7964a663 100644 --- a/tests/nghttp2_test_helper.c +++ b/tests/nghttp2_test_helper.c @@ -120,20 +120,23 @@ ssize_t inflate_hd(nghttp2_hd_context *inflater, nva_out *out, { ssize_t rv; nghttp2_nv nv; - int final; + int inflate_flags; size_t initial = buflen; for(;;) { - rv = nghttp2_hd_inflate_hd(inflater, &nv, &final, buf, buflen); + inflate_flags = 0; + rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, buf, buflen, 1); if(rv < 0) { return rv; } buf += rv; buflen -= rv; - if(final) { + if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + add_out(out, &nv); + } + if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { break; } - add_out(out, &nv); } nghttp2_hd_inflate_end_headers(inflater); return initial - buflen;