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:
parent
f8a446fbeb
commit
8317559090
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
821
lib/nghttp2_hd.c
821
lib/nghttp2_hd.c
|
@ -331,8 +331,22 @@ int nghttp2_hd_deflate_init2(nghttp2_hd_context *deflater,
|
|||
|
||||
int nghttp2_hd_inflate_init(nghttp2_hd_context *inflater, nghttp2_hd_side side)
|
||||
{
|
||||
return nghttp2_hd_context_init(inflater, NGHTTP2_HD_ROLE_INFLATE, side,
|
||||
NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE);
|
||||
int rv;
|
||||
rv = nghttp2_hd_context_init(inflater, NGHTTP2_HD_ROLE_INFLATE, side,
|
||||
NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE);
|
||||
if(rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
inflater->opcode = NGHTTP2_HD_OPCODE_NONE;
|
||||
inflater->state = NGHTTP2_HD_STATE_OPCODE;
|
||||
nghttp2_buffer_init(&inflater->namebuf, NGHTTP2_HD_MAX_NAME);
|
||||
nghttp2_buffer_init(&inflater->valuebuf, NGHTTP2_HD_MAX_VALUE);
|
||||
inflater->huffman_encoded = 0;
|
||||
inflater->index = 0;
|
||||
inflater->left = 0;
|
||||
inflater->index_required = 0;
|
||||
inflater->ent_name = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hd_inflate_keep_free(nghttp2_hd_context *inflater)
|
||||
|
@ -363,6 +377,8 @@ void nghttp2_hd_deflate_free(nghttp2_hd_context *deflater)
|
|||
void nghttp2_hd_inflate_free(nghttp2_hd_context *inflater)
|
||||
{
|
||||
hd_inflate_keep_free(inflater);
|
||||
nghttp2_buffer_free(&inflater->namebuf);
|
||||
nghttp2_buffer_free(&inflater->valuebuf);
|
||||
nghttp2_hd_context_free(inflater);
|
||||
}
|
||||
|
||||
|
@ -494,28 +510,39 @@ static size_t encode_length(uint8_t *buf, size_t n, int prefix)
|
|||
* region from |in|. The decoded integer must be strictly less than 1
|
||||
* << 16.
|
||||
*
|
||||
* If the |initial| is nonzero, it is used as a initial value, this
|
||||
* function assumes the |in| starts with intermediate data.
|
||||
*
|
||||
* An entire integer is decoded successfully, decoded, the |*final| is
|
||||
* set to nonzero.
|
||||
*
|
||||
* This function returns the next byte of read byte. This function
|
||||
* stores the decoded integer in |*res| if it succeeds, or stores -1
|
||||
* in |*res|, indicating decoding error.
|
||||
* stores the decoded integer in |*res| if it succeed, including
|
||||
* partial decoding, or stores -1 in |*res|, indicating decoding
|
||||
* error.
|
||||
*/
|
||||
static uint8_t* decode_length(ssize_t *res, uint8_t *in, uint8_t *last,
|
||||
int prefix)
|
||||
static uint8_t* decode_length(ssize_t *res, int *final, ssize_t initial,
|
||||
uint8_t *in, uint8_t *last, int prefix)
|
||||
{
|
||||
int k = (1 << prefix) - 1, r;
|
||||
if(in == last) {
|
||||
*res = -1;
|
||||
ssize_t n = initial;
|
||||
*final = 0;
|
||||
if(n == 0) {
|
||||
if((*in & k) == k) {
|
||||
n = k;
|
||||
} else {
|
||||
*res = (*in) & k;
|
||||
*final = 1;
|
||||
return in + 1;
|
||||
}
|
||||
}
|
||||
if(++in == last) {
|
||||
*res = n;
|
||||
return in;
|
||||
}
|
||||
if((*in & k) == k) {
|
||||
*res = k;
|
||||
} else {
|
||||
*res = (*in) & k;
|
||||
return in + 1;
|
||||
}
|
||||
++in;
|
||||
for(r = 0; in != last; ++in, r += 7) {
|
||||
*res += (*in & 0x7f) << r;
|
||||
if(*res >= (1 << 16)) {
|
||||
n += (*in & 0x7f) << r;
|
||||
if(n >= (1 << 16)) {
|
||||
*res = -1;
|
||||
return in + 1;
|
||||
}
|
||||
|
@ -523,12 +550,17 @@ static uint8_t* decode_length(ssize_t *res, uint8_t *in, uint8_t *last,
|
|||
break;
|
||||
}
|
||||
}
|
||||
if(in == last || *in & (1 << 7)) {
|
||||
if(in == last) {
|
||||
*res = n;
|
||||
return in;
|
||||
}
|
||||
if(*in & (1 << 7)) {
|
||||
*res = -1;
|
||||
return NULL;
|
||||
} else {
|
||||
return in + 1;
|
||||
}
|
||||
*res = n;
|
||||
*final = 1;
|
||||
return in + 1;
|
||||
}
|
||||
|
||||
static int emit_indexed0(uint8_t **buf_ptr, size_t *buflen_ptr,
|
||||
|
@ -911,6 +943,11 @@ static int check_index_range(nghttp2_hd_context *context, size_t index)
|
|||
return index < context->hd_table.len + STATIC_TABLE_LENGTH;
|
||||
}
|
||||
|
||||
static int get_max_index(nghttp2_hd_context *context)
|
||||
{
|
||||
return context->hd_table.len + STATIC_TABLE_LENGTH - 1;
|
||||
}
|
||||
|
||||
nghttp2_hd_entry* nghttp2_hd_table_get(nghttp2_hd_context *context,
|
||||
size_t index)
|
||||
{
|
||||
|
@ -1128,274 +1165,502 @@ ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_context *deflater,
|
|||
return rv;
|
||||
}
|
||||
|
||||
ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater,
|
||||
nghttp2_nv *nv_out, int *final,
|
||||
uint8_t *in, size_t inlen)
|
||||
static void hd_inflate_set_huffman_encoded(nghttp2_hd_context *inflater,
|
||||
const uint8_t *in)
|
||||
{
|
||||
int rv = 0;
|
||||
uint8_t *first = in;
|
||||
uint8_t *last = in + inlen;
|
||||
inflater->huffman_encoded = (*in & (1 << 7)) != 0;
|
||||
}
|
||||
|
||||
DEBUGF(fprintf(stderr, "infalte_hd start\n"));
|
||||
if(inflater->bad) {
|
||||
/*
|
||||
* Decodes the integer from the range [in, last). The result is
|
||||
* assigned to |inflater->left|. If the |inflater->left| is 0, then
|
||||
* it performs variable integer decoding from scratch. Otherwise, it
|
||||
* uses the |inflater->left| as the initial value and continues to
|
||||
* decode assuming that [in, last) begins with intermediary sequence.
|
||||
*
|
||||
* This function returns the number of bytes read if it succeeds, or
|
||||
* one of the following negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_HEADER_COMP
|
||||
* Integer decoding failed
|
||||
*/
|
||||
static ssize_t hd_inflate_read_len(nghttp2_hd_context *inflater,
|
||||
int *rfin,
|
||||
uint8_t *in, uint8_t *last,
|
||||
int prefix, size_t maxlen)
|
||||
{
|
||||
uint8_t *nin;
|
||||
*rfin = 0;
|
||||
nin = decode_length(&inflater->left, rfin, inflater->left, in, last, prefix);
|
||||
if(inflater->left == -1) {
|
||||
DEBUGF(fprintf(stderr, "invalid integer\n"));
|
||||
return NGHTTP2_ERR_HEADER_COMP;
|
||||
}
|
||||
|
||||
*final = 0;
|
||||
hd_inflate_keep_free(inflater);
|
||||
|
||||
for(; in != last;) {
|
||||
uint8_t c = *in;
|
||||
if(c & 0x80u) {
|
||||
/* Indexed Header Repr */
|
||||
ssize_t index;
|
||||
nghttp2_hd_entry *ent;
|
||||
in = decode_length(&index, in, last, 7);
|
||||
DEBUGF(fprintf(stderr, "Indexed repr index=%zd\n", index));
|
||||
if(index < 0) {
|
||||
DEBUGF(fprintf(stderr, "Index out of range index=%zd\n", index));
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
goto fail;
|
||||
}
|
||||
if(index == 0) {
|
||||
DEBUGF(fprintf(stderr, "Clearing reference set\n"));
|
||||
clear_refset(inflater);
|
||||
continue;
|
||||
}
|
||||
--index;
|
||||
if(!check_index_range(inflater, index)) {
|
||||
DEBUGF(fprintf(stderr, "Index out of range index=%zd\n", index));
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
goto fail;
|
||||
}
|
||||
ent = nghttp2_hd_table_get(inflater, index);
|
||||
if(index >= (ssize_t)inflater->hd_table.len) {
|
||||
nghttp2_hd_entry *new_ent;
|
||||
new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL,
|
||||
&ent->nv, NGHTTP2_HD_FLAG_NONE);
|
||||
if(!new_ent) {
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
goto fail;
|
||||
}
|
||||
/* new_ent->ref == 0 may be hold but emit_indexed_header
|
||||
tracks new_ent, so there is no leak. */
|
||||
emit_indexed_header(inflater, nv_out, new_ent);
|
||||
inflater->ent_keep = new_ent;
|
||||
return in - first;
|
||||
} else {
|
||||
ent->flags ^= NGHTTP2_HD_FLAG_REFSET;
|
||||
if(ent->flags & NGHTTP2_HD_FLAG_REFSET) {
|
||||
emit_indexed_header(inflater, nv_out, ent);
|
||||
return in - first;
|
||||
} else {
|
||||
DEBUGF(fprintf(stderr, "Toggle off item:\n"));
|
||||
DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr));
|
||||
DEBUGF(fprintf(stderr, ": "));
|
||||
DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr));
|
||||
DEBUGF(fprintf(stderr, "\n"));
|
||||
}
|
||||
}
|
||||
if(rv != 0) {
|
||||
goto fail;
|
||||
}
|
||||
} else if(c == 0x40u || c == 0) {
|
||||
/* Literal Header Repr - New Name */
|
||||
nghttp2_nv nv;
|
||||
ssize_t namelen, valuelen;
|
||||
int name_huffman, value_huffman;
|
||||
uint8_t *decoded_huffman_name = NULL, *decoded_huffman_value = NULL;
|
||||
DEBUGF(fprintf(stderr, "Literal header repr - new name\n"));
|
||||
if(++in == last) {
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
goto fail;
|
||||
}
|
||||
name_huffman = *in & (1 << 7);
|
||||
in = decode_length(&namelen, in, last, 7);
|
||||
if(namelen < 0 || in + namelen > last) {
|
||||
DEBUGF(fprintf(stderr, "Invalid namelen=%zd\n", namelen));
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
goto fail;
|
||||
}
|
||||
if(name_huffman) {
|
||||
rv = nghttp2_hd_huff_decode(&nv.name, in, namelen, inflater->side);
|
||||
if(rv < 0) {
|
||||
DEBUGF(fprintf(stderr, "Name huffman decoding failed\n"));
|
||||
goto fail;
|
||||
}
|
||||
decoded_huffman_name = nv.name;
|
||||
nv.namelen = rv;
|
||||
} else {
|
||||
nv.name = in;
|
||||
nv.namelen = namelen;
|
||||
}
|
||||
in += namelen;
|
||||
|
||||
if(in == last) {
|
||||
DEBUGF(fprintf(stderr, "No value found\n"));
|
||||
free(decoded_huffman_name);
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
goto fail;
|
||||
}
|
||||
value_huffman = *in & (1 << 7);
|
||||
in = decode_length(&valuelen, in, last, 7);
|
||||
if(valuelen < 0 || in + valuelen > last) {
|
||||
DEBUGF(fprintf(stderr, "Invalid valuelen=%zd\n", valuelen));
|
||||
free(decoded_huffman_name);
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
goto fail;
|
||||
}
|
||||
if(value_huffman) {
|
||||
rv = nghttp2_hd_huff_decode(&nv.value, in, valuelen, inflater->side);
|
||||
if(rv < 0) {
|
||||
DEBUGF(fprintf(stderr, "Value huffman decoding failed\n"));
|
||||
free(decoded_huffman_name);
|
||||
goto fail;
|
||||
}
|
||||
decoded_huffman_value = nv.value;
|
||||
nv.valuelen = rv;
|
||||
} else {
|
||||
nv.value = in;
|
||||
nv.valuelen = valuelen;
|
||||
}
|
||||
in += valuelen;
|
||||
|
||||
if(c == 0x40u) {
|
||||
int flags = NGHTTP2_HD_FLAG_NONE;
|
||||
if(name_huffman) {
|
||||
flags |= NGHTTP2_HD_FLAG_NAME_GIFT;
|
||||
}
|
||||
if(value_huffman) {
|
||||
flags |= NGHTTP2_HD_FLAG_VALUE_GIFT;
|
||||
}
|
||||
emit_newname_header(inflater, nv_out, &nv);
|
||||
inflater->name_keep = decoded_huffman_name;
|
||||
inflater->value_keep = decoded_huffman_value;
|
||||
return in - first;
|
||||
} else {
|
||||
nghttp2_hd_entry *new_ent;
|
||||
uint8_t ent_flags = NGHTTP2_HD_FLAG_NAME_ALLOC |
|
||||
NGHTTP2_HD_FLAG_VALUE_ALLOC;
|
||||
if(name_huffman) {
|
||||
ent_flags |= NGHTTP2_HD_FLAG_NAME_GIFT;
|
||||
}
|
||||
if(value_huffman) {
|
||||
ent_flags |= NGHTTP2_HD_FLAG_VALUE_GIFT;
|
||||
}
|
||||
new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv,
|
||||
ent_flags);
|
||||
if(new_ent) {
|
||||
emit_indexed_header(inflater, nv_out, new_ent);
|
||||
inflater->ent_keep = new_ent;
|
||||
return in - first;
|
||||
} else {
|
||||
free(decoded_huffman_name);
|
||||
free(decoded_huffman_value);
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
}
|
||||
}
|
||||
if(rv != 0) {
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
/* Literal Header Repr - Indexed Name */
|
||||
nghttp2_hd_entry *ent;
|
||||
uint8_t *value;
|
||||
ssize_t valuelen, index;
|
||||
int value_huffman;
|
||||
uint8_t *decoded_huffman_value = NULL;
|
||||
DEBUGF(fprintf(stderr, "Literal header repr - indexed name\n"));
|
||||
in = decode_length(&index, in, last, 6);
|
||||
if(index <= 0) {
|
||||
DEBUGF(fprintf(stderr, "Index out of range index=%zd\n", index));
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
goto fail;
|
||||
}
|
||||
--index;
|
||||
if(!check_index_range(inflater, index)) {
|
||||
DEBUGF(fprintf(stderr, "Index out of range index=%zd\n", index));
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
goto fail;
|
||||
}
|
||||
ent = nghttp2_hd_table_get(inflater, index);
|
||||
if(in == last) {
|
||||
DEBUGF(fprintf(stderr, "No value found\n"));
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
goto fail;
|
||||
}
|
||||
value_huffman = *in & (1 << 7);
|
||||
in = decode_length(&valuelen, in , last, 7);
|
||||
if(valuelen < 0 || in + valuelen > last) {
|
||||
DEBUGF(fprintf(stderr, "Invalid valuelen=%zd\n", valuelen));
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
goto fail;
|
||||
}
|
||||
if(value_huffman) {
|
||||
rv = nghttp2_hd_huff_decode(&value, in, valuelen, inflater->side);
|
||||
if(rv < 0) {
|
||||
DEBUGF(fprintf(stderr, "Value huffman decoding failed\n"));
|
||||
goto fail;
|
||||
}
|
||||
decoded_huffman_value = value;
|
||||
in += valuelen;
|
||||
valuelen = rv;
|
||||
} else {
|
||||
value = in;
|
||||
in += valuelen;
|
||||
}
|
||||
if((c & 0x40u) == 0x40u) {
|
||||
emit_indname_header(inflater, nv_out, ent, value, valuelen);
|
||||
inflater->value_keep = decoded_huffman_value;
|
||||
return in - first;
|
||||
} else {
|
||||
nghttp2_nv nv;
|
||||
nghttp2_hd_entry *new_ent;
|
||||
uint8_t ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC;
|
||||
if(value_huffman) {
|
||||
ent_flags |= NGHTTP2_HD_FLAG_VALUE_GIFT;
|
||||
}
|
||||
++ent->ref;
|
||||
nv.name = ent->nv.name;
|
||||
if((size_t)index < inflater->hd_table.len) {
|
||||
ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC;
|
||||
}
|
||||
nv.namelen = ent->nv.namelen;
|
||||
nv.value = value;
|
||||
nv.valuelen = valuelen;
|
||||
new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv,
|
||||
ent_flags);
|
||||
if(--ent->ref == 0) {
|
||||
nghttp2_hd_entry_free(ent);
|
||||
free(ent);
|
||||
}
|
||||
if(new_ent) {
|
||||
emit_indexed_header(inflater, nv_out, new_ent);
|
||||
inflater->ent_keep = new_ent;
|
||||
return in - first;
|
||||
} else {
|
||||
free(decoded_huffman_value);
|
||||
rv = NGHTTP2_ERR_HEADER_COMP;
|
||||
}
|
||||
}
|
||||
if(rv != 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
if((size_t)inflater->left > maxlen) {
|
||||
DEBUGF(fprintf(stderr, "integer exceeds the maximum value %zu\n", maxlen));
|
||||
return NGHTTP2_ERR_HEADER_COMP;
|
||||
}
|
||||
return nin - in;
|
||||
}
|
||||
|
||||
for(; inflater->end_headers_index < inflater->hd_table.len;
|
||||
++inflater->end_headers_index) {
|
||||
nghttp2_hd_entry *ent;
|
||||
ent = nghttp2_hd_ringbuf_get(&inflater->hd_table,
|
||||
inflater->end_headers_index);
|
||||
/*
|
||||
* Reads |inflater->left| bytes from the range [in, last) and performs
|
||||
* huffman decoding against them and pushes the result into the
|
||||
* |buffer|.
|
||||
*
|
||||
* This function returns the number of bytes read if it succeeds, or
|
||||
* one of the following negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory
|
||||
* NGHTTP2_ERR_HEADER_COMP
|
||||
* Huffman decoding failed
|
||||
*/
|
||||
static ssize_t hd_inflate_read_huff(nghttp2_hd_context *inflater,
|
||||
nghttp2_buffer *buffer,
|
||||
uint8_t *in, uint8_t *last)
|
||||
{
|
||||
int rv;
|
||||
int final = 0;
|
||||
if(last - in >= inflater->left) {
|
||||
last = in + inflater->left;
|
||||
final = 1;
|
||||
}
|
||||
rv = nghttp2_hd_huff_decode(&inflater->huff_decode_ctx, buffer,
|
||||
in, last - in, final);
|
||||
if(rv == NGHTTP2_ERR_BUFFER_ERROR) {
|
||||
return NGHTTP2_ERR_HEADER_COMP;
|
||||
}
|
||||
if(rv < 0) {
|
||||
DEBUGF(fprintf(stderr, "huffman decoding failed\n"));
|
||||
return rv;
|
||||
}
|
||||
inflater->left -= rv;
|
||||
return rv;
|
||||
}
|
||||
|
||||
if((ent->flags & NGHTTP2_HD_FLAG_REFSET) &&
|
||||
(ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) {
|
||||
emit_indexed_header(inflater, nv_out, ent);
|
||||
/*
|
||||
* Reads |inflater->left| bytes from the range [in, last) and copies
|
||||
* them into the |buffer|.
|
||||
*
|
||||
* This function returns the number of bytes read if it succeeds, or
|
||||
* one of the following negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory
|
||||
* NGHTTP2_ERR_HEADER_COMP
|
||||
* Header decompression failed
|
||||
*/
|
||||
static ssize_t hd_inflate_read(nghttp2_hd_context *inflater,
|
||||
nghttp2_buffer *buffer,
|
||||
uint8_t *in, uint8_t *last)
|
||||
{
|
||||
int rv;
|
||||
size_t len = nghttp2_min(last - in, inflater->left);
|
||||
rv = nghttp2_buffer_add(buffer, in, len);
|
||||
if(rv == NGHTTP2_ERR_BUFFER_ERROR) {
|
||||
return NGHTTP2_ERR_HEADER_COMP;
|
||||
}
|
||||
if(rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
inflater->left -= len;
|
||||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalize indexed header representation reception. If header is
|
||||
* emitted, |*nv_out| is filled with that value and 0 is returned. If
|
||||
* no header is emitted, 1 is returned.
|
||||
*
|
||||
* This function returns either 0 or 1 if it succeeds, or one of the
|
||||
* following negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory
|
||||
*/
|
||||
static int hd_inflate_commit_indexed(nghttp2_hd_context *inflater,
|
||||
nghttp2_nv *nv_out)
|
||||
{
|
||||
nghttp2_hd_entry *ent = nghttp2_hd_table_get(inflater, inflater->index);
|
||||
if(inflater->index >= inflater->hd_table.len) {
|
||||
nghttp2_hd_entry *new_ent;
|
||||
new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL,
|
||||
&ent->nv, NGHTTP2_HD_FLAG_NONE);
|
||||
if(!new_ent) {
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
/* new_ent->ref == 0 may be hold */
|
||||
emit_indexed_header(inflater, nv_out, new_ent);
|
||||
inflater->ent_keep = new_ent;
|
||||
return 0;
|
||||
}
|
||||
ent->flags ^= NGHTTP2_HD_FLAG_REFSET;
|
||||
if(ent->flags & NGHTTP2_HD_FLAG_REFSET) {
|
||||
emit_indexed_header(inflater, nv_out, ent);
|
||||
return 0;
|
||||
}
|
||||
DEBUGF(fprintf(stderr, "Toggle off item:\n"));
|
||||
DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr));
|
||||
DEBUGF(fprintf(stderr, ": "));
|
||||
DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr));
|
||||
DEBUGF(fprintf(stderr, "\n"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalize literal header representation - new name- reception. If
|
||||
* header is emitted, |*nv_out| is filled with that value and 0 is
|
||||
* returned.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory
|
||||
*/
|
||||
static int hd_inflate_commit_newname(nghttp2_hd_context *inflater,
|
||||
nghttp2_nv *nv_out)
|
||||
{
|
||||
nghttp2_nv nv = {
|
||||
inflater->namebuf.buf,
|
||||
inflater->valuebuf.buf,
|
||||
inflater->namebuf.len,
|
||||
inflater->valuebuf.len
|
||||
};
|
||||
if(inflater->index_required) {
|
||||
nghttp2_hd_entry *new_ent;
|
||||
uint8_t ent_flags =
|
||||
NGHTTP2_HD_FLAG_NAME_ALLOC | NGHTTP2_HD_FLAG_VALUE_ALLOC |
|
||||
NGHTTP2_HD_FLAG_NAME_GIFT | NGHTTP2_HD_FLAG_VALUE_GIFT;
|
||||
new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv,
|
||||
ent_flags);
|
||||
if(new_ent) {
|
||||
nghttp2_buffer_release(&inflater->namebuf);
|
||||
nghttp2_buffer_release(&inflater->valuebuf);
|
||||
emit_indexed_header(inflater, nv_out, new_ent);
|
||||
inflater->ent_keep = new_ent;
|
||||
return 0;
|
||||
}
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
emit_newname_header(inflater, nv_out, &nv);
|
||||
inflater->name_keep = inflater->namebuf.buf;
|
||||
nghttp2_buffer_release(&inflater->namebuf);
|
||||
inflater->value_keep = inflater->valuebuf.buf;
|
||||
nghttp2_buffer_release(&inflater->valuebuf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalize literal header representation - indexed name-
|
||||
* reception. If header is emitted, |*nv_out| is filled with that
|
||||
* value and 0 is returned.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory
|
||||
*/
|
||||
static int hd_inflate_commit_indname(nghttp2_hd_context *inflater,
|
||||
nghttp2_nv *nv_out)
|
||||
{
|
||||
if(inflater->index_required) {
|
||||
nghttp2_nv nv;
|
||||
nghttp2_hd_entry *new_ent;
|
||||
uint8_t ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC |
|
||||
NGHTTP2_HD_FLAG_VALUE_GIFT;
|
||||
|
||||
if(inflater->index < inflater->hd_table.len) {
|
||||
ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC;
|
||||
}
|
||||
++inflater->ent_name->ref;
|
||||
nv.name = inflater->ent_name->nv.name;
|
||||
nv.namelen = inflater->ent_name->nv.namelen;
|
||||
nv.value = inflater->valuebuf.buf;
|
||||
nv.valuelen = inflater->valuebuf.len;
|
||||
new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv,
|
||||
ent_flags);
|
||||
if(--inflater->ent_name->ref == 0) {
|
||||
nghttp2_hd_entry_free(inflater->ent_name);
|
||||
free(inflater->ent_name);
|
||||
}
|
||||
inflater->ent_name = NULL;
|
||||
if(new_ent) {
|
||||
nghttp2_buffer_release(&inflater->valuebuf);
|
||||
emit_indexed_header(inflater, nv_out, new_ent);
|
||||
inflater->ent_keep = new_ent;
|
||||
return 0;
|
||||
}
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
emit_indname_header(inflater, nv_out, inflater->ent_name,
|
||||
inflater->valuebuf.buf, inflater->valuebuf.len);
|
||||
inflater->value_keep = inflater->valuebuf.buf;
|
||||
nghttp2_buffer_release(&inflater->valuebuf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t guess_huff_decode_len(size_t encode_len)
|
||||
{
|
||||
return encode_len * 3 / 2;
|
||||
}
|
||||
|
||||
ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater,
|
||||
nghttp2_nv *nv_out, int *inflate_flags,
|
||||
uint8_t *in, size_t inlen, int in_final)
|
||||
{
|
||||
ssize_t rv = 0;
|
||||
uint8_t *first = in;
|
||||
uint8_t *last = in + inlen;
|
||||
int rfin = 0;
|
||||
|
||||
DEBUGF(fprintf(stderr, "nghtp2_hd_infalte_hd start state=%d\n",
|
||||
inflater->state));
|
||||
*inflate_flags = NGHTTP2_HD_INFLATE_NONE;
|
||||
for(; in != last;) {
|
||||
switch(inflater->state) {
|
||||
case NGHTTP2_HD_STATE_OPCODE:
|
||||
if(*in & 0x80u) {
|
||||
DEBUGF(fprintf(stderr, "Indexed repr\n"));
|
||||
inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
|
||||
inflater->state = NGHTTP2_HD_STATE_READ_INDEX;
|
||||
} else {
|
||||
if(*in == 0x40 || *in == 0) {
|
||||
DEBUGF(fprintf(stderr, "Literal header repr - new name\n"));
|
||||
inflater->opcode = NGHTTP2_HD_OPCODE_NEWNAME;
|
||||
inflater->state = NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN;
|
||||
} else {
|
||||
DEBUGF(fprintf(stderr, "Literal header repr - indexed name\n"));
|
||||
inflater->opcode = NGHTTP2_HD_OPCODE_INDNAME;
|
||||
inflater->state = NGHTTP2_HD_STATE_READ_INDEX;
|
||||
}
|
||||
inflater->index_required = (*in & 0x40) == 0;
|
||||
DEBUGF(fprintf(stderr, "indexing required=%d\n",
|
||||
inflater->index_required != 0));
|
||||
if(inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) {
|
||||
++in;
|
||||
}
|
||||
}
|
||||
inflater->left = 0;
|
||||
break;
|
||||
case NGHTTP2_HD_STATE_READ_INDEX:
|
||||
rfin = 0;
|
||||
rv = hd_inflate_read_len(inflater, &rfin, in, last,
|
||||
inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED ?
|
||||
7 : 6,
|
||||
get_max_index(inflater) + 1);
|
||||
if(rv < 0) {
|
||||
goto fail;
|
||||
}
|
||||
in += rv;
|
||||
if(!rfin) {
|
||||
return in - first;
|
||||
}
|
||||
DEBUGF(fprintf(stderr, "index=%zd\n", inflater->left));
|
||||
if(inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) {
|
||||
inflater->index = inflater->left;
|
||||
if(inflater->index == 0) {
|
||||
DEBUGF(fprintf(stderr, "Clearing reference set\n"));
|
||||
clear_refset(inflater);
|
||||
inflater->state = NGHTTP2_HD_STATE_OPCODE;
|
||||
break;
|
||||
}
|
||||
--inflater->index;
|
||||
rv = hd_inflate_commit_indexed(inflater, nv_out);
|
||||
if(rv < 0) {
|
||||
goto fail;
|
||||
}
|
||||
inflater->state = NGHTTP2_HD_STATE_OPCODE;
|
||||
/* If rv == 1, no header was emitted */
|
||||
if(rv == 0) {
|
||||
*inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
|
||||
return in - first;
|
||||
}
|
||||
} else {
|
||||
inflater->index = inflater->left;
|
||||
--inflater->index;
|
||||
inflater->ent_name = nghttp2_hd_table_get(inflater, inflater->index);
|
||||
inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN:
|
||||
hd_inflate_set_huffman_encoded(inflater, in);
|
||||
inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN;
|
||||
inflater->left = 0;
|
||||
DEBUGF(fprintf(stderr, "huffman encoded=%d\n",
|
||||
inflater->huffman_encoded != 0));
|
||||
/* Fall through */
|
||||
case NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN:
|
||||
rfin = 0;
|
||||
rv = hd_inflate_read_len(inflater, &rfin, in, last, 7,
|
||||
NGHTTP2_HD_MAX_NAME);
|
||||
if(rv < 0) {
|
||||
goto fail;
|
||||
}
|
||||
in += rv;
|
||||
if(!rfin) {
|
||||
DEBUGF(fprintf(stderr, "integer not fully decoded. current=%zd\n",
|
||||
inflater->left));
|
||||
return in - first;
|
||||
}
|
||||
rv = 0;
|
||||
if(inflater->huffman_encoded) {
|
||||
nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx,
|
||||
inflater->side);
|
||||
rv = nghttp2_buffer_reserve(&inflater->namebuf,
|
||||
guess_huff_decode_len(inflater->left));
|
||||
if(rv != 0) {
|
||||
goto fail;
|
||||
}
|
||||
inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF;
|
||||
} else {
|
||||
rv = nghttp2_buffer_reserve(&inflater->namebuf, inflater->left);
|
||||
if(rv != 0) {
|
||||
goto fail;
|
||||
}
|
||||
inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAME;
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF:
|
||||
rv = hd_inflate_read_huff(inflater, &inflater->namebuf, in, last);
|
||||
if(rv < 0) {
|
||||
goto fail;
|
||||
}
|
||||
in += rv;
|
||||
DEBUGF(fprintf(stderr, "%zd bytes read\n", rv));
|
||||
if(inflater->left) {
|
||||
DEBUGF(fprintf(stderr, "still %zd bytes to go\n", inflater->left));
|
||||
return in - first;
|
||||
}
|
||||
inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
|
||||
break;
|
||||
case NGHTTP2_HD_STATE_NEWNAME_READ_NAME:
|
||||
rv = hd_inflate_read(inflater, &inflater->namebuf, in, last);
|
||||
if(rv < 0) {
|
||||
goto fail;
|
||||
}
|
||||
in += rv;
|
||||
DEBUGF(fprintf(stderr, "%zd bytes read\n", rv));
|
||||
if(inflater->left) {
|
||||
DEBUGF(fprintf(stderr, "still %zd bytes to go\n", inflater->left));
|
||||
return in - first;
|
||||
}
|
||||
inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
|
||||
break;
|
||||
case NGHTTP2_HD_STATE_CHECK_VALUELEN:
|
||||
hd_inflate_set_huffman_encoded(inflater, in);
|
||||
inflater->state = NGHTTP2_HD_STATE_READ_VALUELEN;
|
||||
inflater->left = 0;
|
||||
DEBUGF(fprintf(stderr, "huffman encoded=%d\n",
|
||||
inflater->huffman_encoded != 0));
|
||||
/* Fall through */
|
||||
case NGHTTP2_HD_STATE_READ_VALUELEN:
|
||||
rfin = 0;
|
||||
rv = hd_inflate_read_len(inflater, &rfin, in, last, 7,
|
||||
NGHTTP2_HD_MAX_VALUE);
|
||||
if(rv < 0) {
|
||||
goto fail;
|
||||
}
|
||||
in += rv;
|
||||
if(!rfin) {
|
||||
return in - first;
|
||||
}
|
||||
DEBUGF(fprintf(stderr, "valuelen=%zd\n", inflater->left));
|
||||
if(inflater->left == 0) {
|
||||
if(inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) {
|
||||
rv = hd_inflate_commit_newname(inflater, nv_out);
|
||||
} else {
|
||||
rv = hd_inflate_commit_indname(inflater, nv_out);
|
||||
}
|
||||
if(rv != 0) {
|
||||
goto fail;
|
||||
}
|
||||
inflater->state = NGHTTP2_HD_STATE_OPCODE;
|
||||
*inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
|
||||
return in - first;
|
||||
}
|
||||
if(inflater->huffman_encoded) {
|
||||
nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx,
|
||||
inflater->side);
|
||||
rv = nghttp2_buffer_reserve(&inflater->valuebuf,
|
||||
guess_huff_decode_len(inflater->left));
|
||||
inflater->state = NGHTTP2_HD_STATE_READ_VALUEHUFF;
|
||||
} else {
|
||||
rv = nghttp2_buffer_reserve(&inflater->valuebuf, inflater->left);
|
||||
if(rv != 0) {
|
||||
goto fail;
|
||||
}
|
||||
inflater->state = NGHTTP2_HD_STATE_READ_VALUE;
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_HD_STATE_READ_VALUEHUFF:
|
||||
rv = hd_inflate_read_huff(inflater, &inflater->valuebuf, in, last);
|
||||
if(rv < 0) {
|
||||
goto fail;
|
||||
}
|
||||
in += rv;
|
||||
DEBUGF(fprintf(stderr, "%zd bytes read\n", rv));
|
||||
if(inflater->left) {
|
||||
DEBUGF(fprintf(stderr, "still %zd bytes to go\n", inflater->left));
|
||||
return in - first;
|
||||
}
|
||||
if(inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) {
|
||||
rv = hd_inflate_commit_newname(inflater, nv_out);
|
||||
} else {
|
||||
rv = hd_inflate_commit_indname(inflater, nv_out);
|
||||
}
|
||||
if(rv != 0) {
|
||||
goto fail;
|
||||
}
|
||||
inflater->state = NGHTTP2_HD_STATE_OPCODE;
|
||||
*inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
|
||||
return in - first;
|
||||
case NGHTTP2_HD_STATE_READ_VALUE:
|
||||
rv = hd_inflate_read(inflater, &inflater->valuebuf, in, last);
|
||||
if(rv < 0) {
|
||||
DEBUGF(fprintf(stderr, "value read failure %zd: %s\n",
|
||||
rv, nghttp2_strerror(rv)));
|
||||
goto fail;
|
||||
}
|
||||
in += rv;
|
||||
DEBUGF(fprintf(stderr, "%zd bytes read\n", rv));
|
||||
if(inflater->left) {
|
||||
DEBUGF(fprintf(stderr, "still %zd bytes to go\n", inflater->left));
|
||||
return in - first;
|
||||
}
|
||||
if(inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) {
|
||||
rv = hd_inflate_commit_newname(inflater, nv_out);
|
||||
} else {
|
||||
rv = hd_inflate_commit_indname(inflater, nv_out);
|
||||
}
|
||||
if(rv != 0) {
|
||||
goto fail;
|
||||
}
|
||||
inflater->state = NGHTTP2_HD_STATE_OPCODE;
|
||||
*inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
|
||||
return in - first;
|
||||
}
|
||||
ent->flags &= ~NGHTTP2_HD_FLAG_EMIT;
|
||||
}
|
||||
*final = 1;
|
||||
assert(in == last);
|
||||
if(in_final) {
|
||||
for(; inflater->end_headers_index < inflater->hd_table.len;
|
||||
++inflater->end_headers_index) {
|
||||
nghttp2_hd_entry *ent;
|
||||
ent = nghttp2_hd_ringbuf_get(&inflater->hd_table,
|
||||
inflater->end_headers_index);
|
||||
|
||||
if((ent->flags & NGHTTP2_HD_FLAG_REFSET) &&
|
||||
(ent->flags & NGHTTP2_HD_FLAG_EMIT) == 0) {
|
||||
emit_indexed_header(inflater, nv_out, ent);
|
||||
*inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
|
||||
return in - first;
|
||||
}
|
||||
ent->flags &= ~NGHTTP2_HD_FLAG_EMIT;
|
||||
}
|
||||
*inflate_flags |= NGHTTP2_HD_INFLATE_FINAL;
|
||||
}
|
||||
return in - first;
|
||||
fail:
|
||||
inflater->bad = 1;
|
||||
|
|
114
lib/nghttp2_hd.h
114
lib/nghttp2_hd.h
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue