Commit 83175590 authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

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.
parent f8a446fb
/*
* 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);
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;
}
chunk->data = buf;
chunk->next = NULL;
buffer->current->next = chunk;
buffer->current = chunk;
} else {
buffer->current = buffer->current->next;
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;
}
/*
* 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.
*/
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);
/*
* Reader interface to read data from nghttp2_buffer sequentially.
*/
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;
/*
* Initializes the |reader| with the |buffer|.
* Out of memory
*/
void nghttp2_buffer_reader_init(nghttp2_buffer_reader *reader,
nghttp2_buffer *buffer);
int nghttp2_buffer_add(nghttp2_buffer *buffer,
const uint8_t *data, size_t len);
/*
* 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|.
* 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
*/
int nghttp2_buffer_reader_count(nghttp2_buffer_reader *reader,
size_t len, uint8_t c);
int nghttp2_buffer_add_byte(nghttp2_buffer *buffer, uint8_t b);
/*
* Advances the current position by |amount|.
* Releases the buffer without freeing it. The data members in buffer
* is initialized.
*/
void nghttp2_buffer_reader_advance(nghttp2_buffer_reader *reader,
size_t amount);
void nghttp2_buffer_release(nghttp2_buffer *buffer);
#endif /* NGHTTP2_BUFFER_H */
This diff is collapsed.
......@@ -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().
*
* The caller must set the |final| to nonzero if the given input is
* the final block.
*
* 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|.
* 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 */
......@@ -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;
/* 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;
ctx->huff_decode_table = req_huff_decode_table;
} else {
huff_decode_table = res_huff_decode_table;
ctx->huff_decode_table = res_huff_decode_table;
}
j = 0;
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 */
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;
}
......@@ -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;
......
......@@ -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 */
......@@ -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) {
......
......@@ -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);
......
......@@ -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",
......
......@@ -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);
}
......@@ -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));
......
......@@ -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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment