Repository: trafficserver Updated Branches: refs/heads/master 5a0952b01 -> a39dd9d7c
TS-4092: Decouple HPACK from HTTP/2 This close #460 Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/a39dd9d7 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/a39dd9d7 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/a39dd9d7 Branch: refs/heads/master Commit: a39dd9d7c6d9454d8e6fe4be5cf18e517b7557aa Parents: 5a0952b Author: Masakazu Kitajo <[email protected]> Authored: Mon Feb 8 23:52:59 2016 +0900 Committer: Masakazu Kitajo <[email protected]> Committed: Sat Apr 2 00:27:36 2016 +0900 ---------------------------------------------------------------------- proxy/http2/HPACK.cc | 334 ++++++++++++++++++---------- proxy/http2/HPACK.h | 109 +++++----- proxy/http2/HTTP2.cc | 360 +++++++++++++------------------ proxy/http2/HTTP2.h | 8 +- proxy/http2/Http2ConnectionState.cc | 104 +++++---- proxy/http2/Http2ConnectionState.h | 12 +- proxy/http2/Http2Stream.cc | 2 +- proxy/http2/Http2Stream.h | 8 +- proxy/http2/RegressionHPACK.cc | 52 ++--- 9 files changed, 520 insertions(+), 469 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a39dd9d7/proxy/http2/HPACK.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/HPACK.cc b/proxy/http2/HPACK.cc index 3254aa5..c6de481 100644 --- a/proxy/http2/HPACK.cc +++ b/proxy/http2/HPACK.cc @@ -24,19 +24,6 @@ #include "HPACK.h" #include "HuffmanCodec.h" -// Constant strings for pseudo headers of HPACK -const char *HPACK_VALUE_SCHEME = ":scheme"; -const char *HPACK_VALUE_METHOD = ":method"; -const char *HPACK_VALUE_AUTHORITY = ":authority"; -const char *HPACK_VALUE_PATH = ":path"; -const char *HPACK_VALUE_STATUS = ":status"; - -const unsigned HPACK_LEN_SCHEME = countof(":scheme") - 1; -const unsigned HPACK_LEN_METHOD = countof(":method") - 1; -const unsigned HPACK_LEN_AUTHORITY = countof(":authority") - 1; -const unsigned HPACK_LEN_PATH = countof(":path") - 1; -const unsigned HPACK_LEN_STATUS = countof(":status") - 1; - // [RFC 7541] 4.1. Calculating Table Size // The size of an entry is the sum of its name's length in octets (as defined in Section 5.2), // its value's length in octets, and 32. @@ -174,14 +161,62 @@ const static struct { {"via", ""}, {"www-authenticate", ""}}; -Http2LookupIndexResult -Http2IndexingTable::get_index(const MIMEFieldWrapper &field) const + +/****************** + * Local functions + ******************/ +static inline bool +hpack_field_is_literal(HpackFieldType ftype) +{ + return ftype == HPACK_FIELD_INDEXED_LITERAL || ftype == HPACK_FIELD_NOINDEX_LITERAL || ftype == HPACK_FIELD_NEVERINDEX_LITERAL; +} + +// +// The first byte of an HPACK field unambiguously tells us what +// kind of field it is. Field types are specified in the high 4 bits +// and all bits are defined, so there's no way to get an invalid field type. +// +HpackFieldType +hpack_parse_field_type(uint8_t ftype) +{ + if (ftype & 0x80) { + return HPACK_FIELD_INDEX; + } + + if (ftype & 0x40) { + return HPACK_FIELD_INDEXED_LITERAL; + } + + if (ftype & 0x20) { + return HPACK_FIELD_TABLESIZE_UPDATE; + } + + if (ftype & 0x10) { + return HPACK_FIELD_NEVERINDEX_LITERAL; + } + + ink_assert((ftype & 0xf0) == 0x0); + return HPACK_FIELD_NOINDEX_LITERAL; +} + + +/************************ + * HpackIndexingTable + ************************/ +HpackLookupResult +HpackIndexingTable::lookup(const MIMEFieldWrapper &field) const { - Http2LookupIndexResult result; int target_name_len = 0, target_value_len = 0; const char *target_name = field.name_get(&target_name_len); const char *target_value = field.value_get(&target_value_len); - const int entry_num = TS_HPACK_STATIC_TABLE_ENTRY_NUM + _dynamic_table.get_current_entry_num(); + return lookup(target_name, target_name_len, target_value, target_value_len); +} + +HpackLookupResult +HpackIndexingTable::lookup(const char *name, int name_len, const char *value, int value_len) const +{ + HpackLookupResult result; + const int entry_num = TS_HPACK_STATIC_TABLE_ENTRY_NUM + _dynamic_table->length(); for (int index = 1; index < entry_num; ++index) { const char *table_name, *table_value; @@ -195,29 +230,37 @@ Http2IndexingTable::get_index(const MIMEFieldWrapper &field) const table_value_len = strlen(table_value); } else { // dynamic table - const MIMEField *m_field = _dynamic_table.get_header_field(index - TS_HPACK_STATIC_TABLE_ENTRY_NUM); + const MIMEField *m_field = _dynamic_table->get_header_field(index - TS_HPACK_STATIC_TABLE_ENTRY_NUM); table_name = m_field->name_get(&table_name_len); table_value = m_field->value_get(&table_value_len); } // Check whether name (and value) are matched - if (ptr_len_casecmp(target_name, target_name_len, table_name, table_name_len) == 0) { - if (ptr_len_cmp(target_value, target_value_len, table_value, table_value_len) == 0) { + if (ptr_len_casecmp(name, name_len, table_name, table_name_len) == 0) { + if (ptr_len_cmp(value, value_len, table_value, table_value_len) == 0) { result.index = index; - result.value_is_indexed = true; + result.match_type = HPACK_EXACT_MATCH; break; } else if (!result.index) { result.index = index; + result.match_type = HPACK_NAME_MATCH; } } } + if (result.match_type != HPACK_NO_MATCH) { + if (result.index < TS_HPACK_STATIC_TABLE_ENTRY_NUM) { + result.index_type = HPACK_INDEX_TYPE_STATIC; + } else { + result.index_type = HPACK_INDEX_TYPE_DYNAMIC; + } + } return result; } int -Http2IndexingTable::get_header_field(uint32_t index, MIMEFieldWrapper &field) const +HpackIndexingTable::get_header_field(uint32_t index, MIMEFieldWrapper &field) const { // Index Address Space starts at 1, so index == 0 is invalid. if (!index) @@ -227,9 +270,9 @@ Http2IndexingTable::get_header_field(uint32_t index, MIMEFieldWrapper &field) co // static table field.name_set(STATIC_TABLE[index].name, strlen(STATIC_TABLE[index].name)); field.value_set(STATIC_TABLE[index].value, strlen(STATIC_TABLE[index].value)); - } else if (index < TS_HPACK_STATIC_TABLE_ENTRY_NUM + _dynamic_table.get_current_entry_num()) { + } else if (index < TS_HPACK_STATIC_TABLE_ENTRY_NUM + _dynamic_table->length()) { // dynamic table - const MIMEField *m_field = _dynamic_table.get_header_field(index - TS_HPACK_STATIC_TABLE_ENTRY_NUM); + const MIMEField *m_field = _dynamic_table->get_header_field(index - TS_HPACK_STATIC_TABLE_ENTRY_NUM); int name_len, value_len; const char *name = m_field->name_get(&name_len); @@ -248,44 +291,38 @@ Http2IndexingTable::get_header_field(uint32_t index, MIMEFieldWrapper &field) co } void -Http2IndexingTable::add_header_field_to_dynamic_table(const MIMEField *field) +HpackIndexingTable::add_header_field(const MIMEField *field) { - _dynamic_table.add_header_field(field); + _dynamic_table->add_header_field(field); } uint32_t -Http2IndexingTable::get_dynamic_table_size() const -{ - return _dynamic_table.get_size(); -} - -bool -Http2IndexingTable::set_dynamic_table_size(uint32_t new_size) +HpackIndexingTable::size() const { - return _dynamic_table.set_size(new_size); + return _dynamic_table->size(); } bool -Http2IndexingTable::is_header_in_dynamic_table(const char *target_name, const char *target_value) const +HpackIndexingTable::update_maximum_size(uint32_t new_size) { - return _dynamic_table.is_header_in(target_name, target_value); + return _dynamic_table->update_maximum_size(new_size); } const MIMEField * -Http2DynamicTable::get_header_field(uint32_t index) const +HpackDynamicTable::get_header_field(uint32_t index) const { return _headers.get(index); } void -Http2DynamicTable::add_header_field(const MIMEField *field) +HpackDynamicTable::add_header_field(const MIMEField *field) { int name_len, value_len; const char *name = field->name_get(&name_len); const char *value = field->value_get(&value_len); uint32_t header_size = ADDITIONAL_OCTETS + name_len + value_len; - if (header_size > _settings_dynamic_table_size) { + if (header_size > _maximum_size) { // [RFC 7541] 4.4. Entry Eviction When Adding New Entries // It is not an error to attempt to add an entry that is larger than // the maximum size; an attempt to add an entry larger than the entire @@ -295,7 +332,7 @@ Http2DynamicTable::add_header_field(const MIMEField *field) _current_size = 0; } else { _current_size += header_size; - while (_current_size > _settings_dynamic_table_size) { + while (_current_size > _maximum_size) { int last_name_len, last_value_len; MIMEField *last_field = _headers.last(); @@ -316,7 +353,7 @@ Http2DynamicTable::add_header_field(const MIMEField *field) } uint32_t -Http2DynamicTable::get_size() const +HpackDynamicTable::size() const { return _current_size; } @@ -329,7 +366,7 @@ Http2DynamicTable::get_size() const // header table is less than or equal to the maximum size. // bool -Http2DynamicTable::set_size(uint32_t new_size) +HpackDynamicTable::update_maximum_size(uint32_t new_size) { while (_current_size > new_size) { if (_headers.n <= 0) { @@ -346,63 +383,16 @@ Http2DynamicTable::set_size(uint32_t new_size) _mhdr->field_delete(last_field, false); } - _settings_dynamic_table_size = new_size; + _maximum_size = new_size; return true; } -const uint32_t -Http2DynamicTable::get_current_entry_num() const +uint32_t +HpackDynamicTable::length() const { return _headers.length(); } -bool -Http2DynamicTable::is_header_in(const char *target_name, const char *target_value) const -{ - const MIMEField *field = _mhdr->field_find(target_name, strlen(target_name)); - - if (field) { - do { - int target_value_len = strlen(target_value); - int table_value_len = 0; - const char *table_value = field->value_get(&table_value_len); - if (ptr_len_cmp(target_value, target_value_len, table_value, table_value_len) == 0) { - return true; - } - } while (field->has_dups() && (field = field->m_next_dup) != NULL); - } - - return false; -} - -// -// The first byte of an HPACK field unambiguously tells us what -// kind of field it is. Field types are specified in the high 4 bits -// and all bits are defined, so there's no way to get an invalid field type. -// -HpackFieldType -hpack_parse_field_type(uint8_t ftype) -{ - if (ftype & 0x80) { - return HPACK_FIELD_INDEX; - } - - if (ftype & 0x40) { - return HPACK_FIELD_INDEXED_LITERAL; - } - - if (ftype & 0x20) { - return HPACK_FIELD_TABLESIZE_UPDATE; - } - - if (ftype & 0x10) { - return HPACK_FIELD_NEVERINDEX_LITERAL; - } - - ink_assert((ftype & 0xf0) == 0x0); - return HPACK_FIELD_NOINDEX_LITERAL; -} - // // [RFC 7541] 5.1. Integer representation // @@ -508,13 +498,13 @@ encode_indexed_header_field(uint8_t *buf_start, const uint8_t *buf_end, uint32_t *p |= 0x80; p += len; - Debug("http2_hpack_encode", "Encoded field: %d", index); + Debug("hpack_encode", "Encoded field: %d", index); return p - buf_start; } int64_t encode_literal_header_field_with_indexed_name(uint8_t *buf_start, const uint8_t *buf_end, const MIMEFieldWrapper &header, - uint32_t index, Http2IndexingTable &indexing_table, HpackFieldType type) + uint32_t index, HpackIndexingTable &indexing_table, HpackFieldType type) { uint8_t *p = buf_start; int64_t len; @@ -524,7 +514,7 @@ encode_literal_header_field_with_indexed_name(uint8_t *buf_start, const uint8_t switch (type) { case HPACK_FIELD_INDEXED_LITERAL: - indexing_table.add_header_field_to_dynamic_table(header.field_get()); + indexing_table.add_header_field(header.field_get()); prefix = 6; flag = 0x40; break; @@ -560,13 +550,13 @@ encode_literal_header_field_with_indexed_name(uint8_t *buf_start, const uint8_t return -1; p += len; - Debug("http2_hpack_encode", "Encoded field: %d: %.*s", index, value_len, value); + Debug("hpack_encode", "Encoded field: %d: %.*s", index, value_len, value); return p - buf_start; } int64_t encode_literal_header_field_with_new_name(uint8_t *buf_start, const uint8_t *buf_end, const MIMEFieldWrapper &header, - Http2IndexingTable &indexing_table, HpackFieldType type) + HpackIndexingTable &indexing_table, HpackFieldType type) { uint8_t *p = buf_start; int64_t len; @@ -576,7 +566,7 @@ encode_literal_header_field_with_new_name(uint8_t *buf_start, const uint8_t *buf switch (type) { case HPACK_FIELD_INDEXED_LITERAL: - indexing_table.add_header_field_to_dynamic_table(header.field_get()); + indexing_table.add_header_field(header.field_get()); flag = 0x40; break; case HPACK_FIELD_NOINDEX_LITERAL: @@ -593,7 +583,8 @@ encode_literal_header_field_with_new_name(uint8_t *buf_start, const uint8_t *buf } *(p++) = flag; - // Convert field name to lower case + // Convert field name to lower case to follow HTTP2 spec. + // This conversion is needed because WKSs in MIMEFields is old fashioned Arena arena; int name_len; const char *name = header.name_get(&name_len); @@ -603,8 +594,9 @@ encode_literal_header_field_with_new_name(uint8_t *buf_start, const uint8_t *buf // Name String len = encode_string(p, buf_end, lower_name, name_len); - if (len == -1) + if (len == -1) { return -1; + } p += len; // Value String @@ -617,7 +609,7 @@ encode_literal_header_field_with_new_name(uint8_t *buf_start, const uint8_t *buf p += len; - Debug("http2_hpack_encode", "Encoded field: %.*s: %.*s", name_len, name, value_len, value); + Debug("hpack_encode", "Encoded field: %.*s: %.*s", name_len, name, value_len, value); return p - buf_start; } @@ -703,7 +695,7 @@ decode_string(Arena &arena, char **str, uint32_t &str_length, const uint8_t *buf // int64_t decode_indexed_header_field(MIMEFieldWrapper &header, const uint8_t *buf_start, const uint8_t *buf_end, - Http2IndexingTable &indexing_table) + HpackIndexingTable &indexing_table) { uint32_t index = 0; int64_t len = 0; @@ -716,14 +708,14 @@ decode_indexed_header_field(MIMEFieldWrapper &header, const uint8_t *buf_start, return HPACK_ERROR_COMPRESSION_ERROR; } - if (is_debug_tag_set("http2_hpack_decode")) { + if (is_debug_tag_set("hpack_decode")) { int decoded_name_len; const char *decoded_name = header.name_get(&decoded_name_len); int decoded_value_len; const char *decoded_value = header.value_get(&decoded_value_len); Arena arena; - Debug("http2_hpack_decode", "Decoded field: %s: %s", arena.str_store(decoded_name, decoded_name_len), + Debug("hpack_decode", "Decoded field: %s: %s", arena.str_store(decoded_name, decoded_name_len), arena.str_store(decoded_value, decoded_value_len)); } @@ -736,13 +728,14 @@ decode_indexed_header_field(MIMEFieldWrapper &header, const uint8_t *buf_start, // int64_t decode_literal_header_field(MIMEFieldWrapper &header, const uint8_t *buf_start, const uint8_t *buf_end, - Http2IndexingTable &indexing_table) + HpackIndexingTable &indexing_table) { const uint8_t *p = buf_start; bool isIncremental = false; uint32_t index = 0; int64_t len = 0; HpackFieldType ftype = hpack_parse_field_type(*p); + bool has_http2_violation = false; if (ftype == HPACK_FIELD_INDEXED_LITERAL) { len = decode_integer(index, p, buf_end, 6); @@ -773,9 +766,11 @@ decode_literal_header_field(MIMEFieldWrapper &header, const uint8_t *buf_start, return HPACK_ERROR_COMPRESSION_ERROR; // Check whether header field name is lower case + // XXX This check shouldn't be here because this rule is not a part of HPACK but HTTP2. for (uint32_t i = 0; i < name_str_len; i++) { if (ParseRules::is_upalpha(name_str[i])) { - return -2; + has_http2_violation = true; + break; } } @@ -796,28 +791,33 @@ decode_literal_header_field(MIMEFieldWrapper &header, const uint8_t *buf_start, // Incremental Indexing adds header to header table as new entry if (isIncremental) { - indexing_table.add_header_field_to_dynamic_table(header.field_get()); + indexing_table.add_header_field(header.field_get()); } // Print decoded header field - if (is_debug_tag_set("http2_hpack_decode")) { + if (is_debug_tag_set("hpack_decode")) { int decoded_name_len; const char *decoded_name = header.name_get(&decoded_name_len); int decoded_value_len; const char *decoded_value = header.value_get(&decoded_value_len); - Debug("http2_hpack_decode", "Decoded field: %s: %s", arena.str_store(decoded_name, decoded_name_len), + Debug("hpack_decode", "Decoded field: %s: %s", arena.str_store(decoded_name, decoded_name_len), arena.str_store(decoded_value, decoded_value_len)); } - return p - buf_start; + if (has_http2_violation) { + // XXX Need to return the length to continue decoding + return -(p - buf_start); + } else { + return p - buf_start; + } } // // [RFC 7541] 6.3. Dynamic Table Size Update // int64_t -update_dynamic_table_size(const uint8_t *buf_start, const uint8_t *buf_end, Http2IndexingTable &indexing_table) +update_dynamic_table_size(const uint8_t *buf_start, const uint8_t *buf_end, HpackIndexingTable &indexing_table) { if (buf_start == buf_end) return HPACK_ERROR_COMPRESSION_ERROR; @@ -828,9 +828,119 @@ update_dynamic_table_size(const uint8_t *buf_start, const uint8_t *buf_end, Http if (len == HPACK_ERROR_COMPRESSION_ERROR) return HPACK_ERROR_COMPRESSION_ERROR; - if (indexing_table.set_dynamic_table_size(size) == false) { + if (indexing_table.update_maximum_size(size) == false) { return HPACK_ERROR_COMPRESSION_ERROR; } return len; } + +int64_t +hpack_decode_header_block(HpackIndexingTable &indexing_table, HTTPHdr *hdr, const uint8_t *in_buf, const size_t in_buf_len) +{ + const uint8_t *cursor = in_buf; + const uint8_t *const in_buf_end = in_buf + in_buf_len; + HdrHeap *heap = hdr->m_heap; + HTTPHdrImpl *hh = hdr->m_http; + bool header_field_started = false; + bool has_http2_violation = false; + + while (cursor < in_buf_end) { + int64_t read_bytes = 0; + + // decode a header field encoded by HPACK + MIMEField *field = mime_field_create(heap, hh->m_fields_impl); + MIMEFieldWrapper header(field, heap, hh->m_fields_impl); + HpackFieldType ftype = hpack_parse_field_type(*cursor); + + switch (ftype) { + case HPACK_FIELD_INDEX: + read_bytes = decode_indexed_header_field(header, cursor, in_buf_end, indexing_table); + if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { + return HPACK_ERROR_COMPRESSION_ERROR; + } + cursor += read_bytes; + header_field_started = true; + break; + case HPACK_FIELD_INDEXED_LITERAL: + case HPACK_FIELD_NOINDEX_LITERAL: + case HPACK_FIELD_NEVERINDEX_LITERAL: + read_bytes = decode_literal_header_field(header, cursor, in_buf_end, indexing_table); + if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { + return HPACK_ERROR_COMPRESSION_ERROR; + } + if (read_bytes < 0) { + has_http2_violation = true; + read_bytes = -read_bytes; + } + cursor += read_bytes; + header_field_started = true; + break; + case HPACK_FIELD_TABLESIZE_UPDATE: + if (header_field_started) { + return HPACK_ERROR_COMPRESSION_ERROR; + } + read_bytes = update_dynamic_table_size(cursor, in_buf_end, indexing_table); + if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { + return HPACK_ERROR_COMPRESSION_ERROR; + } + cursor += read_bytes; + continue; + } + // Store to HdrHeap + mime_hdr_field_attach(hh->m_fields_impl, field, 1, NULL); + } + // Parsing all headers is done + if (has_http2_violation) { + return -(cursor - in_buf); + } else { + return cursor - in_buf; + } +} + +int64_t +hpack_encode_header_block(HpackIndexingTable &indexing_table, uint8_t *out_buf, const size_t out_buf_len, HTTPHdr *hdr) +{ + uint8_t *cursor = out_buf; + const uint8_t *const out_buf_end = out_buf + out_buf_len; + int64_t written; + + ink_assert(http_hdr_type_get(hdr->m_http) != HTTP_TYPE_UNKNOWN); + + MIMEFieldIter field_iter; + for (MIMEField *field = hdr->iter_get_first(&field_iter); field != NULL; field = hdr->iter_get_next(&field_iter)) { + HpackFieldType field_type; + MIMEFieldWrapper header(field, hdr->m_heap, hdr->m_http->m_fields_impl); + int name_len; + int value_len; + const char *name = header.name_get(&name_len); + header.value_get(&value_len); + // Choose field representation (See RFC7541 7.1.3) + // - Authorization header obviously should not be indexed + // - Short Cookie header should not be indexed because of low entropy + if ((ptr_len_casecmp(name, name_len, MIME_FIELD_COOKIE, MIME_LEN_COOKIE) == 0 && value_len < 20) || + (ptr_len_casecmp(name, name_len, MIME_FIELD_AUTHORIZATION, MIME_LEN_AUTHORIZATION) == 0)) { + field_type = HPACK_FIELD_NEVERINDEX_LITERAL; + } else { + field_type = HPACK_FIELD_INDEXED_LITERAL; + } + const HpackLookupResult result = indexing_table.lookup(header); + switch (result.match_type) { + case HPACK_NO_MATCH: + written = encode_literal_header_field_with_new_name(cursor, out_buf_end, header, indexing_table, field_type); + break; + case HPACK_NAME_MATCH: + written = + encode_literal_header_field_with_indexed_name(cursor, out_buf_end, header, result.index, indexing_table, field_type); + break; + case HPACK_EXACT_MATCH: + written = encode_indexed_header_field(cursor, out_buf_end, result.index); + break; + } + if (written == HPACK_ERROR_COMPRESSION_ERROR) { + return HPACK_ERROR_COMPRESSION_ERROR; + } + cursor += written; + } + return cursor - out_buf; +} http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a39dd9d7/proxy/http2/HPACK.h ---------------------------------------------------------------------- diff --git a/proxy/http2/HPACK.h b/proxy/http2/HPACK.h index 14430da..37e3a65 100644 --- a/proxy/http2/HPACK.h +++ b/proxy/http2/HPACK.h @@ -29,23 +29,8 @@ #include "ts/Diags.h" #include "HTTP.h" -// Constant strings for pseudo headers of HPACK -extern const char *HPACK_VALUE_SCHEME; -extern const char *HPACK_VALUE_METHOD; -extern const char *HPACK_VALUE_AUTHORITY; -extern const char *HPACK_VALUE_PATH; -extern const char *HPACK_VALUE_STATUS; - -extern const unsigned HPACK_LEN_SCHEME; -extern const unsigned HPACK_LEN_METHOD; -extern const unsigned HPACK_LEN_AUTHORITY; -extern const unsigned HPACK_LEN_PATH; -extern const unsigned HPACK_LEN_STATUS; - // It means that any header field can be compressed/decompressed by ATS const static int HPACK_ERROR_COMPRESSION_ERROR = -1; -// It means that any header field is invalid in HTTP/2 spec -const static int HPACK_ERROR_HTTP2_PROTOCOL_ERROR = -2; enum HpackFieldType { HPACK_FIELD_INDEX, // [RFC 7541] 6.1. Indexed Header Field Representation @@ -55,6 +40,28 @@ enum HpackFieldType { HPACK_FIELD_TABLESIZE_UPDATE, // [RFC 7541] 6.3. Dynamic Table Size Update }; +enum HpackIndexType { + HPACK_INDEX_TYPE_NONE, + HPACK_INDEX_TYPE_STATIC, + HPACK_INDEX_TYPE_DYNAMIC, +}; + +enum HpackMatchType { + HPACK_NO_MATCH, + HPACK_NAME_MATCH, + HPACK_EXACT_MATCH, +}; + +// Result of looking for a header field in IndexingTable +struct HpackLookupResult { + HpackLookupResult() : index(0), index_type(HPACK_INDEX_TYPE_NONE), match_type(HPACK_NO_MATCH) {} + + int index; + HpackIndexType index_type; + HpackMatchType match_type; +}; + + class MIMEFieldWrapper { public: @@ -96,25 +103,17 @@ private: MIMEHdrImpl *_mh; }; -// Result of looking for a header field in IndexingTable -struct Http2LookupIndexResult { - Http2LookupIndexResult() : index(0), value_is_indexed(false) {} - - int index; - bool value_is_indexed; -}; - // [RFC 7541] 2.3.2. Dynamic Table -class Http2DynamicTable +class HpackDynamicTable { public: - Http2DynamicTable() : _current_size(0), _settings_dynamic_table_size(4096) + HpackDynamicTable(uint32_t size) : _current_size(0), _maximum_size(size) { _mhdr = new MIMEHdr(); _mhdr->create(); } - ~Http2DynamicTable() + ~HpackDynamicTable() { _headers.clear(); _mhdr->fields_clear(); @@ -125,64 +124,60 @@ public: const MIMEField *get_header_field(uint32_t index) const; void add_header_field(const MIMEField *field); - uint32_t get_size() const; - bool set_size(uint32_t new_size); + uint32_t size() const; + bool update_maximum_size(uint32_t new_size); - const uint32_t get_current_entry_num() const; - - // For regression test - bool is_header_in(const char *target_name, const char *target_value) const; + uint32_t length() const; private: uint32_t _current_size; - uint32_t _settings_dynamic_table_size; + uint32_t _maximum_size; MIMEHdr *_mhdr; Vec<MIMEField *> _headers; }; - // [RFC 7541] 2.3. Indexing Table -class Http2IndexingTable +class HpackIndexingTable { public: - Http2LookupIndexResult get_index(const MIMEFieldWrapper &field) const; + HpackIndexingTable(uint32_t size) { _dynamic_table = new HpackDynamicTable(size); } + + ~HpackIndexingTable() { delete _dynamic_table; } + + HpackLookupResult lookup(const MIMEFieldWrapper &field) const; + HpackLookupResult lookup(const char *name, int name_len, const char *value, int value_len) const; int get_header_field(uint32_t index, MIMEFieldWrapper &header_field) const; - void add_header_field_to_dynamic_table(const MIMEField *field); - uint32_t get_dynamic_table_size() const; - bool set_dynamic_table_size(uint32_t new_size); - bool is_header_in_dynamic_table(const char *target_name, const char *target_value) const; + void add_header_field(const MIMEField *field); + uint32_t size() const; + bool update_maximum_size(uint32_t new_size); private: - Http2DynamicTable _dynamic_table; + HpackDynamicTable *_dynamic_table; }; -HpackFieldType hpack_parse_field_type(uint8_t ftype); - -static inline bool -hpack_field_is_literal(HpackFieldType ftype) -{ - return ftype == HPACK_FIELD_INDEXED_LITERAL || ftype == HPACK_FIELD_NOINDEX_LITERAL || ftype == HPACK_FIELD_NEVERINDEX_LITERAL; -} +// Low level interfaces int64_t encode_integer(uint8_t *buf_start, const uint8_t *buf_end, uint32_t value, uint8_t n); int64_t decode_integer(uint32_t &dst, const uint8_t *buf_start, const uint8_t *buf_end, uint8_t n); int64_t encode_string(uint8_t *buf_start, const uint8_t *buf_end, const char *value, size_t value_len); int64_t decode_string(Arena &arena, char **str, uint32_t &str_length, const uint8_t *buf_start, const uint8_t *buf_end); - int64_t encode_indexed_header_field(uint8_t *buf_start, const uint8_t *buf_end, uint32_t index); int64_t encode_literal_header_field_with_indexed_name(uint8_t *buf_start, const uint8_t *buf_end, const MIMEFieldWrapper &header, - uint32_t index, Http2IndexingTable &indexing_table, HpackFieldType type); + uint32_t index, HpackIndexingTable &indexing_table, HpackFieldType type); int64_t encode_literal_header_field_with_new_name(uint8_t *buf_start, const uint8_t *buf_end, const MIMEFieldWrapper &header, - Http2IndexingTable &indexing_table, HpackFieldType type); - -// When these functions returns minus value, any error occurs -// TODO Separate error code and length of processed buffer + HpackIndexingTable &indexing_table, HpackFieldType type); int64_t decode_indexed_header_field(MIMEFieldWrapper &header, const uint8_t *buf_start, const uint8_t *buf_end, - Http2IndexingTable &indexing_table); + HpackIndexingTable &indexing_table); int64_t decode_literal_header_field(MIMEFieldWrapper &header, const uint8_t *buf_start, const uint8_t *buf_end, - Http2IndexingTable &indexing_table); -int64_t update_dynamic_table_size(const uint8_t *buf_start, const uint8_t *buf_end, Http2IndexingTable &indexing_table); + HpackIndexingTable &indexing_table); +int64_t update_dynamic_table_size(const uint8_t *buf_start, const uint8_t *buf_end, HpackIndexingTable &indexing_table); + + +// High level interfaces +typedef HpackIndexingTable HpackHandle; +int64_t hpack_decode_header_block(HpackHandle &handle, HTTPHdr *hdr, const uint8_t *in_buf, const size_t in_buf_len); +int64_t hpack_encode_header_block(HpackHandle &handle, uint8_t *out_buf, const size_t out_buf_len, HTTPHdr *hdr); #endif /* __HPACK_H__ */ http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a39dd9d7/proxy/http2/HTTP2.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc index 3eac878..f563210 100644 --- a/proxy/http2/HTTP2.cc +++ b/proxy/http2/HTTP2.cc @@ -29,7 +29,21 @@ #include "P_RecProcess.h" const char *const HTTP2_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; -static size_t HPACK_LEN_STATUS_VALUE_STR = 3; + +// Constant strings for pseudo headers +const char *HTTP2_VALUE_SCHEME = ":scheme"; +const char *HTTP2_VALUE_METHOD = ":method"; +const char *HTTP2_VALUE_AUTHORITY = ":authority"; +const char *HTTP2_VALUE_PATH = ":path"; +const char *HTTP2_VALUE_STATUS = ":status"; + +const unsigned HTTP2_LEN_SCHEME = countof(":scheme") - 1; +const unsigned HTTP2_LEN_METHOD = countof(":method") - 1; +const unsigned HTTP2_LEN_AUTHORITY = countof(":authority") - 1; +const unsigned HTTP2_LEN_PATH = countof(":path") - 1; +const unsigned HTTP2_LEN_STATUS = countof(":status") - 1; + +static size_t HTTP2_LEN_STATUS_VALUE_STR = 3; // Statistics RecRawStatBlock *http2_rsb; @@ -391,7 +405,7 @@ http2_parse_window_update(IOVec iov, uint32_t &size) } MIMEParseResult -convert_from_2_to_1_1_header(HTTPHdr *headers) +http2_convert_header_from_2_to_1_1(HTTPHdr *headers) { MIMEField *field; @@ -402,19 +416,19 @@ convert_from_2_to_1_1_header(HTTPHdr *headers) int scheme_len, authority_len, path_len, method_len; // Get values of :scheme, :authority and :path to assemble requested URL - if ((field = headers->field_find(HPACK_VALUE_SCHEME, HPACK_LEN_SCHEME)) != NULL && field->value_is_valid()) { + if ((field = headers->field_find(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME)) != NULL && field->value_is_valid()) { scheme = field->value_get(&scheme_len); } else { return PARSE_ERROR; } - if ((field = headers->field_find(HPACK_VALUE_AUTHORITY, HPACK_LEN_AUTHORITY)) != NULL && field->value_is_valid()) { + if ((field = headers->field_find(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY)) != NULL && field->value_is_valid()) { authority = field->value_get(&authority_len); } else { return PARSE_ERROR; } - if ((field = headers->field_find(HPACK_VALUE_PATH, HPACK_LEN_PATH)) != NULL && field->value_is_valid()) { + if ((field = headers->field_find(HTTP2_VALUE_PATH, HTTP2_LEN_PATH)) != NULL && field->value_is_valid()) { path = field->value_get(&path_len); } else { return PARSE_ERROR; @@ -434,7 +448,7 @@ convert_from_2_to_1_1_header(HTTPHdr *headers) arena.str_free(url); // Get value of :method - if ((field = headers->field_find(HPACK_VALUE_METHOD, HPACK_LEN_METHOD)) != NULL && field->value_is_valid()) { + if ((field = headers->field_find(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD)) != NULL && field->value_is_valid()) { method = field->value_get(&method_len); int method_wks_idx = hdrtoken_tokenize(method, method_len); @@ -454,15 +468,15 @@ convert_from_2_to_1_1_header(HTTPHdr *headers) http_hdr_version_set(headers->m_http, version); // Remove HTTP/2 style headers - headers->field_delete(HPACK_VALUE_SCHEME, HPACK_LEN_SCHEME); - headers->field_delete(HPACK_VALUE_METHOD, HPACK_LEN_METHOD); - headers->field_delete(HPACK_VALUE_AUTHORITY, HPACK_LEN_AUTHORITY); - headers->field_delete(HPACK_VALUE_PATH, HPACK_LEN_PATH); + headers->field_delete(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME); + headers->field_delete(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD); + headers->field_delete(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY); + headers->field_delete(HTTP2_VALUE_PATH, HTTP2_LEN_PATH); } else { int status_len; const char *status; - if ((field = headers->field_find(HPACK_VALUE_STATUS, HPACK_LEN_STATUS)) != NULL) { + if ((field = headers->field_find(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS)) != NULL) { status = field->value_get(&status_len); headers->status_set(http_parse_status(status, status + status_len)); } else { @@ -470,7 +484,7 @@ convert_from_2_to_1_1_header(HTTPHdr *headers) } // Remove HTTP/2 style headers - headers->field_delete(HPACK_VALUE_STATUS, HPACK_LEN_STATUS); + headers->field_delete(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS); } // Check validity of all names and values @@ -484,246 +498,168 @@ convert_from_2_to_1_1_header(HTTPHdr *headers) return PARSE_DONE; } -static int64_t -http2_write_header_field(uint8_t *out, const uint8_t *end, MIMEFieldWrapper &header, Http2IndexingTable &indexing_table) -{ - HpackFieldType field_type = HPACK_FIELD_INDEXED_LITERAL; - - // Cookie less that 20 bytes and Authorization are never indexed - // This policy is refer to Firefox and nghttp2 - int name_len = 0, value_len = 0; - const char *name = header.name_get(&name_len); - header.value_get(&value_len); - if ((ptr_len_casecmp(name, name_len, MIME_FIELD_COOKIE, MIME_LEN_COOKIE) == 0 && value_len < 20) || - (ptr_len_casecmp(name, name_len, MIME_FIELD_AUTHORIZATION, MIME_LEN_AUTHORIZATION) == 0)) { - field_type = HPACK_FIELD_NEVERINDEX_LITERAL; - } - - // TODO Enable to configure selecting header field representation +void +http2_convert_header_from_1_1_to_2(HTTPHdr *headers) +{ + HTTPHdr tmp; + tmp.create(http_hdr_type_get(headers->m_http)); + tmp.copy(headers); + headers->fields_clear(); + + if (http_hdr_type_get(tmp.m_http) == HTTP_TYPE_RESPONSE) { + char status_str[HTTP2_LEN_STATUS_VALUE_STR + 1]; + snprintf(status_str, sizeof(status_str), "%d", tmp.status_get()); + + // Add ':status' header field + MIMEField *status_field = headers->field_create(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS); + status_field->value_set(headers->m_heap, headers->m_mime, status_str, HTTP2_LEN_STATUS_VALUE_STR); + headers->field_attach(status_field); + + MIMEFieldIter field_iter; + for (MIMEField *field = tmp.iter_get_first(&field_iter); field != NULL; field = tmp.iter_get_next(&field_iter)) { + // Intermediaries SHOULD remove connection-specific header fields. + const char *name; + int name_len; + const char *value; + int value_len; + name = field->name_get(&name_len); + if ((name_len == MIME_LEN_CONNECTION && strncasecmp(name, MIME_FIELD_CONNECTION, name_len) == 0) || + (name_len == MIME_LEN_KEEP_ALIVE && strncasecmp(name, MIME_FIELD_KEEP_ALIVE, name_len) == 0) || + (name_len == MIME_LEN_PROXY_CONNECTION && strncasecmp(name, MIME_FIELD_PROXY_CONNECTION, name_len) == 0) || + (name_len == MIME_LEN_TRANSFER_ENCODING && strncasecmp(name, MIME_FIELD_TRANSFER_ENCODING, name_len) == 0) || + (name_len == MIME_LEN_UPGRADE && strncasecmp(name, MIME_FIELD_UPGRADE, name_len) == 0)) { + continue; + } - const Http2LookupIndexResult &result = indexing_table.get_index(header); - if (result.index > 0) { - if (result.value_is_indexed) { - return encode_indexed_header_field(out, end, result.index); - } else { - return encode_literal_header_field_with_indexed_name(out, end, header, result.index, indexing_table, field_type); + MIMEField *newfield; + name = field->name_get(&name_len); + newfield = headers->field_create(name, name_len); + value = field->value_get(&value_len); + newfield->value_set(headers->m_heap, headers->m_mime, value, value_len); + tmp.field_delete(field); + headers->field_attach(newfield); } - } else { - return encode_literal_header_field_with_new_name(out, end, header, indexing_table, field_type); } + tmp.destroy(); } -int64_t -http2_write_psuedo_headers(HTTPHdr *in, uint8_t *out, uint64_t out_len, Http2IndexingTable &indexing_table) +Http2ErrorCode +http2_encode_header_blocks(HTTPHdr *in, uint8_t *out, uint32_t out_len, uint32_t *len_written, HpackHandle &handle) { - uint8_t *p = out; - uint8_t *end = out + out_len; - int64_t len; - - ink_assert(http_hdr_type_get(in->m_http) != HTTP_TYPE_UNKNOWN); - - // TODO Check whether buffer size is enough - - // Set psuedo header - if (http_hdr_type_get(in->m_http) == HTTP_TYPE_RESPONSE) { - char status_str[HPACK_LEN_STATUS_VALUE_STR + 1]; - snprintf(status_str, sizeof(status_str), "%d", in->status_get()); - - // Add 'Status:' dummy header field - MIMEField *status_field = mime_field_create(in->m_heap, in->m_http->m_fields_impl); - mime_field_name_value_set(in->m_heap, in->m_mime, status_field, -1, HPACK_VALUE_STATUS, HPACK_LEN_STATUS, status_str, - HPACK_LEN_STATUS_VALUE_STR, 0, HPACK_LEN_STATUS + HPACK_LEN_STATUS_VALUE_STR, true); - mime_hdr_field_attach(in->m_mime, status_field, 1, NULL); - - // Encode psuedo headers by HPACK - MIMEFieldWrapper header(status_field, in->m_heap, in->m_http->m_fields_impl); - - len = http2_write_header_field(p, end, header, indexing_table); - if (len == -1) - return -1; - p += len; - - // Remove dummy header field - in->field_delete(HPACK_VALUE_STATUS, HPACK_LEN_STATUS); + // TODO: It would be better to split Cookie header value + int64_t result = hpack_encode_header_block(handle, out, out_len, in); + if (result < 0) { + return HTTP2_ERROR_COMPRESSION_ERROR; } - - return p - out; -} - -int64_t -http2_write_header_fragment(HTTPHdr *in, MIMEFieldIter &field_iter, uint8_t *out, uint64_t out_len, - Http2IndexingTable &indexing_table, bool &cont) -{ - uint8_t *p = out; - uint8_t *end = out + out_len; - int64_t len; - - ink_assert(http_hdr_type_get(in->m_http) != HTTP_TYPE_UNKNOWN); - ink_assert(in); - - // TODO Get a index value from the tables for the header field, and then - // choose a representation type. - // TODO Each indexing types per field should be passed by a caller, HTTP/2 - // implementation. - - // Get first header field which is required encoding - MIMEField *field; - if (!field_iter.m_block) { - field = in->iter_get_first(&field_iter); - } else { - field = in->iter_get(&field_iter); + if (len_written) { + *len_written = result; } - - // Set mime headers - cont = false; - for (; field != NULL; field = in->iter_get_next(&field_iter)) { - // Intermediaries SHOULD remove connection-specific header fields. - int name_len; - const char *name = field->name_get(&name_len); - if ((name_len == MIME_LEN_CONNECTION && strncasecmp(name, MIME_FIELD_CONNECTION, name_len) == 0) || - (name_len == MIME_LEN_KEEP_ALIVE && strncasecmp(name, MIME_FIELD_KEEP_ALIVE, name_len) == 0) || - (name_len == MIME_LEN_PROXY_CONNECTION && strncasecmp(name, MIME_FIELD_PROXY_CONNECTION, name_len) == 0) || - (name_len == MIME_LEN_TRANSFER_ENCODING && strncasecmp(name, MIME_FIELD_TRANSFER_ENCODING, name_len) == 0) || - (name_len == MIME_LEN_UPGRADE && strncasecmp(name, MIME_FIELD_UPGRADE, name_len) == 0)) { - continue; - } - - MIMEFieldWrapper header(field, in->m_heap, in->m_http->m_fields_impl); - if ((len = http2_write_header_field(p, end, header, indexing_table)) == -1) { - if (p == out) { - // no progress was made, header was too big for the buffer, skipping for now - continue; - } - if (!cont) { - // Parsing a part of headers is done - cont = true; - return p - out; - } else { - // Parse error - return -1; - } - } - p += len; - } - - // Parsing all headers is done - return p - out; + return HTTP2_ERROR_NO_ERROR; } /* * Decode Header Blocks to Header List. */ -int64_t -http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint8_t *buf_end, Http2IndexingTable &indexing_table, +Http2ErrorCode +http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_t buf_len, uint32_t *len_read, HpackHandle &handle, bool &trailing_header) { - const uint8_t *cursor = buf_start; - HdrHeap *heap = hdr->m_heap; - HTTPHdrImpl *hh = hdr->m_http; - bool header_field_started = false; + const MIMEField *field; + const char *value; + int len; bool is_trailing_header = trailing_header; + int64_t result = hpack_decode_header_block(handle, hdr, buf_start, buf_len); - while (cursor < buf_end) { - int64_t read_bytes = 0; + if (result < 0) { + if (result == HPACK_ERROR_COMPRESSION_ERROR) { + return HTTP2_ERROR_COMPRESSION_ERROR; + } + return HTTP2_ERROR_PROTOCOL_ERROR; + } + if (len_read) { + *len_read = result; + } - // decode a header field encoded by HPACK - MIMEField *field = mime_field_create(heap, hh->m_fields_impl); - MIMEFieldWrapper header(field, heap, hh->m_fields_impl); - HpackFieldType ftype = hpack_parse_field_type(*cursor); - switch (ftype) { - case HPACK_FIELD_INDEX: - read_bytes = decode_indexed_header_field(header, cursor, buf_end, indexing_table); - if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { - return HPACK_ERROR_COMPRESSION_ERROR; - } - cursor += read_bytes; - header_field_started = true; - break; - case HPACK_FIELD_INDEXED_LITERAL: - case HPACK_FIELD_NOINDEX_LITERAL: - case HPACK_FIELD_NEVERINDEX_LITERAL: - read_bytes = decode_literal_header_field(header, cursor, buf_end, indexing_table); - if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { - return HPACK_ERROR_COMPRESSION_ERROR; - } - cursor += read_bytes; - header_field_started = true; - break; - case HPACK_FIELD_TABLESIZE_UPDATE: - if (header_field_started) { - return HPACK_ERROR_COMPRESSION_ERROR; + MIMEFieldIter iter; + unsigned int expected_pseudo_header_count = 4; + unsigned int pseudo_header_count = 0; + + if (is_trailing_header) { + expected_pseudo_header_count = 0; + } + for (field = hdr->iter_get_first(&iter); field != NULL; field = hdr->iter_get_next(&iter)) { + value = field->name_get(&len); + // Pseudo headers must appear before regular headers + if (len && value[0] == ':') { + ++pseudo_header_count; + if (pseudo_header_count > expected_pseudo_header_count) { + return HTTP2_ERROR_PROTOCOL_ERROR; } - read_bytes = update_dynamic_table_size(cursor, buf_end, indexing_table); - if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { - return HPACK_ERROR_COMPRESSION_ERROR; + } else { + if (pseudo_header_count != expected_pseudo_header_count) { + return HTTP2_ERROR_PROTOCOL_ERROR; } - cursor += read_bytes; - continue; - } - - int name_len = 0; - const char *name = field->name_get(&name_len); - - // ':' started header name is only allowed for pseudo headers - if (hdr->fields_count() >= 4 && (name_len <= 0 || name[0] == ':')) { - // Decoded header field is invalid - return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; } + // Check whether header field name is lower case + // This check should be here but it will fail because WKSs in MIMEField is old fashioned. + // for (uint32_t i = 0; i < len; ++i) { + // if (ParseRules::is_upalpha(value[i])) { + // return HTTP2_ERROR_PROTOCOL_ERROR; + // } + // } + } - // rfc7540,sec8.1.2.2: Any message containing connection-specific header - // fields MUST be treated as malformed - if (name == MIME_FIELD_CONNECTION) { - return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; - } + // rfc7540,sec8.1.2.2: Any message containing connection-specific header + // fields MUST be treated as malformed + if (hdr->field_find(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION) != NULL) { + return HTTP2_ERROR_PROTOCOL_ERROR; + } - // :path pseudo header MUST NOT empty for http or https URIs - if (static_cast<unsigned>(name_len) == HPACK_LEN_PATH && strncmp(name, HPACK_VALUE_PATH, name_len) == 0) { - int value_len = 0; - field->value_get(&value_len); - if (value_len == 0) { - return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; - } + // :path pseudo header MUST NOT empty for http or https URIs + field = hdr->field_find(HTTP2_VALUE_PATH, HTTP2_LEN_PATH); + if (field) { + field->value_get(&len); + if (len == 0) { + return HTTP2_ERROR_PROTOCOL_ERROR; } + } - // when The TE header field is received, it MUST NOT contain any value other than "trailers". - if (name_len == MIME_LEN_TE && strncmp(name, MIME_FIELD_TE, name_len) == 0) { - int value_len = 0; - const char *value = field->value_get(&value_len); - const char trailers[] = "trailers"; - if (!(value_len == (sizeof(trailers) - 1) && memcmp(value, trailers, value_len) == 0)) { - return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; - } - } + // turn on that we have a trailer header + const char trailer_name[] = "trailer"; + field = hdr->field_find(trailer_name, sizeof(trailer_name) - 1); + if (field) { + trailing_header = true; + } - // turn on that we have a trailer header - const char trailer_name[] = "trailer"; - if (name_len == (sizeof(trailer_name) - 1) && strncmp(name, trailer_name, sizeof(trailer_name) - 1) == 0) { - trailing_header = true; + // when The TE header field is received, it MUST NOT contain any + // value other than "trailers". + field = hdr->field_find(MIME_FIELD_TE, MIME_LEN_TE); + if (field) { + value = field->value_get(&len); + if (!(len == 8 && memcmp(value, "trailers", 8) == 0)) { + return HTTP2_ERROR_PROTOCOL_ERROR; } - - // Store to HdrHeap - mime_hdr_field_attach(hh->m_fields_impl, field, 1, NULL); } if (!is_trailing_header) { // Check psuedo headers if (hdr->fields_count() >= 4) { - if (hdr->field_find(HPACK_VALUE_SCHEME, HPACK_LEN_SCHEME) == NULL || - hdr->field_find(HPACK_VALUE_METHOD, HPACK_LEN_METHOD) == NULL || - hdr->field_find(HPACK_VALUE_PATH, HPACK_LEN_PATH) == NULL || - hdr->field_find(HPACK_VALUE_AUTHORITY, HPACK_LEN_AUTHORITY) == NULL) { + if (hdr->field_find(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME) == NULL || + hdr->field_find(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD) == NULL || + hdr->field_find(HTTP2_VALUE_PATH, HTTP2_LEN_PATH) == NULL || + hdr->field_find(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY) == NULL || + hdr->field_find(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS) != NULL) { // Decoded header field is invalid - return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; + return HTTP2_ERROR_PROTOCOL_ERROR; } } else { // Psuedo headers is insufficient - return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; + return HTTP2_ERROR_PROTOCOL_ERROR; } } - // Parsing all headers is done - return cursor - buf_start; + return HTTP2_ERROR_NO_ERROR; } // Initialize this subsystem with librecords configs (for now) http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a39dd9d7/proxy/http2/HTTP2.h ---------------------------------------------------------------------- diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h index 9b62b17..a70703c 100644 --- a/proxy/http2/HTTP2.h +++ b/proxy/http2/HTTP2.h @@ -326,13 +326,13 @@ bool http2_parse_goaway(IOVec, Http2Goaway &); bool http2_parse_window_update(IOVec, uint32_t &); -int64_t http2_decode_header_blocks(HTTPHdr *, const uint8_t *, const uint8_t *, Http2IndexingTable &, bool &); +Http2ErrorCode http2_decode_header_blocks(HTTPHdr *, const uint8_t *, const uint32_t, uint32_t *, HpackHandle &, bool &); -MIMEParseResult convert_from_2_to_1_1_header(HTTPHdr *); +Http2ErrorCode http2_encode_header_blocks(HTTPHdr *, uint8_t *, uint32_t, uint32_t *, HpackHandle &); -int64_t http2_write_psuedo_headers(HTTPHdr *, uint8_t *, uint64_t, Http2IndexingTable &); +MIMEParseResult http2_convert_header_from_2_to_1_1(HTTPHdr *); +void http2_convert_header_from_1_1_to_2(HTTPHdr *); -int64_t http2_write_header_fragment(HTTPHdr *, MIMEFieldIter &, uint8_t *, uint64_t, Http2IndexingTable &, bool &); // Not sure where else to put this, but figure this is as good of a start as // anything else. http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a39dd9d7/proxy/http2/Http2ConnectionState.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc index 5180b3f..713b91f 100644 --- a/proxy/http2/Http2ConnectionState.cc +++ b/proxy/http2/Http2ConnectionState.cc @@ -277,12 +277,14 @@ rcv_headers_frame(Http2ConnectionState &cstate, const Http2Frame &frame) skip_fetcher = true; } - const int64_t decoded_bytes = stream->decode_header_blocks(*cstate.local_indexing_table); + Http2ErrorCode result = stream->decode_header_blocks(*cstate.local_hpack_handle); - if (decoded_bytes == 0 || decoded_bytes == HPACK_ERROR_COMPRESSION_ERROR) { - return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_COMPRESSION_ERROR); - } else if (decoded_bytes == HPACK_ERROR_HTTP2_PROTOCOL_ERROR) { - return Http2Error(HTTP2_ERROR_CLASS_STREAM, HTTP2_ERROR_PROTOCOL_ERROR); + if (result != HTTP2_ERROR_NO_ERROR) { + if (result == HTTP2_ERROR_COMPRESSION_ERROR) { + return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_COMPRESSION_ERROR); + } else { + return Http2Error(HTTP2_ERROR_CLASS_STREAM, HTTP2_ERROR_PROTOCOL_ERROR); + } } if (!skip_fetcher) { @@ -678,12 +680,14 @@ rcv_continuation_frame(Http2ConnectionState &cstate, const Http2Frame &frame) return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); } - const int64_t decoded_bytes = stream->decode_header_blocks(*cstate.local_indexing_table); + Http2ErrorCode result = stream->decode_header_blocks(*cstate.local_hpack_handle); - if (decoded_bytes == 0 || decoded_bytes == HPACK_ERROR_COMPRESSION_ERROR) { - return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_COMPRESSION_ERROR); - } else if (decoded_bytes == HPACK_ERROR_HTTP2_PROTOCOL_ERROR) { - return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_PROTOCOL_ERROR); + if (result != HTTP2_ERROR_NO_ERROR) { + if (result == HTTP2_ERROR_COMPRESSION_ERROR) { + return Http2Error(HTTP2_ERROR_CLASS_CONNECTION, HTTP2_ERROR_COMPRESSION_ERROR); + } else { + return Http2Error(HTTP2_ERROR_CLASS_STREAM, HTTP2_ERROR_PROTOCOL_ERROR); + } } stream->init_fetcher(cstate); @@ -991,9 +995,11 @@ Http2ConnectionState::send_data_frame(FetchSM *fetch_sm) void Http2ConnectionState::send_headers_frame(FetchSM *fetch_sm) { - const size_t buf_len = BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]) - HTTP2_FRAME_HEADER_LEN; - uint8_t payload_buffer[buf_len]; - size_t payload_length = 0; + uint8_t *buf = NULL; + uint32_t buf_len = 0; + uint32_t header_blocks_size = 0; + int payload_length = 0; + uint64_t sent = 0; uint8_t flags = 0x00; Http2Stream *stream = static_cast<Http2Stream *>(fetch_sm->ext_get_user_data()); @@ -1001,43 +1007,57 @@ Http2ConnectionState::send_headers_frame(FetchSM *fetch_sm) DebugHttp2Stream(ua_session, stream->get_id(), "Send HEADERS frame"); - // Write pseudo headers - payload_length += http2_write_psuedo_headers(resp_header, payload_buffer, buf_len, *(this->remote_indexing_table)); - - // If response body is empty, set END_STREAM flag to HEADERS frame - // Must check to ensure content-length is there. Otherwise the value defaults - // to 0 - if (resp_header->presence(MIME_PRESENCE_CONTENT_LENGTH) && resp_header->get_content_length() == 0) { - flags |= HTTP2_FLAGS_HEADERS_END_STREAM; + http2_convert_header_from_1_1_to_2(resp_header); + buf_len = resp_header->length_get() * 2; // Make it double just in case + buf = (uint8_t *)ats_malloc(buf_len); + if (buf == NULL) { + return; + } + Http2ErrorCode result = http2_encode_header_blocks(resp_header, buf, buf_len, &header_blocks_size, *(this->remote_hpack_handle)); + if (result != HTTP2_ERROR_NO_ERROR) { + ats_free(buf); + return; } - MIMEFieldIter field_iter; - bool cont = false; - do { - // Handle first sending frame is as HEADERS - Http2FrameType type = cont ? HTTP2_FRAME_TYPE_CONTINUATION : HTTP2_FRAME_TYPE_HEADERS; - - // Encode by HPACK naive - payload_length += http2_write_header_fragment(resp_header, field_iter, payload_buffer + payload_length, - buf_len - payload_length, *(this->remote_indexing_table), cont); - - // If buffer size is enough to send rest of headers, set END_HEADERS flag - if (buf_len >= payload_length && !cont) { - flags |= HTTP2_FLAGS_HEADERS_END_HEADERS; + // Send a HEADERS frame + if (header_blocks_size <= BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]) - HTTP2_FRAME_HEADER_LEN) { + payload_length = header_blocks_size; + flags |= HTTP2_FLAGS_HEADERS_END_HEADERS; + if (resp_header->presence(MIME_PRESENCE_CONTENT_LENGTH) && resp_header->get_content_length() == 0) { + flags |= HTTP2_FLAGS_HEADERS_END_STREAM; } - - // Create HEADERS or CONTINUATION frame - Http2Frame headers(type, stream->get_id(), flags); - headers.alloc(buffer_size_index[type]); - http2_write_headers(payload_buffer, payload_length, headers.write()); + } else { + payload_length = BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]) - HTTP2_FRAME_HEADER_LEN; + } + Http2Frame headers(HTTP2_FRAME_TYPE_HEADERS, stream->get_id(), flags); + headers.alloc(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]); + http2_write_headers(buf, payload_length, headers.write()); + headers.finalize(payload_length); + // xmit event + SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); + this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &headers); + sent += payload_length; + + // Send CONTINUATION frames + flags = 0; + while (sent < header_blocks_size) { + DebugHttp2Stream(ua_session, stream->get_id(), "Send CONTINUATION frame"); + payload_length = MIN(BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_CONTINUATION]) - HTTP2_FRAME_HEADER_LEN, + header_blocks_size - sent); + if (sent + payload_length == header_blocks_size) { + flags |= HTTP2_FLAGS_CONTINUATION_END_HEADERS; + } + Http2Frame headers(HTTP2_FRAME_TYPE_CONTINUATION, stream->get_id(), flags); + headers.alloc(buffer_size_index[HTTP2_FRAME_TYPE_CONTINUATION]); + http2_write_headers(buf + sent, payload_length, headers.write()); headers.finalize(payload_length); - // xmit event SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &headers); + sent += payload_length; + } - payload_length = 0; // we will reuse the same buffer for more headers - } while (cont); + ats_free(buf); } void http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a39dd9d7/proxy/http2/Http2ConnectionState.h ---------------------------------------------------------------------- diff --git a/proxy/http2/Http2ConnectionState.h b/proxy/http2/Http2ConnectionState.h index afd0cf2..a80fc9d 100644 --- a/proxy/http2/Http2ConnectionState.h +++ b/proxy/http2/Http2ConnectionState.h @@ -112,8 +112,8 @@ public: } Http2ClientSession *ua_session; - Http2IndexingTable *local_indexing_table; - Http2IndexingTable *remote_indexing_table; + HpackHandle *local_hpack_handle; + HpackHandle *remote_hpack_handle; // Settings. Http2ConnectionSettings server_settings; @@ -122,8 +122,8 @@ public: void init() { - local_indexing_table = new Http2IndexingTable(); - remote_indexing_table = new Http2IndexingTable(); + local_hpack_handle = new HpackHandle(HTTP2_HEADER_TABLE_SIZE); + remote_hpack_handle = new HpackHandle(HTTP2_HEADER_TABLE_SIZE); continued_buffer.iov_base = NULL; continued_buffer.iov_len = 0; @@ -135,8 +135,8 @@ public: cleanup_streams(); mutex = NULL; // magic happens - assigning to NULL frees the ProxyMutex - delete local_indexing_table; - delete remote_indexing_table; + delete local_hpack_handle; + delete remote_hpack_handle; ats_free(continued_buffer.iov_base); } http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a39dd9d7/proxy/http2/Http2Stream.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc index 30c175f..b998be7 100644 --- a/proxy/http2/Http2Stream.cc +++ b/proxy/http2/Http2Stream.cc @@ -34,7 +34,7 @@ Http2Stream::init_fetcher(Http2ConnectionState &cstate) extern ClassAllocator<FetchSM> FetchSMAllocator; // Convert header to HTTP/1.1 format - if (convert_from_2_to_1_1_header(&_req_header) == PARSE_ERROR) { + if (http2_convert_header_from_2_to_1_1(&_req_header) == PARSE_ERROR) { return false; } http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a39dd9d7/proxy/http2/Http2Stream.h ---------------------------------------------------------------------- diff --git a/proxy/http2/Http2Stream.h b/proxy/http2/Http2Stream.h index b89fb02..311733c 100644 --- a/proxy/http2/Http2Stream.h +++ b/proxy/http2/Http2Stream.h @@ -98,11 +98,11 @@ public: return trailing_header; } - int64_t - decode_header_blocks(Http2IndexingTable &indexing_table) + Http2ErrorCode + decode_header_blocks(HpackHandle &hpack_handle) { - return http2_decode_header_blocks(&_req_header, (const uint8_t *)header_blocks, - (const uint8_t *)header_blocks + header_blocks_length, indexing_table, trailing_header); + return http2_decode_header_blocks(&_req_header, (const uint8_t *)header_blocks, header_blocks_length, NULL, hpack_handle, + trailing_header); } // Check entire DATA payload length if content-length: header is exist http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a39dd9d7/proxy/http2/RegressionHPACK.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/RegressionHPACK.cc b/proxy/http2/RegressionHPACK.cc index 5e52f15..779d576 100644 --- a/proxy/http2/RegressionHPACK.cc +++ b/proxy/http2/RegressionHPACK.cc @@ -21,7 +21,6 @@ * limitations under the License. */ -#include "HTTP2.h" #include "HPACK.h" #include "HuffmanCodec.h" #include "ts/TestBox.h" @@ -362,7 +361,7 @@ REGRESSION_TEST(HPACK_EncodeLiteralHeaderField)(RegressionTest *t, int, int *pst uint8_t buf[BUFSIZE_FOR_REGRESSION_TEST]; int len; - Http2IndexingTable indexing_table; + HpackIndexingTable indexing_table(4096); for (unsigned int i = 9; i < sizeof(literal_test_case) / sizeof(literal_test_case[0]); i++) { memset(buf, 0, BUFSIZE_FOR_REGRESSION_TEST); @@ -395,8 +394,8 @@ REGRESSION_TEST(HPACK_Encode)(RegressionTest *t, int, int *pstatus) box = REGRESSION_TEST_PASSED; uint8_t buf[BUFSIZE_FOR_REGRESSION_TEST]; - Http2IndexingTable indexing_table; - indexing_table.set_dynamic_table_size(DYNAMIC_TABLE_SIZE_FOR_REGRESSION_TEST); + HpackIndexingTable indexing_table(4096); + indexing_table.update_maximum_size(DYNAMIC_TABLE_SIZE_FOR_REGRESSION_TEST); for (unsigned int i = 0; i < sizeof(encoded_field_response_test_case) / sizeof(encoded_field_response_test_case[0]); i++) { ats_scoped_obj<HTTPHdr> headers(new HTTPHdr); @@ -408,30 +407,21 @@ REGRESSION_TEST(HPACK_Encode)(RegressionTest *t, int, int *pstatus) if (strlen(expected_name) == 0) break; - if (strlen(expected_name) == HPACK_LEN_STATUS && strncasecmp(expected_name, HPACK_VALUE_STATUS, HPACK_LEN_STATUS) == 0) { - headers->status_set(http_parse_status(expected_value, expected_value + strlen(expected_value))); - } else { - MIMEField *field = mime_field_create(headers->m_heap, headers->m_http->m_fields_impl); - field->name_set(headers->m_heap, headers->m_http->m_fields_impl, expected_name, strlen(expected_name)); - field->value_set(headers->m_heap, headers->m_http->m_fields_impl, expected_value, strlen(expected_value)); - mime_hdr_field_attach(headers->m_http->m_fields_impl, field, 1, NULL); - } + MIMEField *field = mime_field_create(headers->m_heap, headers->m_http->m_fields_impl); + field->name_set(headers->m_heap, headers->m_http->m_fields_impl, expected_name, strlen(expected_name)); + field->value_set(headers->m_heap, headers->m_http->m_fields_impl, expected_value, strlen(expected_value)); + mime_hdr_field_attach(headers->m_http->m_fields_impl, field, 1, NULL); } memset(buf, 0, BUFSIZE_FOR_REGRESSION_TEST); uint64_t buf_len = BUFSIZE_FOR_REGRESSION_TEST; - int64_t len = http2_write_psuedo_headers(headers, buf, buf_len, indexing_table); - buf_len -= len; + int64_t len = hpack_encode_header_block(indexing_table, buf, buf_len, headers); if (len < 0) { - box.check(false, "http2_write_psuedo_headers returned negative value: %" PRId64, len); + box.check(false, "hpack_encode_header_blocks returned negative value: %" PRId64, len); break; } - MIMEFieldIter field_iter; - bool cont = false; - len += http2_write_header_fragment(headers, field_iter, buf + len, buf_len, indexing_table, cont); - box.check(len == encoded_field_response_test_case[i].encoded_field_len, "encoded length was %" PRId64 ", expecting %d", len, encoded_field_response_test_case[i].encoded_field_len); box.check(len > 0 && memcmp(buf, encoded_field_response_test_case[i].encoded_field, len) == 0, "encoded value was invalid"); @@ -442,16 +432,19 @@ REGRESSION_TEST(HPACK_Encode)(RegressionTest *t, int, int *pstatus) j++) { const char *expected_name = dynamic_table_response_test_case[i][j].name; const char *expected_value = dynamic_table_response_test_case[i][j].value; + int expected_name_len = strlen(expected_name); + int expected_value_len = strlen(expected_value); - if (strlen(expected_name) == 0) + if (expected_name_len == 0) break; - box.check(indexing_table.is_header_in_dynamic_table(expected_name, expected_value), "dynamic table has unexpected entries"); + HpackLookupResult lookupResult = indexing_table.lookup(expected_name, expected_name_len, expected_value, expected_value_len); + box.check(lookupResult.match_type == HPACK_EXACT_MATCH && lookupResult.index_type == HPACK_INDEX_TYPE_DYNAMIC, + "the header field is not indexed"); expected_dynamic_table_size += dynamic_table_response_test_case[i][j].size; } - box.check(indexing_table.get_dynamic_table_size() == expected_dynamic_table_size, "dynamic table is unexpected size: %d", - indexing_table.get_dynamic_table_size()); + box.check(indexing_table.size() == expected_dynamic_table_size, "dynamic table is unexpected size: %d", indexing_table.size()); } } @@ -502,7 +495,7 @@ REGRESSION_TEST(HPACK_DecodeIndexedHeaderField)(RegressionTest *t, int, int *pst TestBox box(t, pstatus); box = REGRESSION_TEST_PASSED; - Http2IndexingTable indexing_table; + HpackIndexingTable indexing_table(4096); for (unsigned int i = 0; i < sizeof(indexed_test_case) / sizeof(indexed_test_case[0]); i++) { ats_scoped_obj<HTTPHdr> headers(new HTTPHdr); @@ -532,7 +525,7 @@ REGRESSION_TEST(HPACK_DecodeLiteralHeaderField)(RegressionTest *t, int, int *pst TestBox box(t, pstatus); box = REGRESSION_TEST_PASSED; - Http2IndexingTable indexing_table; + HpackIndexingTable indexing_table(4096); for (unsigned int i = 0; i < sizeof(literal_test_case) / sizeof(literal_test_case[0]); i++) { ats_scoped_obj<HTTPHdr> headers(new HTTPHdr); @@ -563,17 +556,14 @@ REGRESSION_TEST(HPACK_Decode)(RegressionTest *t, int, int *pstatus) TestBox box(t, pstatus); box = REGRESSION_TEST_PASSED; - Http2IndexingTable indexing_table; - bool trailing_header = false; + HpackIndexingTable indexing_table(4096); for (unsigned int i = 0; i < sizeof(encoded_field_request_test_case) / sizeof(encoded_field_request_test_case[0]); i++) { ats_scoped_obj<HTTPHdr> headers(new HTTPHdr); headers->create(HTTP_TYPE_REQUEST); - http2_decode_header_blocks(headers, encoded_field_request_test_case[i].encoded_field, - encoded_field_request_test_case[i].encoded_field + - encoded_field_request_test_case[i].encoded_field_len, - indexing_table, trailing_header); + hpack_decode_header_block(indexing_table, headers, encoded_field_request_test_case[i].encoded_field, + encoded_field_request_test_case[i].encoded_field_len); for (unsigned int j = 0; j < sizeof(raw_field_request_test_case[i]) / sizeof(raw_field_request_test_case[i][0]); j++) { const char *expected_name = raw_field_request_test_case[i][j].raw_name;
