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.
This commit is contained in:
Tatsuhiro Tsujikawa 2014-01-25 18:24:15 +09:00
parent f8a446fbeb
commit 8317559090
13 changed files with 785 additions and 756 deletions

View File

@ -1,7 +1,7 @@
/* /*
* nghttp2 - HTTP/2.0 C Library * 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 * Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the * a copy of this software and associated documentation files (the
@ -27,217 +27,68 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "nghttp2_net.h"
#include "nghttp2_helper.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->buf = NULL;
buffer->root.next = NULL;
buffer->current = &buffer->root;
buffer->capacity = chunk_capacity;
buffer->len = 0; buffer->len = 0;
/* buffer->capacity = 0;
* Set last_offset to maximum so that first append adds new buffer buffer->max_capacity = max_capacity;
* buffer.
*/
buffer->last_offset = buffer->capacity;
} }
void nghttp2_buffer_free(nghttp2_buffer *buffer) void nghttp2_buffer_free(nghttp2_buffer *buffer)
{ {
nghttp2_buffer_chunk *p = buffer->root.next; free(buffer->buf);
while(p) {
nghttp2_buffer_chunk *next = p->next;
free(p->data);
free(p);
p = next;
}
} }
int nghttp2_buffer_alloc(nghttp2_buffer *buffer) int nghttp2_buffer_reserve(nghttp2_buffer *buffer, size_t len)
{ {
if(buffer->current->next == NULL) { if(len > buffer->max_capacity) {
nghttp2_buffer_chunk *chunk; return NGHTTP2_ERR_BUFFER_ERROR;
uint8_t *buf; }
chunk = malloc(sizeof(nghttp2_buffer_chunk)); if(buffer->capacity < len) {
if(chunk == NULL) { 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; return NGHTTP2_ERR_NOMEM;
} }
buf = malloc(buffer->capacity); buffer->buf = new_buf;
if(buf == NULL) { buffer->capacity = new_cap;
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;
}
buffer->len += buffer->capacity-buffer->last_offset;
buffer->last_offset = 0;
return 0; return 0;
} }
uint8_t* nghttp2_buffer_get(nghttp2_buffer *buffer) int nghttp2_buffer_add(nghttp2_buffer *buffer,
{ const uint8_t *data, size_t len)
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 rv; int rv;
while(len) { rv = nghttp2_buffer_reserve(buffer, buffer->len + len);
size_t writelen; if(rv != 0) {
if(nghttp2_buffer_avail(buffer) == 0) {
if((rv = nghttp2_buffer_alloc(buffer)) != 0) {
return rv; return rv;
} }
} memcpy(buffer->buf + buffer->len, data, len);
writelen = nghttp2_min(nghttp2_buffer_avail(buffer), len); buffer->len += len;
memcpy(nghttp2_buffer_get(buffer), data, writelen);
data += writelen;
len -= writelen;
nghttp2_buffer_advance(buffer, writelen);
}
return 0; return 0;
} }
size_t nghttp2_buffer_length(nghttp2_buffer *buffer) int nghttp2_buffer_add_byte(nghttp2_buffer *buffer, uint8_t b)
{ {
return buffer->len; int rv;
} rv = nghttp2_buffer_reserve(buffer, buffer->len + 1);
if(rv != 0) {
size_t nghttp2_buffer_capacity(nghttp2_buffer *buffer) return rv;
{
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;
} }
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->len = 0;
buffer->last_offset = buffer->capacity; buffer->capacity = 0;
}
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;
}
}
} }

View File

