From 397cffa4e91021244093ba4db908a308406d29c3 Mon Sep 17 00:00:00 2001 From: Michael Graeb Date: Mon, 1 Aug 2022 12:56:52 -0700 Subject: [PATCH] Split up hpack.c (#385) **Issue**: `aws_hpack_context` did too many things and was difficult to understand. **Description of changes:** Sorry, this looks like a lot of new code, but mostly just moving stuff around. Changes are: - Split `aws_hpack_context` (hpack.c) into 3 classes: - `aws_hpack_context` (hpack.c) - Still handles dynamic and static table logic. - `aws_hpack_encoder` (hpack_encoder.c) - Handles encoding of outgoing headers (contains an `aws_hpack_context`) - `aws_hpack_decoder` (hpack_encoder.c) - Handles decoding of incoming headers (contains an `aws_hpack_context`) - These structs are now `init()/clean_up()` by-value structs, they used to be `new()/delete()` heap-only structs. - This avoids an unnecessary allocation (total of 4 per HTTP connection) - These are package-private structs so don't really care that their internals are visible. --- include/aws/http/private/h2_frames.h | 3 +- include/aws/http/private/hpack.h | 241 ++++-- source/h2_decoder.c | 15 +- source/h2_frames.c | 13 +- source/hpack.c | 1008 +------------------------- source/hpack_decoder.c | 446 ++++++++++++ source/hpack_encoder.c | 418 +++++++++++ tests/fuzz/fuzz_h2_decoder_correct.c | 2 +- tests/test_h2_headers.c | 68 +- tests/test_hpack.c | 171 +++-- 10 files changed, 1212 insertions(+), 1173 deletions(-) create mode 100644 source/hpack_decoder.c create mode 100644 source/hpack_encoder.c diff --git a/include/aws/http/private/h2_frames.h b/include/aws/http/private/h2_frames.h index 6ffb16b0b..23c1daf1e 100644 --- a/include/aws/http/private/h2_frames.h +++ b/include/aws/http/private/h2_frames.h @@ -7,6 +7,7 @@ */ #include +#include #include #include @@ -106,7 +107,7 @@ struct aws_h2_frame { struct aws_h2_frame_encoder { struct aws_allocator *allocator; const void *logging_id; - struct aws_hpack_context *hpack; + struct aws_hpack_encoder hpack; struct aws_h2_frame *current_frame; /* Settings for frame encoder, which is based on the settings received from peer */ diff --git a/include/aws/http/private/hpack.h b/include/aws/http/private/hpack.h index 07b01b45c..d0507c2af 100644 --- a/include/aws/http/private/hpack.h +++ b/include/aws/http/private/hpack.h @@ -7,10 +7,8 @@ */ #include -struct aws_byte_buf; -struct aws_byte_cursor; -struct aws_http_header; -struct aws_hpack_context; +#include +#include /** * Result of aws_hpack_decode() call. @@ -47,48 +45,141 @@ enum aws_hpack_huffman_mode { AWS_HPACK_HUFFMAN_ALWAYS, }; +/** + * Maintains the dynamic table. + * Insertion is backwards, indexing is forwards + */ +struct aws_hpack_context { + struct aws_allocator *allocator; + + enum aws_http_log_subject log_subject; + const void *log_id; + + struct { + /* Array of headers, pointers to memory we alloced, which needs to be cleaned up whenever we move an entry out + */ + struct aws_http_header *buffer; + size_t buffer_capacity; /* Number of http_headers that can fit in buffer */ + + size_t num_elements; + size_t index_0; + + /* Size in bytes, according to [4.1] */ + size_t size; + size_t max_size; + + /* aws_http_header * -> size_t */ + struct aws_hash_table reverse_lookup; + /* aws_byte_cursor * -> size_t */ + struct aws_hash_table reverse_lookup_name_only; + } dynamic_table; +}; + +/** + * Encodes outgoing headers. + */ +struct aws_hpack_encoder { + const void *log_id; + + struct aws_huffman_encoder huffman_encoder; + enum aws_hpack_huffman_mode huffman_mode; + + struct aws_hpack_context context; + + struct { + size_t latest_value; + size_t smallest_value; + bool pending; + } dynamic_table_size_update; +}; + +/** + * Decodes incoming headers + */ +struct aws_hpack_decoder { + const void *log_id; + + struct aws_huffman_decoder huffman_decoder; + + struct aws_hpack_context context; + + /* TODO: check the new (RFC 9113 - 4.3.1) to make sure we did it right */ + /* SETTINGS_HEADER_TABLE_SIZE from http2 */ + size_t dynamic_table_protocol_max_size_setting; + + /* PRO TIP: Don't union progress_integer and progress_string together, since string_decode calls integer_decode */ + struct hpack_progress_integer { + enum { + HPACK_INTEGER_STATE_INIT, + HPACK_INTEGER_STATE_VALUE, + } state; + uint8_t bit_count; + } progress_integer; + + struct hpack_progress_string { + enum { + HPACK_STRING_STATE_INIT, + HPACK_STRING_STATE_LENGTH, + HPACK_STRING_STATE_VALUE, + } state; + bool use_huffman; + uint64_t length; + } progress_string; + + struct hpack_progress_entry { + enum { + HPACK_ENTRY_STATE_INIT, + /* Indexed header field: just 1 state. read index, find name and value at index */ + HPACK_ENTRY_STATE_INDEXED, + /* Literal header field: name may be indexed OR literal, value is always literal */ + HPACK_ENTRY_STATE_LITERAL_BEGIN, + HPACK_ENTRY_STATE_LITERAL_NAME_STRING, + HPACK_ENTRY_STATE_LITERAL_VALUE_STRING, + /* Dynamic table resize: just 1 state. read new size */ + HPACK_ENTRY_STATE_DYNAMIC_TABLE_RESIZE, + /* Done */ + HPACK_ENTRY_STATE_COMPLETE, + } state; + + union { + struct { + uint64_t index; + } indexed; + + struct hpack_progress_literal { + uint8_t prefix_size; + enum aws_http_header_compression compression; + uint64_t name_index; + size_t name_length; + } literal; + + struct { + uint64_t size; + } dynamic_table_resize; + } u; + + enum aws_hpack_decode_type type; + + /* Scratch holds header name and value while decoding */ + struct aws_byte_buf scratch; + } progress_entry; +}; + AWS_EXTERN_C_BEGIN /* Library-level init and shutdown */ -AWS_HTTP_API void aws_hpack_static_table_init(struct aws_allocator *allocator); - -AWS_HTTP_API void aws_hpack_static_table_clean_up(void); -/* General HPACK API */ AWS_HTTP_API -struct aws_hpack_context *aws_hpack_context_new( +void aws_hpack_context_init( + struct aws_hpack_context *aws_hpack_context, struct aws_allocator *allocator, enum aws_http_log_subject log_subject, const void *log_id); AWS_HTTP_API -void aws_hpack_context_destroy(struct aws_hpack_context *context); - -/** - * Decode the next entry in the header-block-fragment. - * If result->type is ONGOING, then call decode() again with more data to resume decoding. - * Otherwise, type is either a HEADER_FIELD or a DYNAMIC_TABLE_RESIZE. - * - * If an error occurs, the decoder is broken and decode() must not be called again. - */ -AWS_HTTP_API -int aws_hpack_decode( - struct aws_hpack_context *context, - struct aws_byte_cursor *to_decode, - struct aws_hpack_decode_result *result); - -/** - * Encode header-block into the output. - * This function will mutate the hpack context, so an error means the context can no longer be used. - * Note that output will be dynamically resized if it's too short. - */ -AWS_HTTP_API -int aws_hpack_encode_header_block( - struct aws_hpack_context *context, - const struct aws_http_headers *headers, - struct aws_byte_buf *output); +void aws_hpack_context_clean_up(struct aws_hpack_context *context); /* Returns the hpack size of a header (name.len + value.len + 32) [4.1] */ AWS_HTTP_API @@ -98,8 +189,11 @@ size_t aws_hpack_get_header_size(const struct aws_http_header *header); AWS_HTTP_API size_t aws_hpack_get_dynamic_table_num_elements(const struct aws_hpack_context *context); +size_t aws_hpack_get_dynamic_table_max_size(const struct aws_hpack_context *context); + AWS_HTTP_API const struct aws_http_header *aws_hpack_get_header(const struct aws_hpack_context *context, size_t index); + /* A return value of 0 indicates that the header wasn't found */ AWS_HTTP_API size_t aws_hpack_find_index( @@ -117,44 +211,83 @@ int aws_hpack_insert_header(struct aws_hpack_context *context, const struct aws_ AWS_HTTP_API int aws_hpack_resize_dynamic_table(struct aws_hpack_context *context, size_t new_max_size); -/* When setting for table size changes, call this function to memorize all updates between the transmission of - * two header blocks. The dynamic table resize and the dynamic table size update entry will be handled properly when we - * encode the next header block */ AWS_HTTP_API -void aws_hpack_set_max_table_size(struct aws_hpack_context *context, uint32_t new_max_size); +void aws_hpack_encoder_init(struct aws_hpack_encoder *encoder, struct aws_allocator *allocator, const void *log_id); AWS_HTTP_API -void aws_hpack_set_protocol_max_size_setting(struct aws_hpack_context *context, uint32_t setting_max_size); +void aws_hpack_encoder_clean_up(struct aws_hpack_encoder *encoder); +/* Call this after receiving SETTINGS_HEADER_TABLE_SIZE from peer and sending the ACK. + * The hpack-encoder remembers all size updates, and makes sure to encode the proper + * number of Dynamic Table Size Updates the next time a header block is sent. */ AWS_HTTP_API -void aws_hpack_set_huffman_mode(struct aws_hpack_context *context, enum aws_hpack_huffman_mode mode); +void aws_hpack_encoder_update_max_table_size(struct aws_hpack_encoder *encoder, uint32_t new_max_size); -/* Public for testing purposes. - * Output will be dynamically resized if it's too short */ AWS_HTTP_API -int aws_hpack_encode_integer(uint64_t integer, uint8_t starting_bits, uint8_t prefix_size, struct aws_byte_buf *output); +void aws_hpack_encoder_set_huffman_mode(struct aws_hpack_encoder *encoder, enum aws_hpack_huffman_mode mode); + +/** + * Encode header-block into the output. + * This function will mutate hpack, so an error means hpack can no longer be used. + * Note that output will be dynamically resized if it's too short. + */ +AWS_HTTP_API +int aws_hpack_encode_header_block( + struct aws_hpack_encoder *encoder, + const struct aws_http_headers *headers, + struct aws_byte_buf *output); -/* Public for testing purposes */ AWS_HTTP_API -int aws_hpack_decode_integer( - struct aws_hpack_context *context, +void aws_hpack_decoder_init(struct aws_hpack_decoder *decoder, struct aws_allocator *allocator, const void *log_id); + +AWS_HTTP_API +void aws_hpack_decoder_clean_up(struct aws_hpack_decoder *decoder); + +/* Call this after sending SETTINGS_HEADER_TABLE_SIZE and receiving ACK from the peer. + * The hpack-decoder remembers all size updates, and makes sure that the peer + * sends the appropriate Dynamic Table Size Updates in the next header block we receive. */ +AWS_HTTP_API +void aws_hpack_decoder_update_max_table_size(struct aws_hpack_decoder *decoder, uint32_t new_max_size); + +/** + * Decode the next entry in the header-block-fragment. + * If result->type is ONGOING, then call decode() again with more data to resume decoding. + * Otherwise, type is either a HEADER_FIELD or a DYNAMIC_TABLE_RESIZE. + * + * If an error occurs, the decoder is broken and decode() must not be called again. + */ +AWS_HTTP_API +int aws_hpack_decode( + struct aws_hpack_decoder *decoder, struct aws_byte_cursor *to_decode, - uint8_t prefix_size, - uint64_t *integer, - bool *complete); + struct aws_hpack_decode_result *result); + +/******************************************************************************* + * Private functions for encoder/decoder, but public for testing purposes + ******************************************************************************/ + +/* Output will be dynamically resized if it's too short */ +AWS_HTTP_API +int aws_hpack_encode_integer(uint64_t integer, uint8_t starting_bits, uint8_t prefix_size, struct aws_byte_buf *output); -/* Public for testing purposes. - * Output will be dynamically resized if it's too short */ +/* Output will be dynamically resized if it's too short */ AWS_HTTP_API int aws_hpack_encode_string( - struct aws_hpack_context *context, + struct aws_hpack_encoder *encoder, struct aws_byte_cursor to_encode, struct aws_byte_buf *output); -/* Public for testing purposes */ +AWS_HTTP_API +int aws_hpack_decode_integer( + struct aws_hpack_decoder *decoder, + struct aws_byte_cursor *to_decode, + uint8_t prefix_size, + uint64_t *integer, + bool *complete); + AWS_HTTP_API int aws_hpack_decode_string( - struct aws_hpack_context *context, + struct aws_hpack_decoder *decoder, struct aws_byte_cursor *to_decode, struct aws_byte_buf *output, bool *complete); diff --git a/source/h2_decoder.c b/source/h2_decoder.c index 0f1024363..0d62f75b7 100644 --- a/source/h2_decoder.c +++ b/source/h2_decoder.c @@ -193,7 +193,7 @@ struct aws_h2_decoder { /* Implementation data. */ struct aws_allocator *alloc; const void *logging_id; - struct aws_hpack_context *hpack; + struct aws_hpack_decoder hpack; bool is_server; struct aws_byte_buf scratch; const struct decoder_state *state; @@ -313,10 +313,7 @@ struct aws_h2_decoder *aws_h2_decoder_new(struct aws_h2_decoder_params *params) decoder->scratch = aws_byte_buf_from_empty_array(scratch_buf, s_scratch_space_size); - decoder->hpack = aws_hpack_context_new(params->alloc, AWS_LS_HTTP_DECODER, decoder); - if (!decoder->hpack) { - goto error; - } + aws_hpack_decoder_init(&decoder->hpack, params->alloc, decoder); if (decoder->is_server && !params->skip_connection_preface) { decoder->state = &s_state_connection_preface_string; @@ -342,7 +339,7 @@ struct aws_h2_decoder *aws_h2_decoder_new(struct aws_h2_decoder_params *params) error: if (decoder) { - aws_hpack_context_destroy(decoder->hpack); + aws_hpack_decoder_clean_up(&decoder->hpack); aws_array_list_clean_up(&decoder->settings_buffer_list); aws_byte_buf_clean_up(&decoder->header_block_in_progress.cookies); } @@ -365,7 +362,7 @@ void aws_h2_decoder_destroy(struct aws_h2_decoder *decoder) { return; } aws_array_list_clean_up(&decoder->settings_buffer_list); - aws_hpack_context_destroy(decoder->hpack); + aws_hpack_decoder_clean_up(&decoder->hpack); s_reset_header_block_in_progress(decoder); aws_byte_buf_clean_up(&decoder->header_block_in_progress.cookies); aws_byte_buf_clean_up(&decoder->goaway_in_progress.debug_data); @@ -1488,7 +1485,7 @@ static struct aws_h2err s_state_fn_header_block_entry(struct aws_h2_decoder *dec const size_t prev_fragment_len = fragment.len; struct aws_hpack_decode_result result; - if (aws_hpack_decode(decoder->hpack, &fragment, &result)) { + if (aws_hpack_decode(&decoder->hpack, &fragment, &result)) { DECODER_LOGF(ERROR, decoder, "Error decoding header-block fragment: %s", aws_error_name(aws_last_error())); /* Any possible error from HPACK decoder (except OOM) is treated as a COMPRESSION error. */ @@ -1583,7 +1580,7 @@ static struct aws_h2err s_state_fn_connection_preface_string( void aws_h2_decoder_set_setting_header_table_size(struct aws_h2_decoder *decoder, uint32_t data) { /* Set the protocol_max_size_setting for hpack. */ - aws_hpack_set_protocol_max_size_setting(decoder->hpack, data); + aws_hpack_decoder_update_max_table_size(&decoder->hpack, data); } void aws_h2_decoder_set_setting_enable_push(struct aws_h2_decoder *decoder, uint32_t data) { diff --git a/source/h2_frames.c b/source/h2_frames.c index fb08b6724..21defb35d 100644 --- a/source/h2_frames.c +++ b/source/h2_frames.c @@ -4,7 +4,6 @@ */ #include -#include #include @@ -298,10 +297,7 @@ int aws_h2_frame_encoder_init( encoder->allocator = allocator; encoder->logging_id = logging_id; - encoder->hpack = aws_hpack_context_new(allocator, AWS_LS_HTTP_ENCODER, logging_id); - if (!encoder->hpack) { - return AWS_OP_ERR; - } + aws_hpack_encoder_init(&encoder->hpack, allocator, logging_id); encoder->settings.max_frame_size = aws_h2_settings_initial[AWS_HTTP2_SETTINGS_MAX_FRAME_SIZE]; return AWS_OP_SUCCESS; @@ -309,7 +305,7 @@ int aws_h2_frame_encoder_init( void aws_h2_frame_encoder_clean_up(struct aws_h2_frame_encoder *encoder) { AWS_PRECONDITION(encoder); - aws_hpack_context_destroy(encoder->hpack); + aws_hpack_encoder_clean_up(&encoder->hpack); } /*********************************************************************************************************************** @@ -755,7 +751,7 @@ static int s_frame_headers_encode( /* Pre-encode the entire header-block into another buffer * the first time we're called. */ if (frame->state == AWS_H2_HEADERS_STATE_INIT) { - if (aws_hpack_encode_header_block(encoder->hpack, frame->headers, &frame->whole_encoded_header_block)) { + if (aws_hpack_encode_header_block(&encoder->hpack, frame->headers, &frame->whole_encoded_header_block)) { ENCODER_LOGF( ERROR, encoder, @@ -1229,8 +1225,7 @@ int aws_h2_encode_frame( void aws_h2_frame_encoder_set_setting_header_table_size(struct aws_h2_frame_encoder *encoder, uint32_t data) { /* Setting for dynamic table size changed from peer, we will update the dynamic table size when we encoder the next * header block */ - aws_hpack_set_max_table_size(encoder->hpack, data); - aws_hpack_set_protocol_max_size_setting(encoder->hpack, data); + aws_hpack_encoder_update_max_table_size(&encoder->hpack, data); } void aws_h2_frame_encoder_set_setting_max_frame_size(struct aws_h2_frame_encoder *encoder, uint32_t data) { diff --git a/source/hpack.c b/source/hpack.c index 0c8863029..ef3d0b3dc 100644 --- a/source/hpack.c +++ b/source/hpack.c @@ -4,121 +4,19 @@ */ #include -#include - -#include - -#include -#include -#include -#include - -#include - -/* #TODO split hpack encoder/decoder into different types */ - /* #TODO test empty strings */ +/* #TODO remove all OOM error handling in HTTP/2 & HPACK. make functions void if possible */ + /* RFC-7540 6.5.2 */ const size_t s_hpack_dynamic_table_initial_size = 4096; const size_t s_hpack_dynamic_table_initial_elements = 512; -/* TBD */ +/* TODO: shouldn't be a hardcoded max_size, it should be driven by SETTINGS_HEADER_TABLE_SIZE */ const size_t s_hpack_dynamic_table_max_size = 16 * 1024 * 1024; /* Used for growing the dynamic table buffer when it fills up */ const float s_hpack_dynamic_table_buffer_growth_rate = 1.5F; -/* Used while decoding the header name & value, grows if necessary */ -const size_t s_hpack_decoder_scratch_initial_size = 512; - -struct aws_huffman_symbol_coder *hpack_get_coder(void); - -/* Return a byte with the N right-most bits masked. - * Ex: 2 -> 00000011 */ -static uint8_t s_masked_right_bits_u8(uint8_t num_masked_bits) { - AWS_ASSERT(num_masked_bits <= 8); - const uint8_t cut_bits = 8 - num_masked_bits; - return UINT8_MAX >> cut_bits; -} - -static int s_append_u8_dynamic(struct aws_byte_buf *output, uint8_t u8) { - struct aws_byte_cursor cursor = aws_byte_cursor_from_array(&u8, 1); - return aws_byte_buf_append_dynamic(output, &cursor); -} - -/* If buffer isn't big enough, grow it intelligently */ -static int s_ensure_space(struct aws_byte_buf *output, size_t required_space) { - size_t available_space = output->capacity - output->len; - if (required_space <= available_space) { - return AWS_OP_SUCCESS; - } - - /* Capacity must grow to at least this size */ - size_t required_capacity; - if (aws_add_size_checked(output->len, required_space, &required_capacity)) { - return AWS_OP_ERR; - } - - /* Prefer to double capacity, but if that's not enough grow to exactly required_capacity */ - size_t double_capacity = aws_add_size_saturating(output->capacity, output->capacity); - size_t reserve = aws_max_size(required_capacity, double_capacity); - return aws_byte_buf_reserve(output, reserve); -} - -int aws_hpack_encode_integer( - uint64_t integer, - uint8_t starting_bits, - uint8_t prefix_size, - struct aws_byte_buf *output) { - AWS_ASSERT(prefix_size <= 8); - - const uint8_t prefix_mask = s_masked_right_bits_u8(prefix_size); - AWS_ASSERT((starting_bits & prefix_mask) == 0); - - const size_t original_len = output->len; - - if (integer < prefix_mask) { - /* If the integer fits inside the specified number of bits but won't be all 1's, just write it */ - - /* Just write out the bits we care about */ - uint8_t first_byte = starting_bits | (uint8_t)integer; - if (s_append_u8_dynamic(output, first_byte)) { - goto error; - } - } else { - /* Set all of the bits in the first octet to 1 */ - uint8_t first_byte = starting_bits | prefix_mask; - if (s_append_u8_dynamic(output, first_byte)) { - goto error; - } - - integer -= prefix_mask; - - const uint64_t hi_57bit_mask = UINT64_MAX - (UINT8_MAX >> 1); - - do { - /* Take top 7 bits from the integer */ - uint8_t this_octet = integer % 128; - if (integer & hi_57bit_mask) { - /* If there's more after this octet, set the hi bit */ - this_octet += 128; - } - - if (s_append_u8_dynamic(output, this_octet)) { - goto error; - } - - /* Remove the written bits */ - integer >>= 7; - } while (integer); - } - - return AWS_OP_SUCCESS; -error: - output->len = original_len; - return AWS_OP_ERR; -} - struct aws_http_header s_static_header_table[] = { #define HEADER(_index, _name) \ [_index] = { \ @@ -210,186 +108,44 @@ void aws_hpack_static_table_clean_up() { aws_hash_table_clean_up(&s_static_header_reverse_lookup_name_only); } -/* Insertion is backwards, indexing is forwards */ -struct aws_hpack_context { - struct aws_allocator *allocator; - - enum aws_hpack_huffman_mode huffman_mode; - enum aws_http_log_subject log_subject; - const void *log_id; - - struct aws_huffman_encoder encoder; - struct aws_huffman_decoder decoder; - - struct { - size_t last_value; - size_t smallest_value; - bool pending; - } dynamic_table_size_update; - - struct { - /* Array of headers, pointers to memory we alloced, which needs to be cleaned up whenever we move an entry out - */ - struct aws_http_header *buffer; - size_t buffer_capacity; /* Number of http_headers that can fit in buffer */ - - size_t num_elements; - size_t index_0; - - /* Size in bytes, according to [4.1] */ - size_t size; - size_t max_size; - - /* TODO: check the new (RFC 9113 - 4.3.1) to make sure we did it right */ - /* SETTINGS_HEADER_TABLE_SIZE from http2 */ - size_t protocol_max_size_setting; - /* aws_http_header * -> size_t */ - struct aws_hash_table reverse_lookup; - /* aws_byte_cursor * -> size_t */ - struct aws_hash_table reverse_lookup_name_only; - } dynamic_table; - - /* PRO TIP: Don't union these, since string_decode calls integer_decode */ - struct hpack_progress_integer { - enum { - HPACK_INTEGER_STATE_INIT, - HPACK_INTEGER_STATE_VALUE, - } state; - uint8_t bit_count; - } progress_integer; - struct hpack_progress_string { - enum { - HPACK_STRING_STATE_INIT, - HPACK_STRING_STATE_LENGTH, - HPACK_STRING_STATE_VALUE, - } state; - bool use_huffman; - uint64_t length; - } progress_string; - - struct hpack_progress_entry { - enum { - HPACK_ENTRY_STATE_INIT, - /* Indexed header field: just 1 state. read index, find name and value at index */ - HPACK_ENTRY_STATE_INDEXED, - /* Literal header field: name may be indexed OR literal, value is always literal */ - HPACK_ENTRY_STATE_LITERAL_BEGIN, - HPACK_ENTRY_STATE_LITERAL_NAME_STRING, - HPACK_ENTRY_STATE_LITERAL_VALUE_STRING, - /* Dynamic table resize: just 1 state. read new size */ - HPACK_ENTRY_STATE_DYNAMIC_TABLE_RESIZE, - /* Done */ - HPACK_ENTRY_STATE_COMPLETE, - } state; - - union { - struct { - uint64_t index; - } indexed; - - struct hpack_progress_literal { - uint8_t prefix_size; - enum aws_http_header_compression compression; - uint64_t name_index; - size_t name_length; - } literal; - - struct { - uint64_t size; - } dynamic_table_resize; - } u; - - enum aws_hpack_decode_type type; - - /* Scratch holds header name and value while decoding */ - struct aws_byte_buf scratch; - } progress_entry; -}; - #define HPACK_LOGF(level, hpack, text, ...) \ AWS_LOGF_##level((hpack)->log_subject, "id=%p [HPACK]: " text, (hpack)->log_id, __VA_ARGS__) #define HPACK_LOG(level, hpack, text) HPACK_LOGF(level, hpack, "%s", text) -struct aws_hpack_context *aws_hpack_context_new( +void aws_hpack_context_init( + struct aws_hpack_context *context, struct aws_allocator *allocator, enum aws_http_log_subject log_subject, const void *log_id) { - struct aws_hpack_context *context = aws_mem_calloc(allocator, 1, sizeof(struct aws_hpack_context)); - if (!context) { - return NULL; - } + AWS_ZERO_STRUCT(*context); context->allocator = allocator; - context->huffman_mode = AWS_HPACK_HUFFMAN_SMALLEST; context->log_subject = log_subject; context->log_id = log_id; - /* Initialize the huffman coders */ - struct aws_huffman_symbol_coder *hpack_coder = hpack_get_coder(); - aws_huffman_encoder_init(&context->encoder, hpack_coder); - aws_huffman_decoder_init(&context->decoder, hpack_coder); - aws_huffman_decoder_allow_growth(&context->decoder, true); - - /* #TODO Rewrite to be based on octet-size instead of list-size */ - /* Initialize dynamic table */ context->dynamic_table.max_size = s_hpack_dynamic_table_initial_size; - /* Initial header table size for http2 setting is the same as initial size for dynamic table */ - context->dynamic_table.protocol_max_size_setting = s_hpack_dynamic_table_initial_size; context->dynamic_table.buffer_capacity = s_hpack_dynamic_table_initial_elements; context->dynamic_table.buffer = aws_mem_calloc(allocator, context->dynamic_table.buffer_capacity, sizeof(struct aws_http_header)); - if (!context->dynamic_table.buffer) { - goto dynamic_table_buffer_failed; - } - - context->dynamic_table_size_update.pending = false; - context->dynamic_table_size_update.last_value = SIZE_MAX; - context->dynamic_table_size_update.smallest_value = SIZE_MAX; - - if (aws_hash_table_init( - &context->dynamic_table.reverse_lookup, - allocator, - s_hpack_dynamic_table_initial_elements, - s_header_hash, - s_header_eq, - NULL, - NULL)) { - goto reverse_lookup_failed; - } - - if (aws_hash_table_init( - &context->dynamic_table.reverse_lookup_name_only, - allocator, - s_hpack_dynamic_table_initial_elements, - aws_hash_byte_cursor_ptr, - (aws_hash_callback_eq_fn *)aws_byte_cursor_eq, - NULL, - NULL)) { - goto name_only_failed; - } - - if (aws_byte_buf_init(&context->progress_entry.scratch, allocator, s_hpack_decoder_scratch_initial_size)) { - goto scratch_failed; - } - - return context; - -scratch_failed: - aws_hash_table_clean_up(&context->dynamic_table.reverse_lookup_name_only); - -name_only_failed: - aws_hash_table_clean_up(&context->dynamic_table.reverse_lookup); -reverse_lookup_failed: - if (context->dynamic_table.buffer) { - aws_mem_release(allocator, context->dynamic_table.buffer); - } - -dynamic_table_buffer_failed: - aws_mem_release(allocator, context); + aws_hash_table_init( + &context->dynamic_table.reverse_lookup, + allocator, + s_hpack_dynamic_table_initial_elements, + s_header_hash, + s_header_eq, + NULL, + NULL); - return NULL; + aws_hash_table_init( + &context->dynamic_table.reverse_lookup_name_only, + allocator, + s_hpack_dynamic_table_initial_elements, + aws_hash_byte_cursor_ptr, + (aws_hash_callback_eq_fn *)aws_byte_cursor_eq, + NULL, + NULL); } static struct aws_http_header *s_dynamic_table_get(const struct aws_hpack_context *context, size_t index); @@ -404,21 +160,13 @@ static void s_clean_up_dynamic_table_buffer(struct aws_hpack_context *context) { aws_mem_release(context->allocator, context->dynamic_table.buffer); } -void aws_hpack_context_destroy(struct aws_hpack_context *context) { - if (!context) { - return; - } +void aws_hpack_context_clean_up(struct aws_hpack_context *context) { if (context->dynamic_table.buffer) { s_clean_up_dynamic_table_buffer(context); } aws_hash_table_clean_up(&context->dynamic_table.reverse_lookup); aws_hash_table_clean_up(&context->dynamic_table.reverse_lookup_name_only); - aws_byte_buf_clean_up(&context->progress_entry.scratch); - aws_mem_release(context->allocator, context); -} - -void aws_hpack_set_huffman_mode(struct aws_hpack_context *context, enum aws_hpack_huffman_mode mode) { - context->huffman_mode = mode; + AWS_ZERO_STRUCT(*context); } size_t aws_hpack_get_header_size(const struct aws_http_header *header) { @@ -429,6 +177,10 @@ size_t aws_hpack_get_dynamic_table_num_elements(const struct aws_hpack_context * return context->dynamic_table.num_elements; } +size_t aws_hpack_get_dynamic_table_max_size(const struct aws_hpack_context *context) { + return context->dynamic_table.max_size; +} + /* * Gets the header from the dynamic table. * NOTE: This function only bounds checks on the buffer size, not the number of elements. @@ -456,16 +208,7 @@ const struct aws_http_header *aws_hpack_get_header(const struct aws_hpack_contex return s_dynamic_table_get(context, index - s_static_header_table_size); } -static const struct aws_http_header *s_get_header_u64(const struct aws_hpack_context *context, uint64_t index) { - if (index > SIZE_MAX) { - HPACK_LOG(ERROR, context, "Header index is absurdly large") - aws_raise_error(AWS_ERROR_INVALID_INDEX); - return NULL; - } - - return aws_hpack_get_header(context, (size_t)index); -} - +/* TODO: remove `bool search_value`, this option has no reason to exist */ size_t aws_hpack_find_index( const struct aws_hpack_context *context, const struct aws_http_header *header, @@ -479,6 +222,7 @@ size_t aws_hpack_find_index( /* Check name-and-value first in static table */ aws_hash_table_find(&s_static_header_reverse_lookup, header, &elem); if (elem) { + /* TODO: Maybe always set found_value to true? Who cares that the value is empty if they matched? */ /* If an element was found, check if it has a value */ *found_value = ((const struct aws_http_header *)elem->key)->value.len; return (size_t)elem->value; @@ -486,6 +230,7 @@ size_t aws_hpack_find_index( /* Check name-and-value in dynamic table */ aws_hash_table_find(&context->dynamic_table.reverse_lookup, header, &elem); if (elem) { + /* TODO: Maybe always set found_value to true? Who cares that the value is empty if they matched? */ *found_value = ((const struct aws_http_header *)elem->key)->value.len; goto trans_index_from_dynamic_table; } @@ -778,694 +523,3 @@ int aws_hpack_resize_dynamic_table(struct aws_hpack_context *context, size_t new error: return AWS_OP_ERR; } - -void aws_hpack_set_max_table_size(struct aws_hpack_context *context, uint32_t new_max_size) { - - if (!context->dynamic_table_size_update.pending) { - context->dynamic_table_size_update.pending = true; - } - context->dynamic_table_size_update.smallest_value = - aws_min_size(new_max_size, context->dynamic_table_size_update.smallest_value); - context->dynamic_table_size_update.last_value = new_max_size; -} - -void aws_hpack_set_protocol_max_size_setting(struct aws_hpack_context *context, uint32_t setting_max_size) { - context->dynamic_table.protocol_max_size_setting = setting_max_size; -} - -int aws_hpack_decode_integer( - struct aws_hpack_context *context, - struct aws_byte_cursor *to_decode, - uint8_t prefix_size, - uint64_t *integer, - bool *complete) { - - AWS_PRECONDITION(context); - AWS_PRECONDITION(to_decode); - AWS_PRECONDITION(prefix_size <= 8); - AWS_PRECONDITION(integer); - - const uint8_t prefix_mask = s_masked_right_bits_u8(prefix_size); - - struct hpack_progress_integer *progress = &context->progress_integer; - - while (to_decode->len) { - switch (progress->state) { - case HPACK_INTEGER_STATE_INIT: { - /* Read the first byte, and check whether this is it, or we need to continue */ - uint8_t byte = 0; - bool succ = aws_byte_cursor_read_u8(to_decode, &byte); - AWS_FATAL_ASSERT(succ); - - /* Cut the prefix */ - byte &= prefix_mask; - - /* No matter what, the first byte's value is always added to the integer */ - *integer = byte; - - if (byte != prefix_mask) { - goto handle_complete; - } - - progress->state = HPACK_INTEGER_STATE_VALUE; - } break; - - case HPACK_INTEGER_STATE_VALUE: { - uint8_t byte = 0; - bool succ = aws_byte_cursor_read_u8(to_decode, &byte); - AWS_FATAL_ASSERT(succ); - - uint64_t new_byte_value = (uint64_t)(byte & 127) << progress->bit_count; - if (*integer + new_byte_value < *integer) { - return aws_raise_error(AWS_ERROR_OVERFLOW_DETECTED); - } - *integer += new_byte_value; - - /* Check if we're done */ - if ((byte & 128) == 0) { - goto handle_complete; - } - - /* Increment the bit count */ - progress->bit_count += 7; - - /* 7 Bits are expected to be used, so if we get to the point where any of - * those bits can't be used it's a decoding error */ - if (progress->bit_count > 64 - 7) { - return aws_raise_error(AWS_ERROR_OVERFLOW_DETECTED); - } - } break; - } - } - - /* Fell out of data loop, must need more data */ - *complete = false; - return AWS_OP_SUCCESS; - -handle_complete: - AWS_ZERO_STRUCT(context->progress_integer); - *complete = true; - return AWS_OP_SUCCESS; -} - -int aws_hpack_encode_string( - struct aws_hpack_context *context, - struct aws_byte_cursor to_encode, - struct aws_byte_buf *output) { - - AWS_PRECONDITION(context); - AWS_PRECONDITION(aws_byte_cursor_is_valid(&to_encode)); - AWS_PRECONDITION(output); - - const size_t original_len = output->len; - - /* Determine length of encoded string (and whether or not to use huffman) */ - uint8_t use_huffman; - size_t str_length; - switch (context->huffman_mode) { - case AWS_HPACK_HUFFMAN_NEVER: - use_huffman = 0; - str_length = to_encode.len; - break; - - case AWS_HPACK_HUFFMAN_ALWAYS: - use_huffman = 1; - str_length = aws_huffman_get_encoded_length(&context->encoder, to_encode); - break; - - case AWS_HPACK_HUFFMAN_SMALLEST: - str_length = aws_huffman_get_encoded_length(&context->encoder, to_encode); - if (str_length < to_encode.len) { - use_huffman = 1; - } else { - str_length = to_encode.len; - use_huffman = 0; - } - break; - - default: - aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); - goto error; - } - - /* - * String literals are encoded like so (RFC-7541 5.2): - * H is whether or not data is huffman-encoded. - * - * 0 1 2 3 4 5 6 7 - * +---+---+---+---+---+---+---+---+ - * | H | String Length (7+) | - * +---+---------------------------+ - * | String Data (Length octets) | - * +-------------------------------+ - */ - - /* Encode string length */ - uint8_t starting_bits = use_huffman << 7; - if (aws_hpack_encode_integer(str_length, starting_bits, 7, output)) { - HPACK_LOGF(ERROR, context, "Error encoding HPACK integer: %s", aws_error_name(aws_last_error())); - goto error; - } - - /* Encode string data */ - if (str_length > 0) { - if (use_huffman) { - /* Huffman encoder doesn't grow buffer, so we ensure it's big enough here */ - if (s_ensure_space(output, str_length)) { - goto error; - } - - if (aws_huffman_encode(&context->encoder, &to_encode, output)) { - HPACK_LOGF(ERROR, context, "Error from Huffman encoder: %s", aws_error_name(aws_last_error())); - goto error; - } - - } else { - if (aws_byte_buf_append_dynamic(output, &to_encode)) { - goto error; - } - } - } - - return AWS_OP_SUCCESS; - -error: - output->len = original_len; - aws_huffman_encoder_reset(&context->encoder); - return AWS_OP_ERR; -} - -int aws_hpack_decode_string( - struct aws_hpack_context *context, - struct aws_byte_cursor *to_decode, - struct aws_byte_buf *output, - bool *complete) { - - AWS_PRECONDITION(context); - AWS_PRECONDITION(to_decode); - AWS_PRECONDITION(output); - AWS_PRECONDITION(complete); - - struct hpack_progress_string *progress = &context->progress_string; - - while (to_decode->len) { - switch (progress->state) { - case HPACK_STRING_STATE_INIT: { - /* Do init stuff */ - progress->state = HPACK_STRING_STATE_LENGTH; - progress->use_huffman = *to_decode->ptr >> 7; - aws_huffman_decoder_reset(&context->decoder); - /* fallthrough, since we didn't consume any data */ - } - /* FALLTHRU */ - case HPACK_STRING_STATE_LENGTH: { - bool length_complete = false; - if (aws_hpack_decode_integer(context, to_decode, 7, &progress->length, &length_complete)) { - return AWS_OP_ERR; - } - - if (!length_complete) { - goto handle_ongoing; - } - - if (progress->length == 0) { - goto handle_complete; - } - - if (progress->length > SIZE_MAX) { - return aws_raise_error(AWS_ERROR_OVERFLOW_DETECTED); - } - - progress->state = HPACK_STRING_STATE_VALUE; - } break; - - case HPACK_STRING_STATE_VALUE: { - /* Take either as much data as we need, or as much as we can */ - size_t to_process = aws_min_size((size_t)progress->length, to_decode->len); - progress->length -= to_process; - - struct aws_byte_cursor chunk = aws_byte_cursor_advance(to_decode, to_process); - - if (progress->use_huffman) { - if (aws_huffman_decode(&context->decoder, &chunk, output)) { - HPACK_LOGF(ERROR, context, "Error from Huffman decoder: %s", aws_error_name(aws_last_error())); - return AWS_OP_ERR; - } - - /* Decoder should consume all bytes we feed it. - * EOS (end-of-string) symbol could stop it early, but HPACK says to treat EOS as error. */ - if (chunk.len != 0) { - HPACK_LOG(ERROR, context, "Huffman encoded end-of-string symbol is illegal"); - return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); - } - } else { - if (aws_byte_buf_append_dynamic(output, &chunk)) { - return AWS_OP_ERR; - } - } - - /* If whole length consumed, we're done */ - if (progress->length == 0) { - /* #TODO Validate any padding bits left over in final byte of string. - * "A padding not corresponding to the most significant bits of the - * code for the EOS symbol MUST be treated as a decoding error" */ - - /* #TODO impose limits on string length */ - - goto handle_complete; - } - } break; - } - } - -handle_ongoing: - /* Fell out of to_decode loop, must still be in progress */ - AWS_ASSERT(to_decode->len == 0); - *complete = false; - return AWS_OP_SUCCESS; - -handle_complete: - AWS_ASSERT(context->progress_string.length == 0); - AWS_ZERO_STRUCT(context->progress_string); - *complete = true; - return AWS_OP_SUCCESS; -} - -/* Implements RFC-7541 Section 6 - Binary Format */ -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; -} - -/* All types that HPACK might encode/decode (RFC-7541 6 - Binary Format) */ -enum aws_hpack_entry_type { - AWS_HPACK_ENTRY_INDEXED_HEADER_FIELD, /* RFC-7541 6.1 */ - AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITH_INCREMENTAL_INDEXING, /* RFC-7541 6.2.1 */ - AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITHOUT_INDEXING, /* RFC-7541 6.2.2 */ - AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_NEVER_INDEXED, /* RFC-7541 6.2.3 */ - AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE, /* RFC-7541 6.3 */ - AWS_HPACK_ENTRY_TYPE_COUNT, -}; - -/** - * First byte each entry type looks like this (RFC-7541 6): - * The "xxxxx" part is the "N-bit prefix" of the entry's first encoded integer. - * - * 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 - */ -static const uint8_t s_hpack_entry_starting_bit_pattern[AWS_HPACK_ENTRY_TYPE_COUNT] = { - [AWS_HPACK_ENTRY_INDEXED_HEADER_FIELD] = 1 << 7, - [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITH_INCREMENTAL_INDEXING] = 1 << 6, - [AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE] = 1 << 5, - [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_NEVER_INDEXED] = 1 << 4, - [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITHOUT_INDEXING] = 0 << 4, -}; - -static const uint8_t s_hpack_entry_num_prefix_bits[AWS_HPACK_ENTRY_TYPE_COUNT] = { - [AWS_HPACK_ENTRY_INDEXED_HEADER_FIELD] = 7, - [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITH_INCREMENTAL_INDEXING] = 6, - [AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE] = 5, - [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_NEVER_INDEXED] = 4, - [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITHOUT_INDEXING] = 4, -}; - -static int s_convert_http_compression_to_literal_entry_type( - enum aws_http_header_compression compression, - enum aws_hpack_entry_type *out_entry_type) { - - switch (compression) { - case AWS_HTTP_HEADER_COMPRESSION_USE_CACHE: - *out_entry_type = AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITH_INCREMENTAL_INDEXING; - return AWS_OP_SUCCESS; - - case AWS_HTTP_HEADER_COMPRESSION_NO_CACHE: - *out_entry_type = AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITHOUT_INDEXING; - return AWS_OP_SUCCESS; - - case AWS_HTTP_HEADER_COMPRESSION_NO_FORWARD_CACHE: - *out_entry_type = AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_NEVER_INDEXED; - return AWS_OP_SUCCESS; - } - - return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); -} - -static int s_encode_header_field( - struct aws_hpack_context *context, - const struct aws_http_header *header, - struct aws_byte_buf *output) { - - AWS_PRECONDITION(context); - AWS_PRECONDITION(header); - AWS_PRECONDITION(output); - - size_t original_len = output->len; - - /* Search for header-field in tables */ - bool found_indexed_value; - size_t header_index = aws_hpack_find_index(context, header, true, &found_indexed_value); - - if (header->compression != AWS_HTTP_HEADER_COMPRESSION_USE_CACHE) { - /* If user doesn't want to use indexed value, then don't use it */ - found_indexed_value = false; - } - - if (header_index && found_indexed_value) { - /* Indexed header field */ - const enum aws_hpack_entry_type entry_type = AWS_HPACK_ENTRY_INDEXED_HEADER_FIELD; - - /* encode the one index (along with the entry type), and we're done! */ - uint8_t starting_bit_pattern = s_hpack_entry_starting_bit_pattern[entry_type]; - uint8_t num_prefix_bits = s_hpack_entry_num_prefix_bits[entry_type]; - if (aws_hpack_encode_integer(header_index, starting_bit_pattern, num_prefix_bits, output)) { - goto error; - } - - return AWS_OP_SUCCESS; - } - - /* Else, Literal header field... */ - - /* determine exactly which type of literal header-field to encode. */ - enum aws_hpack_entry_type literal_entry_type = AWS_HPACK_ENTRY_TYPE_COUNT; - if (s_convert_http_compression_to_literal_entry_type(header->compression, &literal_entry_type)) { - goto error; - } - - /* the entry type makes up the first few bits of the next integer we encode */ - uint8_t starting_bit_pattern = s_hpack_entry_starting_bit_pattern[literal_entry_type]; - uint8_t num_prefix_bits = s_hpack_entry_num_prefix_bits[literal_entry_type]; - - if (header_index) { - /* Literal header field, indexed name */ - - /* first encode the index of name */ - if (aws_hpack_encode_integer(header_index, starting_bit_pattern, num_prefix_bits, output)) { - goto error; - } - } else { - /* Literal header field, new name */ - - /* first encode index of 0 to indicate that header-name is not indexed */ - if (aws_hpack_encode_integer(0, starting_bit_pattern, num_prefix_bits, output)) { - goto error; - } - - /* next encode header-name string */ - if (aws_hpack_encode_string(context, header->name, output)) { - goto error; - } - } - - /* then encode header-value string, and we're done encoding! */ - if (aws_hpack_encode_string(context, header->value, output)) { - goto error; - } - - /* if "incremental indexing" type, insert header into the dynamic table. */ - if (AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITH_INCREMENTAL_INDEXING == literal_entry_type) { - if (aws_hpack_insert_header(context, header)) { - goto error; - } - } - - return AWS_OP_SUCCESS; -error: - output->len = original_len; - return AWS_OP_ERR; -} - -int aws_hpack_encode_header_block( - struct aws_hpack_context *context, - const struct aws_http_headers *headers, - struct aws_byte_buf *output) { - - /* Encode a dynamic table size update at the beginning of the first header-block - * following the change to the dynamic table size RFC-7541 4.2 */ - if (context->dynamic_table_size_update.pending) { - if (context->dynamic_table_size_update.smallest_value != context->dynamic_table_size_update.last_value) { - size_t smallest_update_value = context->dynamic_table_size_update.smallest_value; - HPACK_LOGF( - TRACE, context, "Encoding smallest dynamic table size update entry size: %zu", smallest_update_value); - if (aws_hpack_resize_dynamic_table(context, smallest_update_value)) { - HPACK_LOGF(ERROR, context, "Dynamic table resize failed, size: %zu", smallest_update_value); - return AWS_OP_ERR; - } - uint8_t starting_bit_pattern = s_hpack_entry_starting_bit_pattern[AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE]; - uint8_t num_prefix_bits = s_hpack_entry_num_prefix_bits[AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE]; - if (aws_hpack_encode_integer(smallest_update_value, starting_bit_pattern, num_prefix_bits, output)) { - HPACK_LOGF( - ERROR, - context, - "Integer encoding failed for table size update entry, integer: %zu", - smallest_update_value) - return AWS_OP_ERR; - } - } - size_t last_update_value = context->dynamic_table_size_update.last_value; - HPACK_LOGF(TRACE, context, "Encoding last dynamic table size update entry size: %zu", last_update_value); - if (aws_hpack_resize_dynamic_table(context, last_update_value)) { - HPACK_LOGF(ERROR, context, "Dynamic table resize failed, size: %zu", last_update_value); - return AWS_OP_ERR; - } - uint8_t starting_bit_pattern = s_hpack_entry_starting_bit_pattern[AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE]; - uint8_t num_prefix_bits = s_hpack_entry_num_prefix_bits[AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE]; - if (aws_hpack_encode_integer(last_update_value, starting_bit_pattern, num_prefix_bits, output)) { - HPACK_LOGF( - ERROR, context, "Integer encoding failed for table size update entry, integer: %zu", last_update_value) - return AWS_OP_ERR; - } - - context->dynamic_table_size_update.pending = false; - context->dynamic_table_size_update.last_value = SIZE_MAX; - context->dynamic_table_size_update.smallest_value = SIZE_MAX; - } - - const size_t num_headers = aws_http_headers_count(headers); - for (size_t i = 0; i < num_headers; ++i) { - struct aws_http_header header; - aws_http_headers_get_index(headers, i, &header); - if (s_encode_header_field(context, &header, output)) { - return AWS_OP_ERR; - } - } - - return AWS_OP_SUCCESS; -} diff --git a/source/hpack_decoder.c b/source/hpack_decoder.c new file mode 100644 index 000000000..5d148f613 --- /dev/null +++ b/source/hpack_decoder.c @@ -0,0 +1,446 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include + +#define HPACK_LOGF(level, decoder, text, ...) \ + AWS_LOGF_##level(AWS_LS_HTTP_DECODER, "id=%p [HPACK]: " text, (decoder)->log_id, __VA_ARGS__) +#define HPACK_LOG(level, decoder, text) HPACK_LOGF(level, decoder, "%s", text) + +struct aws_huffman_symbol_coder *hpack_get_coder(void); + +/* Used while decoding the header name & value, grows if necessary */ +const size_t s_hpack_decoder_scratch_initial_size = 512; + +void aws_hpack_decoder_init(struct aws_hpack_decoder *decoder, struct aws_allocator *allocator, const void *log_id) { + AWS_ZERO_STRUCT(*decoder); + decoder->log_id = log_id; + + aws_huffman_decoder_init(&decoder->huffman_decoder, hpack_get_coder()); + aws_huffman_decoder_allow_growth(&decoder->huffman_decoder, true); + + aws_hpack_context_init(&decoder->context, allocator, AWS_LS_HTTP_DECODER, log_id); + + aws_byte_buf_init(&decoder->progress_entry.scratch, allocator, s_hpack_decoder_scratch_initial_size); + + decoder->dynamic_table_protocol_max_size_setting = aws_hpack_get_dynamic_table_max_size(&decoder->context); +} + +void aws_hpack_decoder_clean_up(struct aws_hpack_decoder *decoder) { + aws_hpack_context_clean_up(&decoder->context); + aws_byte_buf_clean_up(&decoder->progress_entry.scratch); + AWS_ZERO_STRUCT(*decoder); +} + +static const struct aws_http_header *s_get_header_u64(const struct aws_hpack_decoder *decoder, uint64_t index) { + if (index > SIZE_MAX) { + HPACK_LOG(ERROR, decoder, "Header index is absurdly large") + aws_raise_error(AWS_ERROR_INVALID_INDEX); + return NULL; + } + + return aws_hpack_get_header(&decoder->context, (size_t)index); +} + +void aws_hpack_decoder_update_max_table_size(struct aws_hpack_decoder *decoder, uint32_t setting_max_size) { + decoder->dynamic_table_protocol_max_size_setting = setting_max_size; +} + +/* Return a byte with the N right-most bits masked. + * Ex: 2 -> 00000011 */ +static uint8_t s_masked_right_bits_u8(uint8_t num_masked_bits) { + AWS_ASSERT(num_masked_bits <= 8); + const uint8_t cut_bits = 8 - num_masked_bits; + return UINT8_MAX >> cut_bits; +} + +int aws_hpack_decode_integer( + struct aws_hpack_decoder *decoder, + struct aws_byte_cursor *to_decode, + uint8_t prefix_size, + uint64_t *integer, + bool *complete) { + + AWS_PRECONDITION(decoder); + AWS_PRECONDITION(to_decode); + AWS_PRECONDITION(prefix_size <= 8); + AWS_PRECONDITION(integer); + + const uint8_t prefix_mask = s_masked_right_bits_u8(prefix_size); + + struct hpack_progress_integer *progress = &decoder->progress_integer; + + while (to_decode->len) { + switch (progress->state) { + case HPACK_INTEGER_STATE_INIT: { + /* Read the first byte, and check whether this is it, or we need to continue */ + uint8_t byte = 0; + bool succ = aws_byte_cursor_read_u8(to_decode, &byte); + AWS_FATAL_ASSERT(succ); + + /* Cut the prefix */ + byte &= prefix_mask; + + /* No matter what, the first byte's value is always added to the integer */ + *integer = byte; + + if (byte != prefix_mask) { + goto handle_complete; + } + + progress->state = HPACK_INTEGER_STATE_VALUE; + } break; + + case HPACK_INTEGER_STATE_VALUE: { + uint8_t byte = 0; + bool succ = aws_byte_cursor_read_u8(to_decode, &byte); + AWS_FATAL_ASSERT(succ); + + uint64_t new_byte_value = (uint64_t)(byte & 127) << progress->bit_count; + if (*integer + new_byte_value < *integer) { + return aws_raise_error(AWS_ERROR_OVERFLOW_DETECTED); + } + *integer += new_byte_value; + + /* Check if we're done */ + if ((byte & 128) == 0) { + goto handle_complete; + } + + /* Increment the bit count */ + progress->bit_count += 7; + + /* 7 Bits are expected to be used, so if we get to the point where any of + * those bits can't be used it's a decoding error */ + if (progress->bit_count > 64 - 7) { + return aws_raise_error(AWS_ERROR_OVERFLOW_DETECTED); + } + } break; + } + } + + /* Fell out of data loop, must need more data */ + *complete = false; + return AWS_OP_SUCCESS; + +handle_complete: + AWS_ZERO_STRUCT(decoder->progress_integer); + *complete = true; + return AWS_OP_SUCCESS; +} + +int aws_hpack_decode_string( + struct aws_hpack_decoder *decoder, + struct aws_byte_cursor *to_decode, + struct aws_byte_buf *output, + bool *complete) { + + AWS_PRECONDITION(decoder); + AWS_PRECONDITION(to_decode); + AWS_PRECONDITION(output); + AWS_PRECONDITION(complete); + + struct hpack_progress_string *progress = &decoder->progress_string; + + while (to_decode->len) { + switch (progress->state) { + case HPACK_STRING_STATE_INIT: { + /* Do init stuff */ + progress->state = HPACK_STRING_STATE_LENGTH; + progress->use_huffman = *to_decode->ptr >> 7; + aws_huffman_decoder_reset(&decoder->huffman_decoder); + /* fallthrough, since we didn't consume any data */ + } + /* FALLTHRU */ + case HPACK_STRING_STATE_LENGTH: { + bool length_complete = false; + if (aws_hpack_decode_integer(decoder, to_decode, 7, &progress->length, &length_complete)) { + return AWS_OP_ERR; + } + + if (!length_complete) { + goto handle_ongoing; + } + + if (progress->length == 0) { + goto handle_complete; + } + + if (progress->length > SIZE_MAX) { + return aws_raise_error(AWS_ERROR_OVERFLOW_DETECTED); + } + + progress->state = HPACK_STRING_STATE_VALUE; + } break; + + case HPACK_STRING_STATE_VALUE: { + /* Take either as much data as we need, or as much as we can */ + size_t to_process = aws_min_size((size_t)progress->length, to_decode->len); + progress->length -= to_process; + + struct aws_byte_cursor chunk = aws_byte_cursor_advance(to_decode, to_process); + + if (progress->use_huffman) { + if (aws_huffman_decode(&decoder->huffman_decoder, &chunk, output)) { + HPACK_LOGF(ERROR, decoder, "Error from Huffman decoder: %s", aws_error_name(aws_last_error())); + return AWS_OP_ERR; + } + + /* Decoder should consume all bytes we feed it. + * EOS (end-of-string) symbol could stop it early, but HPACK says to treat EOS as error. */ + if (chunk.len != 0) { + HPACK_LOG(ERROR, decoder, "Huffman encoded end-of-string symbol is illegal"); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + } else { + if (aws_byte_buf_append_dynamic(output, &chunk)) { + return AWS_OP_ERR; + } + } + + /* If whole length consumed, we're done */ + if (progress->length == 0) { + /* #TODO Validate any padding bits left over in final byte of string. + * "A padding not corresponding to the most significant bits of the + * code for the EOS symbol MUST be treated as a decoding error" */ + + /* #TODO impose limits on string length */ + + goto handle_complete; + } + } break; + } + } + +handle_ongoing: + /* Fell out of to_decode loop, must still be in progress */ + AWS_ASSERT(to_decode->len == 0); + *complete = false; + return AWS_OP_SUCCESS; + +handle_complete: + AWS_ASSERT(decoder->progress_string.length == 0); + AWS_ZERO_STRUCT(decoder->progress_string); + *complete = true; + return AWS_OP_SUCCESS; +} + +/* Implements RFC-7541 Section 6 - Binary Format */ +int aws_hpack_decode( + struct aws_hpack_decoder *decoder, + struct aws_byte_cursor *to_decode, + struct aws_hpack_decode_result *result) { + + AWS_PRECONDITION(decoder); + 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 (decoder->progress_entry.state) { + + case HPACK_ENTRY_STATE_INIT: { + /* Reset entry */ + AWS_ZERO_STRUCT(decoder->progress_entry.u); + decoder->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 */ + decoder->progress_entry.state = HPACK_ENTRY_STATE_INDEXED; + + } else if (first_byte & (1 << 6)) { + /* 01xxxxxx: Literal Header Field with Incremental Indexing */ + decoder->progress_entry.u.literal.compression = AWS_HTTP_HEADER_COMPRESSION_USE_CACHE; + decoder->progress_entry.u.literal.prefix_size = 6; + decoder->progress_entry.state = HPACK_ENTRY_STATE_LITERAL_BEGIN; + + } else if (first_byte & (1 << 5)) { + /* 001xxxxx: Dynamic Table Size Update */ + decoder->progress_entry.state = HPACK_ENTRY_STATE_DYNAMIC_TABLE_RESIZE; + + } else if (first_byte & (1 << 4)) { + /* 0001xxxx: Literal Header Field Never Indexed */ + decoder->progress_entry.u.literal.compression = AWS_HTTP_HEADER_COMPRESSION_NO_FORWARD_CACHE; + decoder->progress_entry.u.literal.prefix_size = 4; + decoder->progress_entry.state = HPACK_ENTRY_STATE_LITERAL_BEGIN; + } else { + /* 0000xxxx: Literal Header Field without Indexing */ + decoder->progress_entry.u.literal.compression = AWS_HTTP_HEADER_COMPRESSION_NO_CACHE; + decoder->progress_entry.u.literal.prefix_size = 4; + decoder->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 = &decoder->progress_entry.u.indexed.index; + if (aws_hpack_decode_integer(decoder, to_decode, 7, index, &complete)) { + return AWS_OP_ERR; + } + + if (!complete) { + break; + } + + const struct aws_http_header *header = s_get_header_u64(decoder, *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 = &decoder->progress_entry.u.literal; + + bool index_complete = false; + if (aws_hpack_decode_integer( + decoder, 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 */ + decoder->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(decoder, 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(&decoder->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; + decoder->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(decoder, to_decode, &decoder->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. */ + decoder->progress_entry.u.literal.name_length = decoder->progress_entry.scratch.len; + decoder->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(decoder, to_decode, &decoder->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 = &decoder->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(&decoder->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(&decoder->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 = &decoder->progress_entry.u.dynamic_table_resize.size; + bool size_complete = false; + if (aws_hpack_decode_integer(decoder, 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 > decoder->dynamic_table_protocol_max_size_setting) { + HPACK_LOG(ERROR, decoder, "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, decoder, "Dynamic table size update %zu", size); + if (aws_hpack_resize_dynamic_table(&decoder->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); + decoder->progress_entry.state = HPACK_ENTRY_STATE_INIT; + return AWS_OP_SUCCESS; +} diff --git a/source/hpack_encoder.c b/source/hpack_encoder.c new file mode 100644 index 000000000..abf1cec82 --- /dev/null +++ b/source/hpack_encoder.c @@ -0,0 +1,418 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include + +#define HPACK_LOGF(level, encoder, text, ...) \ + AWS_LOGF_##level(AWS_LS_HTTP_ENCODER, "id=%p [HPACK]: " text, (encoder)->log_id, __VA_ARGS__) +#define HPACK_LOG(level, encoder, text) HPACK_LOGF(level, encoder, "%s", text) + +struct aws_huffman_symbol_coder *hpack_get_coder(void); + +void aws_hpack_encoder_init(struct aws_hpack_encoder *encoder, struct aws_allocator *allocator, const void *log_id) { + + AWS_ZERO_STRUCT(*encoder); + encoder->log_id = log_id; + + aws_huffman_encoder_init(&encoder->huffman_encoder, hpack_get_coder()); + + aws_hpack_context_init(&encoder->context, allocator, AWS_LS_HTTP_ENCODER, log_id); + + encoder->dynamic_table_size_update.pending = false; + encoder->dynamic_table_size_update.latest_value = SIZE_MAX; + encoder->dynamic_table_size_update.smallest_value = SIZE_MAX; +} + +void aws_hpack_encoder_clean_up(struct aws_hpack_encoder *encoder) { + aws_hpack_context_clean_up(&encoder->context); + AWS_ZERO_STRUCT(*encoder); +} + +void aws_hpack_encoder_set_huffman_mode(struct aws_hpack_encoder *encoder, enum aws_hpack_huffman_mode mode) { + encoder->huffman_mode = mode; +} + +void aws_hpack_encoder_update_max_table_size(struct aws_hpack_encoder *encoder, uint32_t new_max_size) { + + if (!encoder->dynamic_table_size_update.pending) { + encoder->dynamic_table_size_update.pending = true; + } + encoder->dynamic_table_size_update.smallest_value = + aws_min_size(new_max_size, encoder->dynamic_table_size_update.smallest_value); + + /* TODO: don't necessarily go as high as possible. The peer said the encoder's + * dynamic table COULD get this big, but it's not required to. + * It's probably not a good idea to let the peer decide how much memory we allocate. + * Not sure how to cap it though... Use a hardcoded number? + * Match whatever SETTINGS_HEADER_TABLE_SIZE this side sends? */ + encoder->dynamic_table_size_update.latest_value = new_max_size; +} + +/* Return a byte with the N right-most bits masked. + * Ex: 2 -> 00000011 */ +static uint8_t s_masked_right_bits_u8(uint8_t num_masked_bits) { + AWS_ASSERT(num_masked_bits <= 8); + const uint8_t cut_bits = 8 - num_masked_bits; + return UINT8_MAX >> cut_bits; +} + +/* If buffer isn't big enough, grow it intelligently */ +static int s_ensure_space(struct aws_byte_buf *output, size_t required_space) { + size_t available_space = output->capacity - output->len; + if (required_space <= available_space) { + return AWS_OP_SUCCESS; + } + + /* Capacity must grow to at least this size */ + size_t required_capacity; + if (aws_add_size_checked(output->len, required_space, &required_capacity)) { + return AWS_OP_ERR; + } + + /* Prefer to double capacity, but if that's not enough grow to exactly required_capacity */ + size_t double_capacity = aws_add_size_saturating(output->capacity, output->capacity); + size_t reserve = aws_max_size(required_capacity, double_capacity); + return aws_byte_buf_reserve(output, reserve); +} + +int aws_hpack_encode_integer( + uint64_t integer, + uint8_t starting_bits, + uint8_t prefix_size, + struct aws_byte_buf *output) { + AWS_ASSERT(prefix_size <= 8); + + const uint8_t prefix_mask = s_masked_right_bits_u8(prefix_size); + AWS_ASSERT((starting_bits & prefix_mask) == 0); + + const size_t original_len = output->len; + + if (integer < prefix_mask) { + /* If the integer fits inside the specified number of bits but won't be all 1's, just write it */ + + /* Just write out the bits we care about */ + uint8_t first_byte = starting_bits | (uint8_t)integer; + if (aws_byte_buf_append_byte_dynamic(output, first_byte)) { + goto error; + } + } else { + /* Set all of the bits in the first octet to 1 */ + uint8_t first_byte = starting_bits | prefix_mask; + if (aws_byte_buf_append_byte_dynamic(output, first_byte)) { + goto error; + } + + integer -= prefix_mask; + + const uint64_t hi_57bit_mask = UINT64_MAX - (UINT8_MAX >> 1); + + do { + /* Take top 7 bits from the integer */ + uint8_t this_octet = integer % 128; + if (integer & hi_57bit_mask) { + /* If there's more after this octet, set the hi bit */ + this_octet += 128; + } + + if (aws_byte_buf_append_byte_dynamic(output, this_octet)) { + goto error; + } + + /* Remove the written bits */ + integer >>= 7; + } while (integer); + } + + return AWS_OP_SUCCESS; +error: + output->len = original_len; + return AWS_OP_ERR; +} + +int aws_hpack_encode_string( + struct aws_hpack_encoder *encoder, + struct aws_byte_cursor to_encode, + struct aws_byte_buf *output) { + + AWS_PRECONDITION(encoder); + AWS_PRECONDITION(aws_byte_cursor_is_valid(&to_encode)); + AWS_PRECONDITION(output); + + const size_t original_len = output->len; + + /* Determine length of encoded string (and whether or not to use huffman) */ + uint8_t use_huffman; + size_t str_length; + switch (encoder->huffman_mode) { + case AWS_HPACK_HUFFMAN_NEVER: + use_huffman = 0; + str_length = to_encode.len; + break; + + case AWS_HPACK_HUFFMAN_ALWAYS: + use_huffman = 1; + str_length = aws_huffman_get_encoded_length(&encoder->huffman_encoder, to_encode); + break; + + case AWS_HPACK_HUFFMAN_SMALLEST: + str_length = aws_huffman_get_encoded_length(&encoder->huffman_encoder, to_encode); + if (str_length < to_encode.len) { + use_huffman = 1; + } else { + str_length = to_encode.len; + use_huffman = 0; + } + break; + + default: + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + goto error; + } + + /* + * String literals are encoded like so (RFC-7541 5.2): + * H is whether or not data is huffman-encoded. + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | H | String Length (7+) | + * +---+---------------------------+ + * | String Data (Length octets) | + * +-------------------------------+ + */ + + /* Encode string length */ + uint8_t starting_bits = use_huffman << 7; + if (aws_hpack_encode_integer(str_length, starting_bits, 7, output)) { + HPACK_LOGF(ERROR, encoder, "Error encoding HPACK integer: %s", aws_error_name(aws_last_error())); + goto error; + } + + /* Encode string data */ + if (str_length > 0) { + if (use_huffman) { + /* Huffman encoder doesn't grow buffer, so we ensure it's big enough here */ + if (s_ensure_space(output, str_length)) { + goto error; + } + + if (aws_huffman_encode(&encoder->huffman_encoder, &to_encode, output)) { + HPACK_LOGF(ERROR, encoder, "Error from Huffman encoder: %s", aws_error_name(aws_last_error())); + goto error; + } + + } else { + if (aws_byte_buf_append_dynamic(output, &to_encode)) { + goto error; + } + } + } + + return AWS_OP_SUCCESS; + +error: + output->len = original_len; + aws_huffman_encoder_reset(&encoder->huffman_encoder); + return AWS_OP_ERR; +} + +/* All types that HPACK might encode/decode (RFC-7541 6 - Binary Format) */ +enum aws_hpack_entry_type { + AWS_HPACK_ENTRY_INDEXED_HEADER_FIELD, /* RFC-7541 6.1 */ + AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITH_INCREMENTAL_INDEXING, /* RFC-7541 6.2.1 */ + AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITHOUT_INDEXING, /* RFC-7541 6.2.2 */ + AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_NEVER_INDEXED, /* RFC-7541 6.2.3 */ + AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE, /* RFC-7541 6.3 */ + AWS_HPACK_ENTRY_TYPE_COUNT, +}; + +/** + * First byte each entry type looks like this (RFC-7541 6): + * The "xxxxx" part is the "N-bit prefix" of the entry's first encoded integer. + * + * 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 + */ +static const uint8_t s_hpack_entry_starting_bit_pattern[AWS_HPACK_ENTRY_TYPE_COUNT] = { + [AWS_HPACK_ENTRY_INDEXED_HEADER_FIELD] = 1 << 7, + [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITH_INCREMENTAL_INDEXING] = 1 << 6, + [AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE] = 1 << 5, + [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_NEVER_INDEXED] = 1 << 4, + [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITHOUT_INDEXING] = 0 << 4, +}; + +static const uint8_t s_hpack_entry_num_prefix_bits[AWS_HPACK_ENTRY_TYPE_COUNT] = { + [AWS_HPACK_ENTRY_INDEXED_HEADER_FIELD] = 7, + [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITH_INCREMENTAL_INDEXING] = 6, + [AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE] = 5, + [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_NEVER_INDEXED] = 4, + [AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITHOUT_INDEXING] = 4, +}; + +static int s_convert_http_compression_to_literal_entry_type( + enum aws_http_header_compression compression, + enum aws_hpack_entry_type *out_entry_type) { + + switch (compression) { + case AWS_HTTP_HEADER_COMPRESSION_USE_CACHE: + *out_entry_type = AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITH_INCREMENTAL_INDEXING; + return AWS_OP_SUCCESS; + + case AWS_HTTP_HEADER_COMPRESSION_NO_CACHE: + *out_entry_type = AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITHOUT_INDEXING; + return AWS_OP_SUCCESS; + + case AWS_HTTP_HEADER_COMPRESSION_NO_FORWARD_CACHE: + *out_entry_type = AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_NEVER_INDEXED; + return AWS_OP_SUCCESS; + } + + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); +} + +static int s_encode_header_field( + struct aws_hpack_encoder *encoder, + const struct aws_http_header *header, + struct aws_byte_buf *output) { + + AWS_PRECONDITION(encoder); + AWS_PRECONDITION(header); + AWS_PRECONDITION(output); + + size_t original_len = output->len; + + /* Search for header-field in tables */ + bool found_indexed_value; + size_t header_index = aws_hpack_find_index(&encoder->context, header, true, &found_indexed_value); + + if (header->compression != AWS_HTTP_HEADER_COMPRESSION_USE_CACHE) { + /* If user doesn't want to use indexed value, then don't use it */ + found_indexed_value = false; + } + + if (header_index && found_indexed_value) { + /* Indexed header field */ + const enum aws_hpack_entry_type entry_type = AWS_HPACK_ENTRY_INDEXED_HEADER_FIELD; + + /* encode the one index (along with the entry type), and we're done! */ + uint8_t starting_bit_pattern = s_hpack_entry_starting_bit_pattern[entry_type]; + uint8_t num_prefix_bits = s_hpack_entry_num_prefix_bits[entry_type]; + if (aws_hpack_encode_integer(header_index, starting_bit_pattern, num_prefix_bits, output)) { + goto error; + } + + return AWS_OP_SUCCESS; + } + + /* Else, Literal header field... */ + + /* determine exactly which type of literal header-field to encode. */ + enum aws_hpack_entry_type literal_entry_type = AWS_HPACK_ENTRY_TYPE_COUNT; + if (s_convert_http_compression_to_literal_entry_type(header->compression, &literal_entry_type)) { + goto error; + } + + /* the entry type makes up the first few bits of the next integer we encode */ + uint8_t starting_bit_pattern = s_hpack_entry_starting_bit_pattern[literal_entry_type]; + uint8_t num_prefix_bits = s_hpack_entry_num_prefix_bits[literal_entry_type]; + + if (header_index) { + /* Literal header field, indexed name */ + + /* first encode the index of name */ + if (aws_hpack_encode_integer(header_index, starting_bit_pattern, num_prefix_bits, output)) { + goto error; + } + } else { + /* Literal header field, new name */ + + /* first encode index of 0 to indicate that header-name is not indexed */ + if (aws_hpack_encode_integer(0, starting_bit_pattern, num_prefix_bits, output)) { + goto error; + } + + /* next encode header-name string */ + if (aws_hpack_encode_string(encoder, header->name, output)) { + goto error; + } + } + + /* then encode header-value string, and we're done encoding! */ + if (aws_hpack_encode_string(encoder, header->value, output)) { + goto error; + } + + /* if "incremental indexing" type, insert header into the dynamic table. */ + if (AWS_HPACK_ENTRY_LITERAL_HEADER_FIELD_WITH_INCREMENTAL_INDEXING == literal_entry_type) { + if (aws_hpack_insert_header(&encoder->context, header)) { + goto error; + } + } + + return AWS_OP_SUCCESS; +error: + output->len = original_len; + return AWS_OP_ERR; +} + +int aws_hpack_encode_header_block( + struct aws_hpack_encoder *encoder, + const struct aws_http_headers *headers, + struct aws_byte_buf *output) { + + /* Encode a dynamic table size update at the beginning of the first header-block + * following the change to the dynamic table size RFC-7541 4.2 */ + if (encoder->dynamic_table_size_update.pending) { + if (encoder->dynamic_table_size_update.smallest_value != encoder->dynamic_table_size_update.latest_value) { + size_t smallest_update_value = encoder->dynamic_table_size_update.smallest_value; + HPACK_LOGF( + TRACE, encoder, "Encoding smallest dynamic table size update entry size: %zu", smallest_update_value); + if (aws_hpack_resize_dynamic_table(&encoder->context, smallest_update_value)) { + HPACK_LOGF(ERROR, encoder, "Dynamic table resize failed, size: %zu", smallest_update_value); + return AWS_OP_ERR; + } + uint8_t starting_bit_pattern = s_hpack_entry_starting_bit_pattern[AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE]; + uint8_t num_prefix_bits = s_hpack_entry_num_prefix_bits[AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE]; + if (aws_hpack_encode_integer(smallest_update_value, starting_bit_pattern, num_prefix_bits, output)) { + HPACK_LOGF( + ERROR, + encoder, + "Integer encoding failed for table size update entry, integer: %zu", + smallest_update_value) + return AWS_OP_ERR; + } + } + size_t last_update_value = encoder->dynamic_table_size_update.latest_value; + HPACK_LOGF(TRACE, encoder, "Encoding last dynamic table size update entry size: %zu", last_update_value); + if (aws_hpack_resize_dynamic_table(&encoder->context, last_update_value)) { + HPACK_LOGF(ERROR, encoder, "Dynamic table resize failed, size: %zu", last_update_value); + return AWS_OP_ERR; + } + uint8_t starting_bit_pattern = s_hpack_entry_starting_bit_pattern[AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE]; + uint8_t num_prefix_bits = s_hpack_entry_num_prefix_bits[AWS_HPACK_ENTRY_DYNAMIC_TABLE_RESIZE]; + if (aws_hpack_encode_integer(last_update_value, starting_bit_pattern, num_prefix_bits, output)) { + HPACK_LOGF( + ERROR, encoder, "Integer encoding failed for table size update entry, integer: %zu", last_update_value) + return AWS_OP_ERR; + } + + encoder->dynamic_table_size_update.pending = false; + encoder->dynamic_table_size_update.latest_value = SIZE_MAX; + encoder->dynamic_table_size_update.smallest_value = SIZE_MAX; + } + + const size_t num_headers = aws_http_headers_count(headers); + for (size_t i = 0; i < num_headers; ++i) { + struct aws_http_header header; + aws_http_headers_get_index(headers, i, &header); + if (s_encode_header_field(encoder, &header, output)) { + return AWS_OP_ERR; + } + } + + return AWS_OP_SUCCESS; +} diff --git a/tests/fuzz/fuzz_h2_decoder_correct.c b/tests/fuzz/fuzz_h2_decoder_correct.c index d6ea3201c..779e53637 100644 --- a/tests/fuzz/fuzz_h2_decoder_correct.c +++ b/tests/fuzz/fuzz_h2_decoder_correct.c @@ -218,7 +218,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { /* figure out if we should use huffman encoding */ uint8_t huffman_choice = 0; aws_byte_cursor_read_u8(&input, &huffman_choice); - aws_hpack_set_huffman_mode(encoder.hpack, huffman_choice % 3); + aws_hpack_encoder_set_huffman_mode(&encoder.hpack, huffman_choice % 3); switch (frame_type) { case AWS_H2_FRAME_T_DATA: { diff --git a/tests/test_h2_headers.c b/tests/test_h2_headers.c index c3c94c624..c045ca1bc 100644 --- a/tests/test_h2_headers.c +++ b/tests/test_h2_headers.c @@ -50,8 +50,8 @@ struct header_test_fixture { struct aws_allocator *allocator; bool one_byte_at_a_time; /* T: decode one byte at a time. F: decode whole buffer at once */ - struct aws_hpack_context *encoder; - struct aws_hpack_context *decoder; + struct aws_hpack_encoder encoder; + struct aws_hpack_decoder decoder; struct aws_http_headers *headers_to_encode; struct aws_byte_buf expected_encoding_buf; @@ -65,10 +65,8 @@ static int s_header_test_before(struct aws_allocator *allocator, void *ctx) { aws_http_library_init(allocator); - fixture->encoder = aws_hpack_context_new(allocator, AWS_LS_HTTP_ENCODER, NULL); - ASSERT_NOT_NULL(fixture->encoder); - fixture->decoder = aws_hpack_context_new(allocator, AWS_LS_HTTP_DECODER, NULL); - ASSERT_NOT_NULL(fixture->decoder); + aws_hpack_encoder_init(&fixture->encoder, allocator, NULL); + aws_hpack_decoder_init(&fixture->decoder, allocator, NULL); fixture->headers_to_encode = aws_http_headers_new(allocator); ASSERT_NOT_NULL(fixture->headers_to_encode); @@ -93,7 +91,7 @@ static int s_header_test_run(struct aws_allocator *allocator, void *ctx) { ASSERT_SUCCESS(aws_byte_buf_init(&output_buffer, allocator, S_BUFFER_SIZE)); /* Encode the headers */ - ASSERT_SUCCESS(aws_hpack_encode_header_block(fixture->encoder, fixture->headers_to_encode, &output_buffer)); + ASSERT_SUCCESS(aws_hpack_encode_header_block(&fixture->encoder, fixture->headers_to_encode, &output_buffer)); /* Compare the encoded output against the expected header block fragment */ ASSERT_BIN_ARRAYS_EQUALS( @@ -109,10 +107,10 @@ static int s_header_test_run(struct aws_allocator *allocator, void *ctx) { if (fixture->one_byte_at_a_time) { struct aws_byte_cursor one_byte_payload = aws_byte_cursor_advance(&payload, 1); - ASSERT_SUCCESS(aws_hpack_decode(fixture->decoder, &one_byte_payload, &result)); + ASSERT_SUCCESS(aws_hpack_decode(&fixture->decoder, &one_byte_payload, &result)); ASSERT_UINT_EQUALS(0, one_byte_payload.len); } else { - ASSERT_SUCCESS(aws_hpack_decode(fixture->decoder, &payload, &result)); + ASSERT_SUCCESS(aws_hpack_decode(&fixture->decoder, &payload, &result)); } if (result.type == AWS_HPACK_DECODE_T_HEADER_FIELD) { @@ -143,8 +141,8 @@ static int s_header_test_after(struct aws_allocator *allocator, int setup_res, v aws_http_headers_release(fixture->decoded_headers); aws_byte_buf_clean_up(&fixture->expected_encoding_buf); aws_http_headers_release(fixture->headers_to_encode); - aws_hpack_context_destroy(fixture->decoder); - aws_hpack_context_destroy(fixture->encoder); + aws_hpack_decoder_clean_up(&fixture->decoder); + aws_hpack_encoder_clean_up(&fixture->encoder); } aws_http_library_clean_up(); @@ -185,7 +183,7 @@ HEADER_TEST(h2_header_empty_payload, s_test_empty_payload, NULL); /* RFC-7541 - Header Field Representation Examples - C.2.1. Literal Header Field with Indexing */ static int s_test_ex_2_1_init(struct header_test_fixture *fixture) { - aws_hpack_set_huffman_mode(fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); + aws_hpack_encoder_set_huffman_mode(&fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); struct aws_http_header headers[] = { DEFINE_STATIC_HEADER("custom-key", "custom-header", USE_CACHE), @@ -205,7 +203,7 @@ HEADER_TEST(h2_header_ex_2_1, s_test_ex_2_1_init, NULL); /* RFC-7541 - Header Field Representation Examples - C.2.2. Literal Header Field without Indexing */ static int s_test_ex_2_2_init(struct header_test_fixture *fixture) { - aws_hpack_set_huffman_mode(fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); + aws_hpack_encoder_set_huffman_mode(&fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); struct aws_http_header headers[] = { DEFINE_STATIC_HEADER(":path", "/sample/path", NO_CACHE), @@ -224,7 +222,7 @@ HEADER_TEST(h2_header_ex_2_2, s_test_ex_2_2_init, NULL); /* RFC-7541 - Header Field Representation Examples - C.2.3. Literal Header Field Never Indexed */ static int s_test_ex_2_3_init(struct header_test_fixture *fixture) { - aws_hpack_set_huffman_mode(fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); + aws_hpack_encoder_set_huffman_mode(&fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); struct aws_http_header headers[] = { DEFINE_STATIC_HEADER("password", "secret", NO_FORWARD_CACHE), @@ -243,7 +241,7 @@ HEADER_TEST(h2_header_ex_2_3, s_test_ex_2_3_init, NULL); /* RFC-7541 - Header Field Representation Examples - C.2.3. Indexed Header Field */ static int s_test_ex_2_4_init(struct header_test_fixture *fixture) { - aws_hpack_set_huffman_mode(fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); + aws_hpack_encoder_set_huffman_mode(&fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); struct aws_http_header headers[] = { DEFINE_STATIC_HEADER(":method", "GET", USE_CACHE), @@ -272,8 +270,8 @@ struct header_request_response_test_fixture { struct aws_allocator *allocator; bool one_byte_at_a_time; /* T: decode one byte at a time. F: decode whole buffer at once */ - struct aws_hpack_context *encoder; - struct aws_hpack_context *decoder; + struct aws_hpack_encoder encoder; + struct aws_hpack_decoder decoder; struct aws_http_headers *headers_to_encode[3]; struct aws_byte_buf expected_encoding_buf[3]; @@ -290,10 +288,8 @@ static int s_header_request_response_test_before(struct aws_allocator *allocator aws_http_library_init(allocator); - fixture->encoder = aws_hpack_context_new(allocator, AWS_LS_HTTP_ENCODER, NULL); - ASSERT_NOT_NULL(fixture->encoder); - fixture->decoder = aws_hpack_context_new(allocator, AWS_LS_HTTP_DECODER, NULL); - ASSERT_NOT_NULL(fixture->decoder); + aws_hpack_encoder_init(&fixture->encoder, allocator, NULL); + aws_hpack_decoder_init(&fixture->decoder, allocator, NULL); for (int i = 0; i < 3; i++) { fixture->headers_to_encode[i] = aws_http_headers_new(allocator); ASSERT_NOT_NULL(fixture->headers_to_encode[i]); @@ -312,7 +308,7 @@ static int s_encoder_result_check( struct aws_byte_buf expected_encoding_buf, struct aws_byte_buf *output_buffer) { /* Encode the headers */ - ASSERT_SUCCESS(aws_hpack_encode_header_block(fixture->encoder, headers_to_encode, output_buffer)); + ASSERT_SUCCESS(aws_hpack_encode_header_block(&fixture->encoder, headers_to_encode, output_buffer)); /* Compare the encoded output against the expected header block fragment */ ASSERT_BIN_ARRAYS_EQUALS( @@ -325,10 +321,10 @@ static int s_encoder_result_check( if (fixture->one_byte_at_a_time) { struct aws_byte_cursor one_byte_payload = aws_byte_cursor_advance(&payload, 1); - ASSERT_SUCCESS(aws_hpack_decode(fixture->decoder, &one_byte_payload, &result)); + ASSERT_SUCCESS(aws_hpack_decode(&fixture->decoder, &one_byte_payload, &result)); ASSERT_UINT_EQUALS(0, one_byte_payload.len); } else { - ASSERT_SUCCESS(aws_hpack_decode(fixture->decoder, &payload, &result)); + ASSERT_SUCCESS(aws_hpack_decode(&fixture->decoder, &payload, &result)); } if (result.type == AWS_HPACK_DECODE_T_HEADER_FIELD) { @@ -349,14 +345,14 @@ static int s_dynamic_table_last_entry_check( struct aws_http_header *expected_entry, size_t dynamic_table_len) { /* check the decoder's dynamic table */ - struct aws_hpack_context *context = fixture->decoder; + const struct aws_hpack_context *context = &fixture->decoder.context; /* get the last element in dynamic table, which will be the absolute index plus all the elements in static table */ ASSERT_TRUE(dynamic_table_len == aws_hpack_get_dynamic_table_num_elements(context)); const struct aws_http_header *back = aws_hpack_get_header(context, dynamic_table_len + 61); ASSERT_TRUE(aws_byte_cursor_eq(&back->name, &expected_entry->name)); ASSERT_TRUE(aws_byte_cursor_eq(&back->value, &expected_entry->value)); /* check the encoder's dynamic table */ - context = fixture->encoder; + context = &fixture->encoder.context; ASSERT_TRUE(dynamic_table_len == aws_hpack_get_dynamic_table_num_elements(context)); back = aws_hpack_get_header(context, dynamic_table_len + 61); ASSERT_TRUE(aws_byte_cursor_eq(&back->name, &expected_entry->name)); @@ -406,8 +402,8 @@ static int s_header_request_response_test_after(struct aws_allocator *allocator, aws_byte_buf_clean_up(&fixture->expected_encoding_buf[i]); aws_http_headers_release(fixture->headers_to_encode[i]); } - aws_hpack_context_destroy(fixture->decoder); - aws_hpack_context_destroy(fixture->encoder); + aws_hpack_decoder_clean_up(&fixture->decoder); + aws_hpack_encoder_clean_up(&fixture->encoder); } aws_http_library_clean_up(); @@ -440,7 +436,7 @@ static int s_header_request_response_test_after(struct aws_allocator *allocator, /* RFC-7541 - Request Examples without Huffman Coding - C.3 */ static int s_test_ex_3_init(struct header_request_response_test_fixture *fixture) { - aws_hpack_set_huffman_mode(fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); + aws_hpack_encoder_set_huffman_mode(&fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); int index = 0; /* First Request RFC-7541 C.3.1 */ struct aws_http_header headers_1[] = { @@ -508,7 +504,7 @@ HEADER_REQUEST_RESPONSE_TEST(h2_header_ex_3, s_test_ex_3_init, NULL); /* RFC-7541 - Request Examples with Huffman Coding - C.4 */ static int s_test_ex_4_init(struct header_request_response_test_fixture *fixture) { - aws_hpack_set_huffman_mode(fixture->encoder, AWS_HPACK_HUFFMAN_ALWAYS); + aws_hpack_encoder_set_huffman_mode(&fixture->encoder, AWS_HPACK_HUFFMAN_ALWAYS); int index = 0; /* First Request RFC-7541 C.4.1 */ struct aws_http_header headers_1[] = { @@ -575,10 +571,10 @@ HEADER_REQUEST_RESPONSE_TEST(h2_header_ex_4, s_test_ex_4_init, NULL); static int s_test_ex_5_init(struct header_request_response_test_fixture *fixture) { /* set the max table size to 256 */ - ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(fixture->encoder, 256)); - ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(fixture->decoder, 256)); + ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(&fixture->encoder.context, 256)); + ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(&fixture->decoder.context, 256)); - aws_hpack_set_huffman_mode(fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); + aws_hpack_encoder_set_huffman_mode(&fixture->encoder, AWS_HPACK_HUFFMAN_NEVER); int index = 0; /* First Response RFC-7541 C.5.1 */ struct aws_http_header headers_1[] = { @@ -651,10 +647,10 @@ HEADER_REQUEST_RESPONSE_TEST(h2_header_ex_5, s_test_ex_5_init, NULL); static int s_test_ex_6_init(struct header_request_response_test_fixture *fixture) { /* set the max table size to 256 */ - ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(fixture->encoder, 256)); - ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(fixture->decoder, 256)); + ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(&fixture->encoder.context, 256)); + ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(&fixture->decoder.context, 256)); - aws_hpack_set_huffman_mode(fixture->encoder, AWS_HPACK_HUFFMAN_ALWAYS); + aws_hpack_encoder_set_huffman_mode(&fixture->encoder, AWS_HPACK_HUFFMAN_ALWAYS); int index = 0; /* First Response RFC-7541 C.6.1 */ diff --git a/tests/test_hpack.c b/tests/test_hpack.c index 817665cd1..195413823 100644 --- a/tests/test_hpack.c +++ b/tests/test_hpack.c @@ -91,15 +91,14 @@ static int test_hpack_encode_integer(struct aws_allocator *allocator, void *ctx) } struct decode_fixture { - struct aws_hpack_context *hpack; + struct aws_hpack_decoder hpack; bool one_byte_at_a_time; }; static int s_decode_fixture_setup(struct aws_allocator *allocator, void *ctx) { struct decode_fixture *fixture = ctx; - fixture->hpack = aws_hpack_context_new(allocator, AWS_LS_HTTP_DECODER, NULL); - ASSERT_NOT_NULL(fixture->hpack); + aws_hpack_decoder_init(&fixture->hpack, allocator, NULL); return AWS_OP_SUCCESS; } @@ -111,7 +110,7 @@ static int s_decode_fixture_teardown(struct aws_allocator *allocator, int setup_ } struct decode_fixture *fixture = ctx; - aws_hpack_context_destroy(fixture->hpack); + aws_hpack_decoder_clean_up(&fixture->hpack); return AWS_OP_SUCCESS; } @@ -126,7 +125,7 @@ static int s_decode_integer( if (fixture->one_byte_at_a_time) { do { struct aws_byte_cursor one_byte = aws_byte_cursor_advance(to_decode, 1); - if (aws_hpack_decode_integer(fixture->hpack, &one_byte, prefix_size, integer, complete)) { + if (aws_hpack_decode_integer(&fixture->hpack, &one_byte, prefix_size, integer, complete)) { return AWS_OP_ERR; } ASSERT_UINT_EQUALS(0, one_byte.len); @@ -135,7 +134,7 @@ static int s_decode_integer( return AWS_OP_SUCCESS; } else { - return aws_hpack_decode_integer(fixture->hpack, to_decode, prefix_size, integer, complete); + return aws_hpack_decode_integer(&fixture->hpack, to_decode, prefix_size, integer, complete); } } @@ -149,7 +148,7 @@ static int s_decode_string( if (fixture->one_byte_at_a_time) { do { struct aws_byte_cursor one_byte = aws_byte_cursor_advance(to_decode, 1); - if (aws_hpack_decode_string(fixture->hpack, &one_byte, output, complete)) { + if (aws_hpack_decode_string(&fixture->hpack, &one_byte, output, complete)) { return AWS_OP_ERR; } ASSERT_UINT_EQUALS(0, one_byte.len); @@ -158,7 +157,7 @@ static int s_decode_string( return AWS_OP_SUCCESS; } else { - return aws_hpack_decode_string(fixture->hpack, to_decode, output, complete); + return aws_hpack_decode_string(&fixture->hpack, to_decode, output, complete); } } @@ -518,9 +517,9 @@ static int test_hpack_static_table_find(struct aws_allocator *allocator, void *c (void)ctx; aws_http_library_init(allocator); - struct aws_hpack_context *context = aws_hpack_context_new(allocator, AWS_LS_HTTP_GENERAL, NULL); - ASSERT_NOT_NULL(context); - ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(context, 0)); + struct aws_hpack_context context; + aws_hpack_context_init(&context, allocator, AWS_LS_HTTP_GENERAL, NULL); + ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(&context, 0)); bool found_value = false; @@ -530,19 +529,19 @@ static int test_hpack_static_table_find(struct aws_allocator *allocator, void *c DEFINE_STATIC_HEADER(s_garbage, "colden's favorite ice cream flavor", "cookie dough"); /* Test header without value */ - ASSERT_UINT_EQUALS(1, aws_hpack_find_index(context, &s_authority, false, &found_value)); + ASSERT_UINT_EQUALS(1, aws_hpack_find_index(&context, &s_authority, false, &found_value)); ASSERT_FALSE(found_value); /* Test header with value */ - ASSERT_UINT_EQUALS(2, aws_hpack_find_index(context, &s_get, true, &found_value)); + ASSERT_UINT_EQUALS(2, aws_hpack_find_index(&context, &s_get, true, &found_value)); ASSERT_TRUE(found_value); - ASSERT_UINT_EQUALS(2, aws_hpack_find_index(context, &s_other_method, true, &found_value)); + ASSERT_UINT_EQUALS(2, aws_hpack_find_index(&context, &s_other_method, true, &found_value)); ASSERT_FALSE(found_value); /* Check invalid header */ - ASSERT_UINT_EQUALS(0, aws_hpack_find_index(context, &s_garbage, true, &found_value)); + ASSERT_UINT_EQUALS(0, aws_hpack_find_index(&context, &s_garbage, true, &found_value)); - aws_hpack_context_destroy(context); + aws_hpack_context_clean_up(&context); aws_http_library_clean_up(); return AWS_OP_SUCCESS; } @@ -552,30 +551,30 @@ static int test_hpack_static_table_get(struct aws_allocator *allocator, void *ct (void)ctx; aws_http_library_init(allocator); - struct aws_hpack_context *context = aws_hpack_context_new(allocator, AWS_LS_HTTP_GENERAL, NULL); - ASSERT_NOT_NULL(context); - ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(context, 0)); + struct aws_hpack_context context; + aws_hpack_context_init(&context, allocator, AWS_LS_HTTP_GENERAL, NULL); + ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(&context, 0)); const struct aws_http_header *found = NULL; DEFINE_STATIC_HEADER(s_get, ":path", "/index.html"); DEFINE_STATIC_HEADER(s_age, "age", "25"); - found = aws_hpack_get_header(context, 21); + found = aws_hpack_get_header(&context, 21); ASSERT_NOT_NULL(found); ASSERT_TRUE(aws_byte_cursor_eq(&s_age.name, &found->name)); ASSERT_NULL(found->value.ptr); ASSERT_UINT_EQUALS(0, found->value.len); - found = aws_hpack_get_header(context, 5); + found = aws_hpack_get_header(&context, 5); ASSERT_NOT_NULL(found); ASSERT_TRUE(aws_byte_cursor_eq(&s_get.name, &found->name)); ASSERT_TRUE(aws_byte_cursor_eq(&s_get.value, &found->value)); - found = aws_hpack_get_header(context, 69); + found = aws_hpack_get_header(&context, 69); ASSERT_NULL(found); - aws_hpack_context_destroy(context); + aws_hpack_context_clean_up(&context); aws_http_library_clean_up(); return AWS_OP_SUCCESS; } @@ -585,8 +584,8 @@ static int test_hpack_dynamic_table_find(struct aws_allocator *allocator, void * (void)ctx; aws_http_library_init(allocator); - struct aws_hpack_context *context = aws_hpack_context_new(allocator, AWS_LS_HTTP_GENERAL, NULL); - ASSERT_NOT_NULL(context); + struct aws_hpack_context context; + aws_hpack_context_init(&context, allocator, AWS_LS_HTTP_GENERAL, NULL); bool found_value = false; @@ -595,37 +594,37 @@ static int test_hpack_dynamic_table_find(struct aws_allocator *allocator, void * DEFINE_STATIC_HEADER(s_fizz, "fizz", "buzz"); /* Test single header */ - ASSERT_SUCCESS(aws_hpack_insert_header(context, &s_herp)); - ASSERT_UINT_EQUALS(62, aws_hpack_find_index(context, &s_herp, true, &found_value)); + ASSERT_SUCCESS(aws_hpack_insert_header(&context, &s_herp)); + ASSERT_UINT_EQUALS(62, aws_hpack_find_index(&context, &s_herp, true, &found_value)); ASSERT_TRUE(found_value); - ASSERT_UINT_EQUALS(62, aws_hpack_find_index(context, &s_herp2, true, &found_value)); + ASSERT_UINT_EQUALS(62, aws_hpack_find_index(&context, &s_herp2, true, &found_value)); ASSERT_FALSE(found_value); /* Test 2 headers */ - ASSERT_SUCCESS(aws_hpack_insert_header(context, &s_fizz)); - ASSERT_UINT_EQUALS(62, aws_hpack_find_index(context, &s_fizz, true, &found_value)); + ASSERT_SUCCESS(aws_hpack_insert_header(&context, &s_fizz)); + ASSERT_UINT_EQUALS(62, aws_hpack_find_index(&context, &s_fizz, true, &found_value)); ASSERT_TRUE(found_value); - ASSERT_UINT_EQUALS(63, aws_hpack_find_index(context, &s_herp, true, &found_value)); + ASSERT_UINT_EQUALS(63, aws_hpack_find_index(&context, &s_herp, true, &found_value)); ASSERT_TRUE(found_value); - ASSERT_UINT_EQUALS(63, aws_hpack_find_index(context, &s_herp2, true, &found_value)); + ASSERT_UINT_EQUALS(63, aws_hpack_find_index(&context, &s_herp2, true, &found_value)); ASSERT_FALSE(found_value); /* Test resizing up doesn't break anything */ - ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(context, 8 * 1024 * 1024)); + ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(&context, 8 * 1024 * 1024)); /* Check invalid header */ DEFINE_STATIC_HEADER(s_garbage, "colden's mother's maiden name", "nice try mr hacker"); - ASSERT_UINT_EQUALS(0, aws_hpack_find_index(context, &s_garbage, true, &found_value)); + ASSERT_UINT_EQUALS(0, aws_hpack_find_index(&context, &s_garbage, true, &found_value)); /* Test resizing so only the first element stays */ - ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(context, aws_hpack_get_header_size(&s_fizz))); + ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(&context, aws_hpack_get_header_size(&s_fizz))); - ASSERT_UINT_EQUALS(62, aws_hpack_find_index(context, &s_fizz, true, &found_value)); + ASSERT_UINT_EQUALS(62, aws_hpack_find_index(&context, &s_fizz, true, &found_value)); ASSERT_TRUE(found_value); - ASSERT_UINT_EQUALS(0, aws_hpack_find_index(context, &s_herp, true, &found_value)); + ASSERT_UINT_EQUALS(0, aws_hpack_find_index(&context, &s_herp, true, &found_value)); ASSERT_FALSE(found_value); - aws_hpack_context_destroy(context); + aws_hpack_context_clean_up(&context); aws_http_library_clean_up(); return AWS_OP_SUCCESS; } @@ -635,8 +634,8 @@ static int test_hpack_dynamic_table_get(struct aws_allocator *allocator, void *c (void)ctx; aws_http_library_init(allocator); - struct aws_hpack_context *context = aws_hpack_context_new(allocator, AWS_LS_HTTP_GENERAL, NULL); - ASSERT_NOT_NULL(context); + struct aws_hpack_context context; + aws_hpack_context_init(&context, allocator, AWS_LS_HTTP_GENERAL, NULL); const struct aws_http_header *found = NULL; @@ -646,48 +645,48 @@ static int test_hpack_dynamic_table_get(struct aws_allocator *allocator, void *c /* Make the dynamic table only big enough for 2 headers */ ASSERT_SUCCESS(aws_hpack_resize_dynamic_table( - context, aws_hpack_get_header_size(&s_fizz) + aws_hpack_get_header_size(&s_status))); + &context, aws_hpack_get_header_size(&s_fizz) + aws_hpack_get_header_size(&s_status))); - ASSERT_SUCCESS(aws_hpack_insert_header(context, &s_herp)); - found = aws_hpack_get_header(context, 62); + ASSERT_SUCCESS(aws_hpack_insert_header(&context, &s_herp)); + found = aws_hpack_get_header(&context, 62); ASSERT_NOT_NULL(found); ASSERT_TRUE(aws_byte_cursor_eq(&s_herp.name, &found->name)); ASSERT_TRUE(aws_byte_cursor_eq(&s_herp.value, &found->value)); - ASSERT_SUCCESS(aws_hpack_insert_header(context, &s_fizz)); - found = aws_hpack_get_header(context, 62); + ASSERT_SUCCESS(aws_hpack_insert_header(&context, &s_fizz)); + found = aws_hpack_get_header(&context, 62); ASSERT_NOT_NULL(found); ASSERT_TRUE(aws_byte_cursor_eq(&s_fizz.name, &found->name)); ASSERT_TRUE(aws_byte_cursor_eq(&s_fizz.value, &found->value)); - found = aws_hpack_get_header(context, 63); + found = aws_hpack_get_header(&context, 63); ASSERT_NOT_NULL(found); ASSERT_TRUE(aws_byte_cursor_eq(&s_herp.name, &found->name)); ASSERT_TRUE(aws_byte_cursor_eq(&s_herp.value, &found->value)); /* This one will result in the first header being evicted */ - ASSERT_SUCCESS(aws_hpack_insert_header(context, &s_status)); - found = aws_hpack_get_header(context, 62); + ASSERT_SUCCESS(aws_hpack_insert_header(&context, &s_status)); + found = aws_hpack_get_header(&context, 62); ASSERT_NOT_NULL(found); ASSERT_TRUE(aws_byte_cursor_eq(&s_status.name, &found->name)); ASSERT_TRUE(aws_byte_cursor_eq(&s_status.value, &found->value)); - found = aws_hpack_get_header(context, 63); + found = aws_hpack_get_header(&context, 63); ASSERT_NOT_NULL(found); ASSERT_TRUE(aws_byte_cursor_eq(&s_fizz.name, &found->name)); ASSERT_TRUE(aws_byte_cursor_eq(&s_fizz.value, &found->value)); - found = aws_hpack_get_header(context, 64); + found = aws_hpack_get_header(&context, 64); ASSERT_NULL(found); /* Test resizing to evict entries */ - ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(context, aws_hpack_get_header_size(&s_status))); + ASSERT_SUCCESS(aws_hpack_resize_dynamic_table(&context, aws_hpack_get_header_size(&s_status))); - found = aws_hpack_get_header(context, 62); + found = aws_hpack_get_header(&context, 62); ASSERT_NOT_NULL(found); ASSERT_TRUE(aws_byte_cursor_eq(&s_status.name, &found->name)); ASSERT_TRUE(aws_byte_cursor_eq(&s_status.value, &found->value)); - found = aws_hpack_get_header(context, 63); + found = aws_hpack_get_header(&context, 63); ASSERT_NULL(found); - aws_hpack_context_destroy(context); + aws_hpack_context_clean_up(&context); aws_http_library_clean_up(); return AWS_OP_SUCCESS; } @@ -709,8 +708,8 @@ static int test_hpack_decode_indexed_from_dynamic_table(struct aws_allocator *al (void)ctx; aws_http_library_init(allocator); - struct aws_hpack_context *context = aws_hpack_context_new(allocator, AWS_LS_HTTP_GENERAL, NULL); - ASSERT_NOT_NULL(context); + struct aws_hpack_decoder decoder; + aws_hpack_decoder_init(&decoder, allocator, NULL); /* clang-format off */ uint8_t input[] = { @@ -728,15 +727,15 @@ static int test_hpack_decode_indexed_from_dynamic_table(struct aws_allocator *al struct aws_byte_cursor input_cursor = aws_byte_cursor_from_array(input, sizeof(input)); /* Three entries in total, decode them all, and check the result */ /* First entry */ - ASSERT_SUCCESS(aws_hpack_decode(context, &input_cursor, &result)); + ASSERT_SUCCESS(aws_hpack_decode(&decoder, &input_cursor, &result)); ASSERT_TRUE(result.type == AWS_HPACK_DECODE_T_HEADER_FIELD); ASSERT_SUCCESS(s_check_header(&result.data.header_field, ":status", "302", AWS_HTTP_HEADER_COMPRESSION_USE_CACHE)); /* Second entry */ - ASSERT_SUCCESS(aws_hpack_decode(context, &input_cursor, &result)); + ASSERT_SUCCESS(aws_hpack_decode(&decoder, &input_cursor, &result)); ASSERT_TRUE(result.type == AWS_HPACK_DECODE_T_HEADER_FIELD); ASSERT_SUCCESS(s_check_header(&result.data.header_field, "a", "b", AWS_HTTP_HEADER_COMPRESSION_USE_CACHE)); /* Third entry */ - ASSERT_SUCCESS(aws_hpack_decode(context, &input_cursor, &result)); + ASSERT_SUCCESS(aws_hpack_decode(&decoder, &input_cursor, &result)); ASSERT_TRUE(result.type == AWS_HPACK_DECODE_T_HEADER_FIELD); ASSERT_SUCCESS(s_check_header(&result.data.header_field, ":status", "302", AWS_HTTP_HEADER_COMPRESSION_USE_CACHE)); @@ -744,7 +743,7 @@ static int test_hpack_decode_indexed_from_dynamic_table(struct aws_allocator *al ASSERT_TRUE(input_cursor.len == 0); /* Clean up */ - aws_hpack_context_destroy(context); + aws_hpack_decoder_clean_up(&decoder); aws_http_library_clean_up(); return AWS_OP_SUCCESS; } @@ -755,16 +754,16 @@ static int test_hpack_dynamic_table_empty_value(struct aws_allocator *allocator, (void)ctx; aws_http_library_init(allocator); - struct aws_hpack_context *context = aws_hpack_context_new(allocator, AWS_LS_HTTP_GENERAL, NULL); - ASSERT_NOT_NULL(context); + struct aws_hpack_context context; + aws_hpack_context_init(&context, allocator, AWS_LS_HTTP_GENERAL, NULL); DEFINE_STATIC_HEADER(header1, ":status", "302"); DEFINE_STATIC_HEADER(empty_value_header, "c", ""); DEFINE_STATIC_HEADER(header2, "a", "b"); - ASSERT_SUCCESS(aws_hpack_insert_header(context, &header1)); - ASSERT_SUCCESS(aws_hpack_insert_header(context, &empty_value_header)); - ASSERT_SUCCESS(aws_hpack_insert_header(context, &header2)); + ASSERT_SUCCESS(aws_hpack_insert_header(&context, &header1)); + ASSERT_SUCCESS(aws_hpack_insert_header(&context, &empty_value_header)); + ASSERT_SUCCESS(aws_hpack_insert_header(&context, &header2)); /* So at this point dynamic table should look like: * INDEX NAME VALUE @@ -773,12 +772,12 @@ static int test_hpack_dynamic_table_empty_value(struct aws_allocator *allocator, * 64 :status 302 */ bool found_value = false; - ASSERT_UINT_EQUALS(64, aws_hpack_find_index(context, &header1, true, &found_value)); - ASSERT_UINT_EQUALS(63, aws_hpack_find_index(context, &empty_value_header, true, &found_value)); - ASSERT_UINT_EQUALS(62, aws_hpack_find_index(context, &header2, true, &found_value)); + ASSERT_UINT_EQUALS(64, aws_hpack_find_index(&context, &header1, true, &found_value)); + ASSERT_UINT_EQUALS(63, aws_hpack_find_index(&context, &empty_value_header, true, &found_value)); + ASSERT_UINT_EQUALS(62, aws_hpack_find_index(&context, &header2, true, &found_value)); /* Clean up */ - aws_hpack_context_destroy(context); + aws_hpack_context_clean_up(&context); aws_http_library_clean_up(); return AWS_OP_SUCCESS; } @@ -789,15 +788,15 @@ static int test_hpack_dynamic_table_with_empty_header(struct aws_allocator *allo (void)ctx; aws_http_library_init(allocator); - struct aws_hpack_context *context = aws_hpack_context_new(allocator, AWS_LS_HTTP_GENERAL, NULL); - ASSERT_NOT_NULL(context); + struct aws_hpack_context context; + aws_hpack_context_init(&context, allocator, AWS_LS_HTTP_GENERAL, NULL); DEFINE_STATIC_HEADER(header1, ":status", "302"); DEFINE_STATIC_HEADER(empty_header, "", ""); DEFINE_STATIC_HEADER(header2, "a", "b"); - ASSERT_SUCCESS(aws_hpack_insert_header(context, &header1)); - ASSERT_SUCCESS(aws_hpack_insert_header(context, &empty_header)); - ASSERT_SUCCESS(aws_hpack_insert_header(context, &header2)); + ASSERT_SUCCESS(aws_hpack_insert_header(&context, &header1)); + ASSERT_SUCCESS(aws_hpack_insert_header(&context, &empty_header)); + ASSERT_SUCCESS(aws_hpack_insert_header(&context, &header2)); /* So at this point dynamic table should look like: * INDEX NAME VALUE @@ -806,12 +805,12 @@ static int test_hpack_dynamic_table_with_empty_header(struct aws_allocator *allo * 64 :status 302 */ bool found_value = false; - ASSERT_UINT_EQUALS(64, aws_hpack_find_index(context, &header1, true, &found_value)); - ASSERT_UINT_EQUALS(63, aws_hpack_find_index(context, &empty_header, true, &found_value)); - ASSERT_UINT_EQUALS(62, aws_hpack_find_index(context, &header2, true, &found_value)); + ASSERT_UINT_EQUALS(64, aws_hpack_find_index(&context, &header1, true, &found_value)); + ASSERT_UINT_EQUALS(63, aws_hpack_find_index(&context, &empty_header, true, &found_value)); + ASSERT_UINT_EQUALS(62, aws_hpack_find_index(&context, &header2, true, &found_value)); /* Clean up */ - aws_hpack_context_destroy(context); + aws_hpack_context_clean_up(&context); aws_http_library_clean_up(); return AWS_OP_SUCCESS; } @@ -821,13 +820,13 @@ static int test_hpack_dynamic_table_size_update_from_setting(struct aws_allocato (void)ctx; aws_http_library_init(allocator); - struct aws_hpack_context *context = aws_hpack_context_new(allocator, AWS_LS_HTTP_GENERAL, NULL); - ASSERT_NOT_NULL(context); + struct aws_hpack_encoder encoder; + aws_hpack_encoder_init(&encoder, allocator, NULL); - /* let's pretent multiple times max size update happened from encoder setting */ - aws_hpack_set_max_table_size(context, 10); - aws_hpack_set_max_table_size(context, 0); - aws_hpack_set_max_table_size(context, 1337); + /* let's pretend multiple times max size update happened from encoder setting */ + aws_hpack_encoder_update_max_table_size(&encoder, 10); + aws_hpack_encoder_update_max_table_size(&encoder, 0); + aws_hpack_encoder_update_max_table_size(&encoder, 1337); /* encode a header block */ struct aws_http_headers *headers = aws_http_headers_new(allocator); @@ -836,7 +835,7 @@ static int test_hpack_dynamic_table_size_update_from_setting(struct aws_allocato ASSERT_SUCCESS(aws_http_headers_add_header(headers, &header)); struct aws_byte_buf output; ASSERT_SUCCESS(aws_byte_buf_init(&output, allocator, 5)); - ASSERT_SUCCESS(aws_hpack_encode_header_block(context, headers, &output)); + ASSERT_SUCCESS(aws_hpack_encode_header_block(&encoder, headers, &output)); /* Check the output result, it should contain two dynamic table size updates, besides the header */ /** @@ -872,7 +871,7 @@ static int test_hpack_dynamic_table_size_update_from_setting(struct aws_allocato /* clean up */ aws_byte_buf_clean_up(&output); aws_http_headers_release(headers); - aws_hpack_context_destroy(context); + aws_hpack_encoder_clean_up(&encoder); aws_http_library_clean_up(); return AWS_OP_SUCCESS; }