Skip to content

Commit

Permalink
Split up hpack.c (#385)
Browse files Browse the repository at this point in the history
**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.
  • Loading branch information
graebm authored Aug 1, 2022
1 parent 30230be commit 397cffa
Show file tree
Hide file tree
Showing 10 changed files with 1,212 additions and 1,173 deletions.
3 changes: 2 additions & 1 deletion include/aws/http/private/h2_frames.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

#include <aws/http/connection.h>
#include <aws/http/private/hpack.h>
#include <aws/http/request_response.h>

#include <aws/common/byte_buf.h>
Expand Down Expand Up @@ -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 */
Expand Down
241 changes: 187 additions & 54 deletions include/aws/http/private/hpack.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
*/
#include <aws/http/request_response.h>

struct aws_byte_buf;
struct aws_byte_cursor;
struct aws_http_header;
struct aws_hpack_context;
#include <aws/common/hash_table.h>
#include <aws/compression/huffman.h>

/**
* Result of aws_hpack_decode() call.
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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);
Expand Down
15 changes: 6 additions & 9 deletions source/h2_decoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
Expand All @@ -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);
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 397cffa

Please sign in to comment.