@ -1,7 +1,7 @@
/* /*
* nghttp2 - HTTP/2.0 C Library * 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 * Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the * a copy of this software and associated documentation files (the
@ -31,140 +31,73 @@
#include <nghttp2/nghttp2.h> #include <nghttp2/nghttp2.h>
typedef struct nghttp2_buffer_chunk { #include "nghttp2_int.h"
uint8_t *data;
struct nghttp2_buffer_chunk *next;
} nghttp2_buffer_chunk;
/* /*
* List of fixed sized chunks * Byte array buffer
*/ */
typedef struct { typedef struct {
/* Capacity of each chunk buffer */ uint8_t *buf;
/* Capacity of this buffer */
size_t capacity; size_t capacity;
/* Root of list of chunk buffers. The root is dummy and its data /* How many bytes are written to buf. len <= capacity must hold. */
member is always NULL. */
nghttp2_buffer_chunk root;
/* Points to the current chunk to write */
nghttp2_buffer_chunk *current;
/* Total length of this buffer */
size_t len; size_t len;
/* Offset of last chunk buffer */ /* Maximum capacity this buffer can grow up */
size_t last_offset; size_t max_capacity;
} nghttp2_buffer; } nghttp2_buffer;
/* void nghttp2_buffer_init(nghttp2_buffer *buffer, size_t max_capacity);
* 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_free(nghttp2_buffer *buffer); 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 * Expands capacity so that it can contain at least |len| bytes of
* position of the |buffer|. The new chunk buffer will be allocated on * data. If buffer->capacity >= len, no action is taken. If len >
* the course of the write and the current position is updated. If * buffer->max_capacity, NGHTTP2_ERR_BUFFER_ERROR is returned.
* this function succeeds, the total length of the |buffer| will be
* increased by |len|.
* *
* This function returns 0 if it succeeds, or one of the following * This function returns 0 if it succeeds, or one of the following
* negative error codes: * negative error codes:
* *
* NGHTTP2_ERR_BUFFER_ERROR
* The |len| is strictly larger than buffer->max_capacity
* NGHTTP2_ERR_NOMEM * NGHTTP2_ERR_NOMEM
* Out of memory. * Out of memory
*/ */
int nghttp2_buffer_write(nghttp2_buffer *buffer, const uint8_t *data, int nghttp2_buffer_reserve(nghttp2_buffer *buffer, size_t len);
size_t len);
/* /*
* Allocate new chunk buffer. This will increase total length of * Appends the |data| with |len| bytes to the buffer. The data is
* buffer (returned by nghttp2_buffer_length) by capacity-last_offset. * copied. The |buffer| will be expanded as needed.
* It means untouched buffer is assumued to be written.
* *
* This function returns 0 if it succeeds, or one of the following * 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 * NGHTTP2_ERR_NOMEM
* Out of memory. * Out of memory
*/ */
int nghttp2_buffer_alloc(nghttp2_buffer *buffer); int nghttp2_buffer_add(nghttp2_buffer *buffer,
const uint8_t *data, size_t len);
/* 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);
/* /*
* 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 { int nghttp2_buffer_add_byte(nghttp2_buffer *buffer, uint8_t b);
/* 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;
/* /*
* 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, void nghttp2_buffer_release(nghttp2_buffer *buffer);
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);
#endif /* NGHTTP2_BUFFER_H */ #endif /* NGHTTP2_BUFFER_H */

View File

