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
*
* 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 <assert.h>
#include <string.h>
#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;
}

View File

@ -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 <nghttp2/nghttp2.h>
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 */

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)
{
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;

View File

@ -31,9 +31,15 @@
#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_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 */

View File

@ -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;
}

View File

@ -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;

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],
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 */

View File

@ -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) {

View File

@ -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);

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, "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",

View File

@ -29,96 +29,28 @@
#include <CUnit/CUnit.h>
#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);
}

View File

@ -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));

View File

@ -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;