in source/hpack.c [1052:1268]
int aws_hpack_decode(
struct aws_hpack_context *context,
struct aws_byte_cursor *to_decode,
struct aws_hpack_decode_result *result) {
AWS_PRECONDITION(context);
AWS_PRECONDITION(to_decode);
AWS_PRECONDITION(result);
/* Run state machine until we decode a complete entry.
* Every state requires data, so we can simply loop until no more data available. */
while (to_decode->len) {
switch (context->progress_entry.state) {
case HPACK_ENTRY_STATE_INIT: {
/* Reset entry */
AWS_ZERO_STRUCT(context->progress_entry.u);
context->progress_entry.scratch.len = 0;
/* Determine next state by looking at first few bits of the next byte:
* 1xxxxxxx: Indexed Header Field Representation
* 01xxxxxx: Literal Header Field with Incremental Indexing
* 001xxxxx: Dynamic Table Size Update
* 0001xxxx: Literal Header Field Never Indexed
* 0000xxxx: Literal Header Field without Indexing */
uint8_t first_byte = to_decode->ptr[0];
if (first_byte & (1 << 7)) {
/* 1xxxxxxx: Indexed Header Field Representation */
context->progress_entry.state = HPACK_ENTRY_STATE_INDEXED;
} else if (first_byte & (1 << 6)) {
/* 01xxxxxx: Literal Header Field with Incremental Indexing */
context->progress_entry.u.literal.compression = AWS_HTTP_HEADER_COMPRESSION_USE_CACHE;
context->progress_entry.u.literal.prefix_size = 6;
context->progress_entry.state = HPACK_ENTRY_STATE_LITERAL_BEGIN;
} else if (first_byte & (1 << 5)) {
/* 001xxxxx: Dynamic Table Size Update */
context->progress_entry.state = HPACK_ENTRY_STATE_DYNAMIC_TABLE_RESIZE;
} else if (first_byte & (1 << 4)) {
/* 0001xxxx: Literal Header Field Never Indexed */
context->progress_entry.u.literal.compression = AWS_HTTP_HEADER_COMPRESSION_NO_FORWARD_CACHE;
context->progress_entry.u.literal.prefix_size = 4;
context->progress_entry.state = HPACK_ENTRY_STATE_LITERAL_BEGIN;
} else {
/* 0000xxxx: Literal Header Field without Indexing */
context->progress_entry.u.literal.compression = AWS_HTTP_HEADER_COMPRESSION_NO_CACHE;
context->progress_entry.u.literal.prefix_size = 4;
context->progress_entry.state = HPACK_ENTRY_STATE_LITERAL_BEGIN;
}
} break;
/* RFC-7541 6.1. Indexed Header Field Representation.
* Decode one integer, which is an index into the table.
* Result is the header name and value stored there. */
case HPACK_ENTRY_STATE_INDEXED: {
bool complete = false;
uint64_t *index = &context->progress_entry.u.indexed.index;
if (aws_hpack_decode_integer(context, to_decode, 7, index, &complete)) {
return AWS_OP_ERR;
}
if (!complete) {
break;
}
const struct aws_http_header *header = s_get_header_u64(context, *index);
if (!header) {
return AWS_OP_ERR;
}
result->type = AWS_HPACK_DECODE_T_HEADER_FIELD;
result->data.header_field = *header;
goto handle_complete;
} break;
/* RFC-7541 6.2. Literal Header Field Representation.
* We use multiple states to decode a literal...
* The header-name MAY come from the table and MAY be encoded as a string.
* The header-value is ALWAYS encoded as a string.
*
* This BEGIN state decodes one integer.
* If it's non-zero, then it's the index in the table where we'll get the header-name from.
* If it's zero, then we move to the HEADER_NAME state and decode header-name as a string instead */
case HPACK_ENTRY_STATE_LITERAL_BEGIN: {
struct hpack_progress_literal *literal = &context->progress_entry.u.literal;
bool index_complete = false;
if (aws_hpack_decode_integer(
context, to_decode, literal->prefix_size, &literal->name_index, &index_complete)) {
return AWS_OP_ERR;
}
if (!index_complete) {
break;
}
if (literal->name_index == 0) {
/* Index 0 means header-name is not in table. Need to decode header-name as a string instead */
context->progress_entry.state = HPACK_ENTRY_STATE_LITERAL_NAME_STRING;
break;
}
/* Otherwise we found index of header-name in table. */
const struct aws_http_header *header = s_get_header_u64(context, literal->name_index);
if (!header) {
return AWS_OP_ERR;
}
/* Store the name in scratch. We don't just keep a pointer to it because it could be
* evicted from the dynamic table later, when we save the literal. */
if (aws_byte_buf_append_dynamic(&context->progress_entry.scratch, &header->name)) {
return AWS_OP_ERR;
}
/* Move on to decoding header-value.
* Value will also decode into the scratch, so save where name ends. */
literal->name_length = header->name.len;
context->progress_entry.state = HPACK_ENTRY_STATE_LITERAL_VALUE_STRING;
} break;
/* We only end up in this state if header-name is encoded as string. */
case HPACK_ENTRY_STATE_LITERAL_NAME_STRING: {
bool string_complete = false;
if (aws_hpack_decode_string(context, to_decode, &context->progress_entry.scratch, &string_complete)) {
return AWS_OP_ERR;
}
if (!string_complete) {
break;
}
/* Done decoding name string! Move on to decoding the value string.
* Value will also decode into the scratch, so save where name ends. */
context->progress_entry.u.literal.name_length = context->progress_entry.scratch.len;
context->progress_entry.state = HPACK_ENTRY_STATE_LITERAL_VALUE_STRING;
} break;
/* Final state for "literal" entries.
* Decode the header-value string, then deliver the results. */
case HPACK_ENTRY_STATE_LITERAL_VALUE_STRING: {
bool string_complete = false;
if (aws_hpack_decode_string(context, to_decode, &context->progress_entry.scratch, &string_complete)) {
return AWS_OP_ERR;
}
if (!string_complete) {
break;
}
/* Done decoding value string. Done decoding entry. */
struct hpack_progress_literal *literal = &context->progress_entry.u.literal;
/* Set up a header with name and value (which are packed one after the other in scratch) */
struct aws_http_header header;
header.value = aws_byte_cursor_from_buf(&context->progress_entry.scratch);
header.name = aws_byte_cursor_advance(&header.value, literal->name_length);
header.compression = literal->compression;
/* Save to table if necessary */
if (literal->compression == AWS_HTTP_HEADER_COMPRESSION_USE_CACHE) {
if (aws_hpack_insert_header(context, &header)) {
return AWS_OP_ERR;
}
}
result->type = AWS_HPACK_DECODE_T_HEADER_FIELD;
result->data.header_field = header;
goto handle_complete;
} break;
/* RFC-7541 6.3. Dynamic Table Size Update
* Read one integer, which is the new maximum size for the dynamic table. */
case HPACK_ENTRY_STATE_DYNAMIC_TABLE_RESIZE: {
uint64_t *size64 = &context->progress_entry.u.dynamic_table_resize.size;
bool size_complete = false;
if (aws_hpack_decode_integer(context, to_decode, 5, size64, &size_complete)) {
return AWS_OP_ERR;
}
if (!size_complete) {
break;
}
/* The new maximum size MUST be lower than or equal to the limit determined by the protocol using HPACK.
* A value that exceeds this limit MUST be treated as a decoding error. */
if (*size64 > context->dynamic_table.protocol_max_size_setting) {
HPACK_LOG(ERROR, context, "Dynamic table update size is larger than the protocal setting");
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
size_t size = (size_t)*size64;
HPACK_LOGF(TRACE, context, "Dynamic table size update %zu", size);
if (aws_hpack_resize_dynamic_table(context, size)) {
return AWS_OP_ERR;
}
result->type = AWS_HPACK_DECODE_T_DYNAMIC_TABLE_RESIZE;
result->data.dynamic_table_resize = size;
goto handle_complete;
} break;
default: {
AWS_ASSERT(0 && "invalid state");
} break;
}
}
AWS_ASSERT(to_decode->len == 0);
result->type = AWS_HPACK_DECODE_T_ONGOING;
return AWS_OP_SUCCESS;
handle_complete:
AWS_ASSERT(result->type != AWS_HPACK_DECODE_T_ONGOING);
context->progress_entry.state = HPACK_ENTRY_STATE_INIT;
return AWS_OP_SUCCESS;
}