@ -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) int nghttp2_hd_inflate_init(nghttp2_hd_context *inflater, nghttp2_hd_side side)
{ {
return nghttp2_hd_context_init(inflater, NGHTTP2_HD_ROLE_INFLATE, side, int rv;
rv = nghttp2_hd_context_init(inflater, NGHTTP2_HD_ROLE_INFLATE, side,
NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE); 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) 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) void nghttp2_hd_inflate_free(nghttp2_hd_context *inflater)
{ {
hd_inflate_keep_free(inflater); hd_inflate_keep_free(inflater);
nghttp2_buffer_free(&inflater->namebuf);
nghttp2_buffer_free(&inflater->valuebuf);
nghttp2_hd_context_free(inflater); 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 * region from |in|. The decoded integer must be strictly less than 1
* << 16. * << 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 * This function returns the next byte of read byte. This function
* stores the decoded integer in |*res| if it succeeds, or stores -1 * stores the decoded integer in |*res| if it succeed, including
* in |*res|, indicating decoding error. * partial decoding, or stores -1 in |*res|, indicating decoding
* error.
*/ */
static uint8_t* decode_length(ssize_t *res, uint8_t *in, uint8_t *last, static uint8_t* decode_length(ssize_t *res, int *final, ssize_t initial,
int prefix) uint8_t *in, uint8_t *last, int prefix)
{ {
int k = (1 << prefix) - 1, r; int k = (1 << prefix) - 1, r;
if(in == last) { ssize_t n = initial;
*res = -1; *final = 0;
return in; if(n == 0) {
}
if((*in & k) == k) { if((*in & k) == k) {
*res = k; n = k;
} else { } else {
*res = (*in) & k; *res = (*in) & k;
*final = 1;
return in + 1; return in + 1;
} }
++in; }
if(++in == last) {
*res = n;
return in;
}
for(r = 0; in != last; ++in, r += 7) { for(r = 0; in != last; ++in, r += 7) {
*res += (*in & 0x7f) << r; n += (*in & 0x7f) << r;
if(*res >= (1 << 16)) { if(n >= (1 << 16)) {
*res = -1; *res = -1;
return in + 1; return in + 1;
} }
@ -523,12 +550,17 @@ static uint8_t* decode_length(ssize_t *res, uint8_t *in, uint8_t *last,
break; break;
} }
} }
if(in == last || *in & (1 << 7)) { if(in == last) {
*res = n;
return in;
}
if(*in & (1 << 7)) {
*res = -1; *res = -1;
return NULL;
} else {
return in + 1; return in + 1;
} }
*res = n;
*final = 1;
return in + 1;
} }
static int emit_indexed0(uint8_t **buf_ptr, size_t *buflen_ptr, 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; 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, nghttp2_hd_entry* nghttp2_hd_table_get(nghttp2_hd_context *context,
size_t index) size_t index)
{ {
@ -1128,260 +1165,486 @@ ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_context *deflater,
return rv; return rv;
} }
ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, static void hd_inflate_set_huffman_encoded(nghttp2_hd_context *inflater,
nghttp2_nv *nv_out, int *final, const uint8_t *in)
uint8_t *in, size_t inlen)
{ {
int rv = 0; inflater->huffman_encoded = (*in & (1 << 7)) != 0;
uint8_t *first = in; }
uint8_t *last = in + inlen;
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; return NGHTTP2_ERR_HEADER_COMP;
} }
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;
}
*final = 0; /*
hd_inflate_keep_free(inflater); * 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;
}
for(; in != last;) { /*
uint8_t c = *in; * Reads |inflater->left| bytes from the range [in, last) and copies
if(c & 0x80u) { * them into the |buffer|.
/* Indexed Header Repr */ *
ssize_t index; * This function returns the number of bytes read if it succeeds, or
nghttp2_hd_entry *ent; * one of the following negative error codes:
in = decode_length(&index, in, last, 7); *
DEBUGF(fprintf(stderr, "Indexed repr index=%zd\n", index)); * NGHTTP2_ERR_NOMEM
if(index < 0) { * Out of memory
DEBUGF(fprintf(stderr, "Index out of range index=%zd\n", index)); * NGHTTP2_ERR_HEADER_COMP
rv = NGHTTP2_ERR_HEADER_COMP; * Header decompression failed
goto fail; */
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(index == 0) { if(rv != 0) {
DEBUGF(fprintf(stderr, "Clearing reference set\n")); return rv;
clear_refset(inflater);
continue;
} }
--index; inflater->left -= len;
if(!check_index_range(inflater, index)) { return len;
DEBUGF(fprintf(stderr, "Index out of range index=%zd\n", index)); }
rv = NGHTTP2_ERR_HEADER_COMP;
goto fail; /*
} * Finalize indexed header representation reception. If header is
ent = nghttp2_hd_table_get(inflater, index); * emitted, |*nv_out| is filled with that value and 0 is returned. If
if(index >= (ssize_t)inflater->hd_table.len) { * 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; nghttp2_hd_entry *new_ent;
new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL,
&ent->nv, NGHTTP2_HD_FLAG_NONE); &ent->nv, NGHTTP2_HD_FLAG_NONE);
if(!new_ent) { if(!new_ent) {
rv = NGHTTP2_ERR_HEADER_COMP; return NGHTTP2_ERR_NOMEM;
goto fail;
} }
/* new_ent->ref == 0 may be hold but emit_indexed_header /* new_ent->ref == 0 may be hold */
tracks new_ent, so there is no leak. */
emit_indexed_header(inflater, nv_out, new_ent); emit_indexed_header(inflater, nv_out, new_ent);
inflater->ent_keep = new_ent; inflater->ent_keep = new_ent;
return in - first; return 0;
} else { }
ent->flags ^= NGHTTP2_HD_FLAG_REFSET; ent->flags ^= NGHTTP2_HD_FLAG_REFSET;
if(ent->flags & NGHTTP2_HD_FLAG_REFSET) { if(ent->flags & NGHTTP2_HD_FLAG_REFSET) {
emit_indexed_header(inflater, nv_out, ent); emit_indexed_header(inflater, nv_out, ent);
return in - first; return 0;
} else { }
DEBUGF(fprintf(stderr, "Toggle off item:\n")); DEBUGF(fprintf(stderr, "Toggle off item:\n"));
DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr)); DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr));
DEBUGF(fprintf(stderr, ": ")); DEBUGF(fprintf(stderr, ": "));
DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr)); DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr));
DEBUGF(fprintf(stderr, "\n")); DEBUGF(fprintf(stderr, "\n"));
} return 1;
} }
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")); * Finalize literal header representation - new name- reception. If
free(decoded_huffman_name); * header is emitted, |*nv_out| is filled with that value and 0 is
rv = NGHTTP2_ERR_HEADER_COMP; * returned.
goto fail; *
* 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;
} }
value_huffman = *in & (1 << 7); return NGHTTP2_ERR_NOMEM;
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); emit_newname_header(inflater, nv_out, &nv);
inflater->name_keep = decoded_huffman_name; inflater->name_keep = inflater->namebuf.buf;
inflater->value_keep = decoded_huffman_value; nghttp2_buffer_release(&inflater->namebuf);
return in - first; inflater->value_keep = inflater->valuebuf.buf;
} else { nghttp2_buffer_release(&inflater->valuebuf);
nghttp2_hd_entry *new_ent; return 0;
uint8_t ent_flags = NGHTTP2_HD_FLAG_NAME_ALLOC | }
NGHTTP2_HD_FLAG_VALUE_ALLOC;
if(name_huffman) { /*
ent_flags |= NGHTTP2_HD_FLAG_NAME_GIFT; * Finalize literal header representation - indexed name-
} * reception. If header is emitted, |*nv_out| is filled with that
if(value_huffman) { * value and 0 is returned.
ent_flags |= NGHTTP2_HD_FLAG_VALUE_GIFT; *
} * This function returns 0 if it succeeds, or one of the following
new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv, * negative error codes:
ent_flags); *
if(new_ent) { * NGHTTP2_ERR_NOMEM
emit_indexed_header(inflater, nv_out, new_ent); * Out of memory
inflater->ent_keep = new_ent; */
return in - first; static int hd_inflate_commit_indname(nghttp2_hd_context *inflater,
} else { nghttp2_nv *nv_out)
free(decoded_huffman_name); {
free(decoded_huffman_value); if(inflater->index_required) {
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_nv nv;
nghttp2_hd_entry *new_ent; nghttp2_hd_entry *new_ent;
uint8_t ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC; uint8_t ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC |
if(value_huffman) { NGHTTP2_HD_FLAG_VALUE_GIFT;
ent_flags |= NGHTTP2_HD_FLAG_VALUE_GIFT;
} if(inflater->index < inflater->hd_table.len) {
++ent->ref;
nv.name = ent->nv.name;
if((size_t)index < inflater->hd_table.len) {
ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC; ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC;
} }
nv.namelen = ent->nv.namelen; ++inflater->ent_name->ref;
nv.value = value; nv.name = inflater->ent_name->nv.name;
nv.valuelen = valuelen; 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, new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv,
ent_flags); ent_flags);
if(--ent->ref == 0) { if(--inflater->ent_name->ref == 0) {
nghttp2_hd_entry_free(ent); nghttp2_hd_entry_free(inflater->ent_name);
free(ent); free(inflater->ent_name);
} }
inflater->ent_name = NULL;
if(new_ent) { if(new_ent) {
nghttp2_buffer_release(&inflater->valuebuf);
emit_indexed_header(inflater, nv_out, new_ent); emit_indexed_header(inflater, nv_out, new_ent);
inflater->ent_keep = new_ent; inflater->ent_keep = new_ent;
return in - first; return 0;
} else {
free(decoded_huffman_value);
rv = NGHTTP2_ERR_HEADER_COMP;
} }
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) { if(rv != 0) {
goto fail; 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;
} }
} }
assert(in == last);
if(in_final) {
for(; inflater->end_headers_index < inflater->hd_table.len; for(; inflater->end_headers_index < inflater->hd_table.len;
++inflater->end_headers_index) { ++inflater->end_headers_index) {
nghttp2_hd_entry *ent; nghttp2_hd_entry *ent;
@ -1391,11 +1654,13 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater,
if((ent->flags & NGHTTP2_HD_FLAG_REFSET) && if((ent->flags & NGHTTP2_HD_FLAG_REFSET) &&
(ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) { (ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) {
emit_indexed_header(inflater, nv_out, ent); emit_indexed_header(inflater, nv_out, ent);
*inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
return in - first; return in - first;
} }
ent->flags &= ~NGHTTP2_HD_FLAG_EMIT; ent->flags &= ~NGHTTP2_HD_FLAG_EMIT;
} }
*final = 1; *inflate_flags |= NGHTTP2_HD_INFLATE_FINAL;
}
return in - first; return in - first;
fail: fail:
inflater->bad = 1; inflater->bad = 1;

View File

@ -31,9 +31,15 @@
#include <nghttp2/nghttp2.h> #include <nghttp2/nghttp2.h>
#include "nghttp2_hd_huffman.h"
#include "nghttp2_buffer.h"
#define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE (1 << 12) #define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE (1 << 12)
#define NGHTTP2_HD_ENTRY_OVERHEAD 32 #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 /* Default size of maximum table buffer size for encoder. Even if
remote decoder notifies larger buffer size for its decoding, remote decoder notifies larger buffer size for its decoding,
encoder only uses the memory up to this value. */ encoder only uses the memory up to this value. */
@ -90,6 +96,26 @@ typedef struct {
size_t len; size_t len;
} nghttp2_hd_ringbuf; } 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 { typedef struct {
/* dynamic header table */ /* dynamic header table */
nghttp2_hd_ringbuf hd_table; nghttp2_hd_ringbuf hd_table;
@ -139,6 +165,17 @@ typedef struct {
/* Set to this nonzero to clear reference set on each deflation each /* Set to this nonzero to clear reference set on each deflation each
time. */ time. */
uint8_t no_refset; 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; } nghttp2_hd_context;
/* /*
@ -270,26 +307,34 @@ ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_context *deflater,
size_t nv_offset, size_t nv_offset,
nghttp2_nv *nva, size_t nvlen); 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 * Inflates name/value block stored in |in| with length |inlen|. This
* function performs decompression. For each successful emission of * function performs decompression. For each successful emission of
* header name/value pair, name/value pair is assigned to the * header name/value pair, NGHTTP2_HD_INFLATE_EMIT is set in
* |nv_out| and the function returns. The caller must not free * |*inflate_flags| and name/value pair is assigned to the |nv_out|
* the members of |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. * |in|. The caller must retain the |in| while the |nv_out| is used.
* *
* The application should call this function repeatedly until the * The application should call this function repeatedly until the
* |*final| is nonzero and return value is non-negative. This means * |(*inflate_flags) & NGHTTP2_HD_INFLATE_FINAL| is nonzero and return
* the all input values are processed successfully. If |*final| is * value is non-negative. This means the all input values are
* nonzero, no header name/value is emitted. Then the application must * processed successfully. Then the application must call
* call `nghttp2_hd_inflate_end_headers()` to prepare for the next * `nghttp2_hd_inflate_end_headers()` to prepare for the next header
* header block input. * block input.
* *
* Currently, the whole compressed header block must be given in the * The caller can feed complete compressed header block. It also can
* |in| and |inlen|. Otherwise, it may lead to NGHTTP2_ERR_HEADER_COMP * feed it in several chunks. The caller must set |in_final| to
* error. * nonzero if the given input is the last block of the compressed
* header.
* *
* This function returns the number of bytes processed if it succeeds, * This function returns the number of bytes processed if it succeeds,
* or one of the following negative error codes: * 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. * Inflation process has failed.
*/ */
ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater,
nghttp2_nv *nv_out, int *final, nghttp2_nv *nv_out, int *inflate_flags,
uint8_t *in, size_t inlen); uint8_t *in, size_t inlen, int in_final);
/* /*
* Signals the end of decompression for one header block. * 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, int inc_indexing,
nghttp2_hd_side side); 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 */ /* For unittesting purpose */
nghttp2_hd_entry* nghttp2_hd_table_get(nghttp2_hd_context *context, nghttp2_hd_entry* nghttp2_hd_table_get(nghttp2_hd_context *context,
size_t index); 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, const uint8_t *src, size_t srclen,
nghttp2_hd_side side); 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 * Decodes the given data |src| with length |srclen|. The |ctx| must
* allocates memory to store the result and assigns the its pointer to * be initialized by nghttp2_hd_huff_decode_context_init(). The result
* |*dest_ptr| on success. The caller is responsible to release the * will be added to |dest|. This function may expand |dest| as
* memory pointed by |*dest_ptr| if this function succeeds. If |side| * needed. The caller is responsible to release the memory of |dest|
* is NGHTTP2_HD_SIDE_REQUEST, the request huffman code table is * by calling nghttp2_buffer_free().
* used. Otherwise, the response code table is used.
* *
* This function returns the number of written bytes. This return * The caller must set the |final| to nonzero if the given input is
* value is exactly the same with the return value of * the final block.
* nghttp2_hd_huff_decode_count() if it is given with the same |src|, *
* |srclen|, and |side|. * This function returns the number of read bytes from the |in|.
* *
* If this function fails, it returns one of the following negative * If this function fails, it returns one of the following negative
* return codes: * return codes:
* *
* NGHTTP2_ERR_NOMEM * NGHTTP2_ERR_NOMEM
* Out of memory. * Out of memory.
* NGHTTP2_ERR_BUFFER_ERROR
* Maximum buffer capacity size exceeded.
* NGHTTP2_ERR_HEADER_COMP * NGHTTP2_ERR_HEADER_COMP
* Decoding process has failed. * Decoding process has failed.
*/ */
ssize_t nghttp2_hd_huff_decode(uint8_t **dest_ptr, ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
const uint8_t *src, size_t srclen, nghttp2_buffer *dest,
nghttp2_hd_side side); const uint8_t *src, size_t srclen, int final);
#endif /* NGHTTP2_HD_COMP_H */ #endif /* NGHTTP2_HD_COMP_H */

View File

@ -114,58 +114,46 @@ ssize_t nghttp2_hd_huff_encode(uint8_t *dest, size_t destlen,
return dest - dest_first; return dest - dest_first;
} }
ssize_t nghttp2_hd_huff_decode(uint8_t **dest_ptr, void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx,
const uint8_t *src, size_t srclen,
nghttp2_hd_side side) nghttp2_hd_side side)
{ {
size_t i, j, k; if(side == NGHTTP2_HD_SIDE_REQUEST) {
const huff_decode_table_type *huff_decode_table; ctx->huff_decode_table = req_huff_decode_table;
uint8_t *dest = NULL; } else {
size_t destlen = 0; ctx->huff_decode_table = res_huff_decode_table;
int rv; }
int16_t state = 0; ctx->state = 0;
const nghttp2_huff_decode *t = NULL; 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 /* We use the decoding algorithm described in
http://graphics.ics.uci.edu/pub/Prefix.pdf */ 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) { for(i = 0; i < srclen; ++i) {
uint8_t in = src[i] >> 4; uint8_t in = src[i] >> 4;
for(k = 0; k < 2; ++k) { for(j = 0; j < 2; ++j) {
t = &huff_decode_table[state][in]; const nghttp2_huff_decode *t = &ctx->huff_decode_table[ctx->state][in];
if(t->state == -1) { if(t->state == -1) {
rv = NGHTTP2_ERR_HEADER_COMP; return NGHTTP2_ERR_HEADER_COMP;
goto fail;
} }
if(t->flags & NGHTTP2_HUFF_SYM) { if(t->flags & NGHTTP2_HUFF_SYM) {
if(destlen == j) { rv = nghttp2_buffer_add_byte(dest, t->sym);
size_t new_len = j == 0 ? 32 : j * 2; if(rv != 0) {
uint8_t *new_dest = realloc(dest, new_len); return rv;
if(new_dest == NULL) {
rv = NGHTTP2_ERR_NOMEM;
goto fail;
} }
dest = new_dest;
destlen = new_len;
} }
dest[j++] = t->sym; ctx->state = t->state;
} ctx->accept = (t->flags & NGHTTP2_HUFF_ACCEPTED) != 0;
state = t->state;
in = src[i] & 0xf; in = src[i] & 0xf;
} }
} }
if(srclen && (t->flags & NGHTTP2_HUFF_ACCEPTED) == 0) { if(final && !ctx->accept) {
rv = NGHTTP2_ERR_HEADER_COMP; return NGHTTP2_ERR_HEADER_COMP;
goto fail;
} }
*dest_ptr = dest; return i;
return j;
fail:
free(dest);
return rv;
} }

View File

@ -40,13 +40,27 @@ enum {
} nghttp2_huff_decode_flag; } nghttp2_huff_decode_flag;
typedef struct { typedef struct {
/* huffman decoding state, which is actually the node ID of internal
huffman tree */
int16_t state; int16_t state;
/* bitwise OR of zero or more of the nghttp2_huff_decode_flag */
uint8_t flags; uint8_t flags;
/* symbol if NGHTTP2_HUFF_SYM flag set */
uint8_t sym; uint8_t sym;
} nghttp2_huff_decode; } nghttp2_huff_decode;
typedef nghttp2_huff_decode huff_decode_table_type[16]; 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 { typedef struct {
/* The number of bits in this code */ /* The number of bits in this code */
uint32_t nbits; uint32_t nbits;

View File

@ -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], /* Internal error code. They must be in the range [-499, -100],
inclusive. */ inclusive. */
typedef enum { typedef enum {
NGHTTP2_ERR_CREDENTIAL_PENDING = -101 NGHTTP2_ERR_CREDENTIAL_PENDING = -101,
NGHTTP2_ERR_BUFFER_ERROR = - 102
} nghttp2_internal_error; } nghttp2_internal_error;
#endif /* NGHTTP2_INT_H */ #endif /* NGHTTP2_INT_H */

View File

@ -1915,14 +1915,15 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
int call_header_cb) int call_header_cb)
{ {
ssize_t rv; ssize_t rv;
int final; int inflate_flags;
nghttp2_nv nv; nghttp2_nv nv;
for(;;) { for(;;) {
inflate_flags = 0;
rv = nghttp2_hd_inflate_hd 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.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)) { if(nghttp2_is_fatal(rv)) {
return rv; return rv;
} }
@ -1942,16 +1943,16 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
return NGHTTP2_ERR_HEADER_COMP; return NGHTTP2_ERR_HEADER_COMP;
} }
session->iframe.inflate_offset += rv; session->iframe.inflate_offset += rv;
if(final) { if(call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) {
break;
}
if(call_header_cb) {
rv = session_call_on_header(session, frame, &nv); rv = session_call_on_header(session, frame, &nv);
/* This handles NGHTTP2_ERR_PAUSE as well */ /* This handles NGHTTP2_ERR_PAUSE as well */
if(rv != 0) { if(rv != 0) {
return rv; return rv;
} }
} }
if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
break;
}
} }
nghttp2_hd_inflate_end_headers(&session->hd_inflater); nghttp2_hd_inflate_end_headers(&session->hd_inflater);
if(call_header_cb) { if(call_header_cb) {

View File

@ -94,7 +94,7 @@ static int inflate_hd(json_t *obj, nghttp2_hd_context *inflater, int seq)
size_t buflen; size_t buflen;
ssize_t rv; ssize_t rv;
nghttp2_nv nv; nghttp2_nv nv;
int final; int inflate_flags;
wire = json_object_get(obj, "wire"); wire = json_object_get(obj, "wire");
if(wire == NULL) { if(wire == NULL) {
@ -131,19 +131,22 @@ static int inflate_hd(json_t *obj, nghttp2_hd_context *inflater, int seq)
p = buf; p = buf;
for(;;) { 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) { if(rv < 0) {
fprintf(stderr, "inflate failed with error code %zd at %d\n", rv, seq); fprintf(stderr, "inflate failed with error code %zd at %d\n", rv, seq);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
p += rv; p += rv;
buflen -= rv; buflen -= rv;
if(final) { if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
break;
}
json_array_append_new(headers, dump_header(nv.name, nv.namelen, json_array_append_new(headers, dump_header(nv.name, nv.namelen,
nv.value, nv.valuelen)); nv.value, nv.valuelen));
} }
if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
break;
}
}
assert(buflen == 0); assert(buflen == 0);
nghttp2_hd_inflate_end_headers(inflater); nghttp2_hd_inflate_end_headers(inflater);
to_json(inflater, headers, wire, seq); to_json(inflater, headers, wire, seq);

View File

@ -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, "map_each_free", test_nghttp2_map_each_free) ||
!CU_add_test(pSuite, "queue", test_nghttp2_queue) || !CU_add_test(pSuite, "queue", test_nghttp2_queue) ||
!CU_add_test(pSuite, "buffer", test_nghttp2_buffer) || !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, "npn", test_nghttp2_npn) ||
!CU_add_test(pSuite, "session_recv", test_nghttp2_session_recv) || !CU_add_test(pSuite, "session_recv", test_nghttp2_session_recv) ||
!CU_add_test(pSuite, "session_recv_invalid_stream_id", !CU_add_test(pSuite, "session_recv_invalid_stream_id",

View File

@ -29,96 +29,28 @@
#include <CUnit/CUnit.h> #include <CUnit/CUnit.h>
#include "nghttp2_buffer.h" #include "nghttp2_buffer.h"
#include "nghttp2_net.h"
void test_nghttp2_buffer(void) void test_nghttp2_buffer(void)
{ {
nghttp2_buffer buffer; 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)); nghttp2_buffer_init(&buffer, 16);
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));
CU_ASSERT(5 == nghttp2_buffer_avail(&buffer)); CU_ASSERT(0 == buffer.len);
memcpy(nghttp2_buffer_get(&buffer), "34567", 5);
nghttp2_buffer_advance(&buffer, 5);
CU_ASSERT(8 == nghttp2_buffer_length(&buffer));
CU_ASSERT(0 == nghttp2_buffer_avail(&buffer)); CU_ASSERT(0 == nghttp2_buffer_add(&buffer, (const uint8_t*)"foo", 3));
CU_ASSERT(0 == nghttp2_buffer_alloc(&buffer)); CU_ASSERT(3 == buffer.len);
memcpy(nghttp2_buffer_get(&buffer), "89ABCDE", 7);
nghttp2_buffer_advance(&buffer, 7);
CU_ASSERT(15 == nghttp2_buffer_length(&buffer));
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 == nghttp2_buffer_add(&buffer,
CU_ASSERT(0 == memcmp("0123456789ABCDE", out, 15)); (const uint8_t*)"012345678901", 12));
CU_ASSERT(16 == buffer.len);
nghttp2_buffer_reset(&buffer); CU_ASSERT(NGHTTP2_ERR_BUFFER_ERROR == nghttp2_buffer_add_byte(&buffer, '.'));
CU_ASSERT(NGHTTP2_ERR_BUFFER_ERROR ==
CU_ASSERT(0 == nghttp2_buffer_length(&buffer)); nghttp2_buffer_add(&buffer, (const uint8_t*)".", 1));
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));
nghttp2_buffer_free(&buffer); nghttp2_buffer_free(&buffer);
} }

View File

@ -185,28 +185,29 @@ void test_nghttp2_hd_deflate_same_indexed_repr(void)
void test_nghttp2_hd_deflate_common_header_eviction(void) void test_nghttp2_hd_deflate_common_header_eviction(void)
{ {
nghttp2_hd_context deflater, inflater; nghttp2_hd_context deflater, inflater;
nghttp2_nv nva[] = {MAKE_NV(":scheme", "http"), nghttp2_nv nva[] = {MAKE_NV("h1", ""),
MAKE_NV("", "")}; MAKE_NV("h2", "")};
uint8_t *buf = NULL; uint8_t *buf = NULL;
size_t buflen = 0; size_t buflen = 0;
ssize_t blocklen; ssize_t blocklen;
/* Default header table capacity is 4096. Adding 2 byte header name /* Default header table capacity is 4096. Adding 2 byte header name
and 4060 byte value, which is 4094 bytes including overhead, to and 4060 byte value, which is 4094 bytes including overhead, to
the table evicts first entry. */ the table evicts first entry. */
uint8_t value[4060]; uint8_t value[3038];
nva_out out; nva_out out;
size_t i;
nva_out_init(&out); nva_out_init(&out);
memset(value, '0', sizeof(value)); memset(value, '0', sizeof(value));
nva[1].name = (uint8_t*)"hd"; for(i = 0; i < 2; ++i) {
nva[1].namelen = strlen((const char*)nva[1].name); nva[i].value = value;
nva[1].value = value; nva[i].valuelen = sizeof(value);
nva[1].valuelen = sizeof(value); }
nghttp2_hd_deflate_init(&deflater, NGHTTP2_HD_SIDE_REQUEST); nghttp2_hd_deflate_init(&deflater, NGHTTP2_HD_SIDE_REQUEST);
nghttp2_hd_inflate_init(&inflater, 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). */ = 0). */
blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, nva, 1); blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, nva, 1);
CU_ASSERT(blocklen > 0); CU_ASSERT(blocklen > 0);
@ -218,11 +219,11 @@ void test_nghttp2_hd_deflate_common_header_eviction(void)
nva_out_reset(&out); nva_out_reset(&out);
/* Encode with large header */ /* Encode with second header */
blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, nva, 2); blocklen = nghttp2_hd_deflate_hd(&deflater, &buf, &buflen, 0, nva, 2);
CU_ASSERT(blocklen > 0); 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 header table because of eviction, is still emitted by the
inflater */ inflater */
CU_ASSERT(blocklen == inflate_hd(&inflater, &out, buf, blocklen)); CU_ASSERT(blocklen == inflate_hd(&inflater, &out, buf, blocklen));

View File

@ -120,20 +120,23 @@ ssize_t inflate_hd(nghttp2_hd_context *inflater, nva_out *out,
{ {
ssize_t rv; ssize_t rv;
nghttp2_nv nv; nghttp2_nv nv;
int final; int inflate_flags;
size_t initial = buflen; size_t initial = buflen;
for(;;) { 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) { if(rv < 0) {
return rv; return rv;
} }
buf += rv; buf += rv;
buflen -= rv; buflen -= rv;
if(final) { if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
add_out(out, &nv);
}
if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
break; break;
} }
add_out(out, &nv);
} }
nghttp2_hd_inflate_end_headers(inflater); nghttp2_hd_inflate_end_headers(inflater);
return initial - buflen; return initial - buflen;