| // Copyright 2017 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "http2/hpack/decoder/hpack_decoder.h" | |
| // Tests of HpackDecoder. | |
| #include <string> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| #include "http2/decoder/decode_buffer.h" | |
| #include "http2/hpack/decoder/hpack_decoder_listener.h" | |
| #include "http2/hpack/decoder/hpack_decoder_state.h" | |
| #include "http2/hpack/decoder/hpack_decoder_tables.h" | |
| #include "http2/hpack/http2_hpack_constants.h" | |
| #include "http2/hpack/tools/hpack_block_builder.h" | |
| #include "http2/hpack/tools/hpack_example.h" | |
| #include "http2/http2_constants.h" | |
| #include "http2/platform/api/http2_logging.h" | |
| #include "http2/platform/api/http2_test_helpers.h" | |
| #include "http2/test_tools/http2_random.h" | |
| #include "http2/tools/random_util.h" | |
| #include "common/platform/api/quiche_test.h" | |
| using ::testing::AssertionFailure; | |
| using ::testing::AssertionResult; | |
| using ::testing::AssertionSuccess; | |
| using ::testing::ElementsAreArray; | |
| using ::testing::Eq; | |
| namespace http2 { | |
| namespace test { | |
| class HpackDecoderStatePeer { | |
| public: | |
| static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) { | |
| return &state->decoder_tables_; | |
| } | |
| static void set_listener(HpackDecoderState* state, | |
| HpackDecoderListener* listener) { | |
| state->listener_ = listener; | |
| } | |
| }; | |
| class HpackDecoderPeer { | |
| public: | |
| static HpackDecoderState* GetDecoderState(HpackDecoder* decoder) { | |
| return &decoder->decoder_state_; | |
| } | |
| static HpackDecoderTables* GetDecoderTables(HpackDecoder* decoder) { | |
| return HpackDecoderStatePeer::GetDecoderTables(GetDecoderState(decoder)); | |
| } | |
| }; | |
| namespace { | |
| typedef std::pair<std::string, std::string> HpackHeaderEntry; | |
| typedef std::vector<HpackHeaderEntry> HpackHeaderEntries; | |
| // TODO(jamessynge): Create a ...test_utils.h file with the mock listener | |
| // and with VerifyDynamicTableContents. | |
| class MockHpackDecoderListener : public HpackDecoderListener { | |
| public: | |
| MOCK_METHOD(void, OnHeaderListStart, (), (override)); | |
| MOCK_METHOD(void, | |
| OnHeader, | |
| (const std::string& name, const std::string& value), | |
| (override)); | |
| MOCK_METHOD(void, OnHeaderListEnd, (), (override)); | |
| MOCK_METHOD(void, | |
| OnHeaderErrorDetected, | |
| (absl::string_view error_message), | |
| (override)); | |
| }; | |
| class HpackDecoderTest : public QuicheTestWithParam<bool>, | |
| public HpackDecoderListener { | |
| protected: | |
| // Note that we initialize the random number generator with the same seed | |
| // for each individual test, therefore the order in which the tests are | |
| // executed does not effect the sequence produced by the RNG within any | |
| // one test. | |
| HpackDecoderTest() : decoder_(this, 4096) { | |
| fragment_the_hpack_block_ = GetParam(); | |
| } | |
| ~HpackDecoderTest() override = default; | |
| void OnHeaderListStart() override { | |
| ASSERT_FALSE(saw_start_); | |
| ASSERT_FALSE(saw_end_); | |
| saw_start_ = true; | |
| header_entries_.clear(); | |
| } | |
| // Called for each header name-value pair that is decoded, in the order they | |
| // appear in the HPACK block. Multiple values for a given key will be emitted | |
| // as multiple calls to OnHeader. | |
| void OnHeader(const std::string& name, const std::string& value) override { | |
| ASSERT_TRUE(saw_start_); | |
| ASSERT_FALSE(saw_end_); | |
| header_entries_.emplace_back(name, value); | |
| } | |
| // OnHeaderBlockEnd is called after successfully decoding an HPACK block. Will | |
| // only be called once per block, even if it extends into CONTINUATION frames. | |
| // A callback method which notifies when the parser finishes handling a | |
| // header block (i.e. the containing frame has the END_STREAM flag set). | |
| // Also indicates the total number of bytes in this block. | |
| void OnHeaderListEnd() override { | |
| ASSERT_TRUE(saw_start_); | |
| ASSERT_FALSE(saw_end_); | |
| ASSERT_TRUE(error_messages_.empty()); | |
| saw_end_ = true; | |
| } | |
| // OnHeaderErrorDetected is called if an error is detected while decoding. | |
| // error_message may be used in a GOAWAY frame as the Opaque Data. | |
| void OnHeaderErrorDetected(absl::string_view error_message) override { | |
| ASSERT_TRUE(saw_start_); | |
| error_messages_.push_back(std::string(error_message)); | |
| // No further callbacks should be made at this point, so replace 'this' as | |
| // the listener with mock_listener_, which is a strict mock, so will | |
| // generate an error for any calls. | |
| HpackDecoderStatePeer::set_listener( | |
| HpackDecoderPeer::GetDecoderState(&decoder_), &mock_listener_); | |
| } | |
| AssertionResult DecodeBlock(absl::string_view block) { | |
| HTTP2_VLOG(1) << "HpackDecoderTest::DecodeBlock"; | |
| VERIFY_FALSE(decoder_.DetectError()); | |
| VERIFY_TRUE(error_messages_.empty()); | |
| VERIFY_FALSE(saw_start_); | |
| VERIFY_FALSE(saw_end_); | |
| header_entries_.clear(); | |
| VERIFY_FALSE(decoder_.DetectError()); | |
| VERIFY_TRUE(decoder_.StartDecodingBlock()); | |
| VERIFY_FALSE(decoder_.DetectError()); | |
| if (fragment_the_hpack_block_) { | |
| // See note in ctor regarding RNG. | |
| while (!block.empty()) { | |
| size_t fragment_size = random_.RandomSizeSkewedLow(block.size()); | |
| DecodeBuffer db(block.substr(0, fragment_size)); | |
| VERIFY_TRUE(decoder_.DecodeFragment(&db)); | |
| VERIFY_EQ(0u, db.Remaining()); | |
| block.remove_prefix(fragment_size); | |
| } | |
| } else { | |
| DecodeBuffer db(block); | |
| VERIFY_TRUE(decoder_.DecodeFragment(&db)); | |
| VERIFY_EQ(0u, db.Remaining()); | |
| } | |
| VERIFY_FALSE(decoder_.DetectError()); | |
| VERIFY_TRUE(decoder_.EndDecodingBlock()); | |
| if (saw_end_) { | |
| VERIFY_FALSE(decoder_.DetectError()); | |
| VERIFY_TRUE(error_messages_.empty()); | |
| } else { | |
| VERIFY_TRUE(decoder_.DetectError()); | |
| VERIFY_FALSE(error_messages_.empty()); | |
| } | |
| saw_start_ = saw_end_ = false; | |
| return AssertionSuccess(); | |
| } | |
| const HpackDecoderTables& GetDecoderTables() { | |
| return *HpackDecoderPeer::GetDecoderTables(&decoder_); | |
| } | |
| const HpackStringPair* Lookup(size_t index) { | |
| return GetDecoderTables().Lookup(index); | |
| } | |
| size_t current_header_table_size() { | |
| return GetDecoderTables().current_header_table_size(); | |
| } | |
| size_t header_table_size_limit() { | |
| return GetDecoderTables().header_table_size_limit(); | |
| } | |
| void set_header_table_size_limit(size_t size) { | |
| HpackDecoderPeer::GetDecoderTables(&decoder_)->DynamicTableSizeUpdate(size); | |
| } | |
| // dynamic_index is one-based, because that is the way RFC 7541 shows it. | |
| AssertionResult VerifyEntry(size_t dynamic_index, | |
| const char* name, | |
| const char* value) { | |
| const HpackStringPair* entry = | |
| Lookup(dynamic_index + kFirstDynamicTableIndex - 1); | |
| VERIFY_NE(entry, nullptr); | |
| VERIFY_EQ(entry->name, name); | |
| VERIFY_EQ(entry->value, value); | |
| return AssertionSuccess(); | |
| } | |
| AssertionResult VerifyNoEntry(size_t dynamic_index) { | |
| const HpackStringPair* entry = | |
| Lookup(dynamic_index + kFirstDynamicTableIndex - 1); | |
| VERIFY_EQ(entry, nullptr); | |
| return AssertionSuccess(); | |
| } | |
| AssertionResult VerifyDynamicTableContents( | |
| const std::vector<std::pair<const char*, const char*>>& entries) { | |
| size_t index = 1; | |
| for (const auto& entry : entries) { | |
| VERIFY_SUCCESS(VerifyEntry(index, entry.first, entry.second)); | |
| ++index; | |
| } | |
| VERIFY_SUCCESS(VerifyNoEntry(index)); | |
| return AssertionSuccess(); | |
| } | |
| Http2Random random_; | |
| HpackDecoder decoder_; | |
| testing::StrictMock<MockHpackDecoderListener> mock_listener_; | |
| HpackHeaderEntries header_entries_; | |
| std::vector<std::string> error_messages_; | |
| bool fragment_the_hpack_block_; | |
| bool saw_start_ = false; | |
| bool saw_end_ = false; | |
| }; | |
| INSTANTIATE_TEST_SUITE_P(AllWays, HpackDecoderTest, ::testing::Bool()); | |
| // Test based on RFC 7541, section C.3: Request Examples without Huffman Coding. | |
| // This section shows several consecutive header lists, corresponding to HTTP | |
| // requests, on the same connection. | |
| // http://httpwg.org/specs/rfc7541.html#rfc.section.C.3 | |
| TEST_P(HpackDecoderTest, C3_RequestExamples) { | |
| // C.3.1 First Request | |
| std::string hpack_block = HpackExampleToStringOrDie(R"( | |
| 82 | == Indexed - Add == | |
| | idx = 2 | |
| | -> :method: GET | |
| 86 | == Indexed - Add == | |
| | idx = 6 | |
| | -> :scheme: http | |
| 84 | == Indexed - Add == | |
| | idx = 4 | |
| | -> :path: / | |
| 41 | == Literal indexed == | |
| | Indexed name (idx = 1) | |
| | :authority | |
| 0f | Literal value (len = 15) | |
| 7777 772e 6578 616d 706c 652e 636f 6d | www.example.com | |
| | -> :authority: | |
| | www.example.com | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT(header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":method", "GET"}, | |
| HpackHeaderEntry{":scheme", "http"}, | |
| HpackHeaderEntry{":path", "/"}, | |
| HpackHeaderEntry{":authority", "www.example.com"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 57) :authority: www.example.com | |
| // Table size: 57 | |
| ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}})); | |
| ASSERT_EQ(57u, current_header_table_size()); | |
| // C.3.2 Second Request | |
| hpack_block = HpackExampleToStringOrDie(R"( | |
| 82 | == Indexed - Add == | |
| | idx = 2 | |
| | -> :method: GET | |
| 86 | == Indexed - Add == | |
| | idx = 6 | |
| | -> :scheme: http | |
| 84 | == Indexed - Add == | |
| | idx = 4 | |
| | -> :path: / | |
| be | == Indexed - Add == | |
| | idx = 62 | |
| | -> :authority: | |
| | www.example.com | |
| 58 | == Literal indexed == | |
| | Indexed name (idx = 24) | |
| | cache-control | |
| 08 | Literal value (len = 8) | |
| 6e6f 2d63 6163 6865 | no-cache | |
| | -> cache-control: no-cache | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT(header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":method", "GET"}, | |
| HpackHeaderEntry{":scheme", "http"}, | |
| HpackHeaderEntry{":path", "/"}, | |
| HpackHeaderEntry{":authority", "www.example.com"}, | |
| HpackHeaderEntry{"cache-control", "no-cache"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 53) cache-control: no-cache | |
| // [ 2] (s = 57) :authority: www.example.com | |
| // Table size: 110 | |
| ASSERT_TRUE(VerifyDynamicTableContents( | |
| {{"cache-control", "no-cache"}, {":authority", "www.example.com"}})); | |
| ASSERT_EQ(110u, current_header_table_size()); | |
| // C.3.2 Third Request | |
| hpack_block = HpackExampleToStringOrDie(R"( | |
| 82 | == Indexed - Add == | |
| | idx = 2 | |
| | -> :method: GET | |
| 87 | == Indexed - Add == | |
| | idx = 7 | |
| | -> :scheme: https | |
| 85 | == Indexed - Add == | |
| | idx = 5 | |
| | -> :path: /index.html | |
| bf | == Indexed - Add == | |
| | idx = 63 | |
| | -> :authority: | |
| | www.example.com | |
| 40 | == Literal indexed == | |
| 0a | Literal name (len = 10) | |
| 6375 7374 6f6d 2d6b 6579 | custom-key | |
| 0c | Literal value (len = 12) | |
| 6375 7374 6f6d 2d76 616c 7565 | custom-value | |
| | -> custom-key: | |
| | custom-value | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT(header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":method", "GET"}, | |
| HpackHeaderEntry{":scheme", "https"}, | |
| HpackHeaderEntry{":path", "/index.html"}, | |
| HpackHeaderEntry{":authority", "www.example.com"}, | |
| HpackHeaderEntry{"custom-key", "custom-value"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 54) custom-key: custom-value | |
| // [ 2] (s = 53) cache-control: no-cache | |
| // [ 3] (s = 57) :authority: www.example.com | |
| // Table size: 164 | |
| ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"}, | |
| {"cache-control", "no-cache"}, | |
| {":authority", "www.example.com"}})); | |
| ASSERT_EQ(164u, current_header_table_size()); | |
| } | |
| // Test based on RFC 7541, section C.4 Request Examples with Huffman Coding. | |
| // This section shows the same examples as the previous section but uses | |
| // Huffman encoding for the literal values. | |
| // http://httpwg.org/specs/rfc7541.html#rfc.section.C.4 | |
| TEST_P(HpackDecoderTest, C4_RequestExamplesWithHuffmanEncoding) { | |
| // C.4.1 First Request | |
| std::string hpack_block = HpackExampleToStringOrDie(R"( | |
| 82 | == Indexed - Add == | |
| | idx = 2 | |
| | -> :method: GET | |
| 86 | == Indexed - Add == | |
| | idx = 6 | |
| | -> :scheme: http | |
| 84 | == Indexed - Add == | |
| | idx = 4 | |
| | -> :path: / | |
| 41 | == Literal indexed == | |
| | Indexed name (idx = 1) | |
| | :authority | |
| 8c | Literal value (len = 12) | |
| | Huffman encoded: | |
| f1e3 c2e5 f23a 6ba0 ab90 f4ff | .....:k..... | |
| | Decoded: | |
| | www.example.com | |
| | -> :authority: | |
| | www.example.com | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT(header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":method", "GET"}, | |
| HpackHeaderEntry{":scheme", "http"}, | |
| HpackHeaderEntry{":path", "/"}, | |
| HpackHeaderEntry{":authority", "www.example.com"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 57) :authority: www.example.com | |
| // Table size: 57 | |
| ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}})); | |
| ASSERT_EQ(57u, current_header_table_size()); | |
| // C.4.2 Second Request | |
| hpack_block = HpackExampleToStringOrDie(R"( | |
| 82 | == Indexed - Add == | |
| | idx = 2 | |
| | -> :method: GET | |
| 86 | == Indexed - Add == | |
| | idx = 6 | |
| | -> :scheme: http | |
| 84 | == Indexed - Add == | |
| | idx = 4 | |
| | -> :path: / | |
| be | == Indexed - Add == | |
| | idx = 62 | |
| | -> :authority: | |
| | www.example.com | |
| 58 | == Literal indexed == | |
| | Indexed name (idx = 24) | |
| | cache-control | |
| 86 | Literal value (len = 6) | |
| | Huffman encoded: | |
| a8eb 1064 9cbf | ...d.. | |
| | Decoded: | |
| | no-cache | |
| | -> cache-control: no-cache | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT(header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":method", "GET"}, | |
| HpackHeaderEntry{":scheme", "http"}, | |
| HpackHeaderEntry{":path", "/"}, | |
| HpackHeaderEntry{":authority", "www.example.com"}, | |
| HpackHeaderEntry{"cache-control", "no-cache"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 53) cache-control: no-cache | |
| // [ 2] (s = 57) :authority: www.example.com | |
| // Table size: 110 | |
| ASSERT_TRUE(VerifyDynamicTableContents( | |
| {{"cache-control", "no-cache"}, {":authority", "www.example.com"}})); | |
| ASSERT_EQ(110u, current_header_table_size()); | |
| // C.4.2 Third Request | |
| hpack_block = HpackExampleToStringOrDie(R"( | |
| 82 | == Indexed - Add == | |
| | idx = 2 | |
| | -> :method: GET | |
| 87 | == Indexed - Add == | |
| | idx = 7 | |
| | -> :scheme: https | |
| 85 | == Indexed - Add == | |
| | idx = 5 | |
| | -> :path: /index.html | |
| bf | == Indexed - Add == | |
| | idx = 63 | |
| | -> :authority: | |
| | www.example.com | |
| 40 | == Literal indexed == | |
| 88 | Literal name (len = 8) | |
| | Huffman encoded: | |
| 25a8 49e9 5ba9 7d7f | %.I.[.}. | |
| | Decoded: | |
| | custom-key | |
| 89 | Literal value (len = 9) | |
| | Huffman encoded: | |
| 25a8 49e9 5bb8 e8b4 bf | %.I.[.... | |
| | Decoded: | |
| | custom-value | |
| | -> custom-key: | |
| | custom-value | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT(header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":method", "GET"}, | |
| HpackHeaderEntry{":scheme", "https"}, | |
| HpackHeaderEntry{":path", "/index.html"}, | |
| HpackHeaderEntry{":authority", "www.example.com"}, | |
| HpackHeaderEntry{"custom-key", "custom-value"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 54) custom-key: custom-value | |
| // [ 2] (s = 53) cache-control: no-cache | |
| // [ 3] (s = 57) :authority: www.example.com | |
| // Table size: 164 | |
| ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"}, | |
| {"cache-control", "no-cache"}, | |
| {":authority", "www.example.com"}})); | |
| ASSERT_EQ(164u, current_header_table_size()); | |
| } | |
| // Test based on RFC 7541, section C.5: Response Examples without Huffman | |
| // Coding. This section shows several consecutive header lists, corresponding | |
| // to HTTP responses, on the same connection. The HTTP/2 setting parameter | |
| // SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing | |
| // some evictions to occur. | |
| // http://httpwg.org/specs/rfc7541.html#rfc.section.C.5 | |
| TEST_P(HpackDecoderTest, C5_ResponseExamples) { | |
| set_header_table_size_limit(256); | |
| // C.5.1 First Response | |
| // | |
| // Header list to encode: | |
| // | |
| // :status: 302 | |
| // cache-control: private | |
| // date: Mon, 21 Oct 2013 20:13:21 GMT | |
| // location: https://www.example.com | |
| std::string hpack_block = HpackExampleToStringOrDie(R"( | |
| 48 | == Literal indexed == | |
| | Indexed name (idx = 8) | |
| | :status | |
| 03 | Literal value (len = 3) | |
| 3330 32 | 302 | |
| | -> :status: 302 | |
| 58 | == Literal indexed == | |
| | Indexed name (idx = 24) | |
| | cache-control | |
| 07 | Literal value (len = 7) | |
| 7072 6976 6174 65 | private | |
| | -> cache-control: private | |
| 61 | == Literal indexed == | |
| | Indexed name (idx = 33) | |
| | date | |
| 1d | Literal value (len = 29) | |
| 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 | |
| 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT | |
| | -> date: Mon, 21 Oct 2013 | |
| | 20:13:21 GMT | |
| 6e | == Literal indexed == | |
| | Indexed name (idx = 46) | |
| | location | |
| 17 | Literal value (len = 23) | |
| 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam | |
| 706c 652e 636f 6d | ple.com | |
| | -> location: | |
| | https://www.example.com | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT(header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":status", "302"}, | |
| HpackHeaderEntry{"cache-control", "private"}, | |
| HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, | |
| HpackHeaderEntry{"location", "https://www.example.com"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 63) location: https://www.example.com | |
| // [ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT | |
| // [ 3] (s = 52) cache-control: private | |
| // [ 4] (s = 42) :status: 302 | |
| // Table size: 222 | |
| ASSERT_TRUE( | |
| VerifyDynamicTableContents({{"location", "https://www.example.com"}, | |
| {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, | |
| {"cache-control", "private"}, | |
| {":status", "302"}})); | |
| ASSERT_EQ(222u, current_header_table_size()); | |
| // C.5.2 Second Response | |
| // | |
| // The (":status", "302") header field is evicted from the dynamic table to | |
| // free space to allow adding the (":status", "307") header field. | |
| // | |
| // Header list to encode: | |
| // | |
| // :status: 307 | |
| // cache-control: private | |
| // date: Mon, 21 Oct 2013 20:13:21 GMT | |
| // location: https://www.example.com | |
| hpack_block = HpackExampleToStringOrDie(R"( | |
| 48 | == Literal indexed == | |
| | Indexed name (idx = 8) | |
| | :status | |
| 03 | Literal value (len = 3) | |
| 3330 37 | 307 | |
| | - evict: :status: 302 | |
| | -> :status: 307 | |
| c1 | == Indexed - Add == | |
| | idx = 65 | |
| | -> cache-control: private | |
| c0 | == Indexed - Add == | |
| | idx = 64 | |
| | -> date: Mon, 21 Oct 2013 | |
| | 20:13:21 GMT | |
| bf | == Indexed - Add == | |
| | idx = 63 | |
| | -> location: | |
| | https://www.example.com | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT(header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":status", "307"}, | |
| HpackHeaderEntry{"cache-control", "private"}, | |
| HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, | |
| HpackHeaderEntry{"location", "https://www.example.com"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 42) :status: 307 | |
| // [ 2] (s = 63) location: https://www.example.com | |
| // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT | |
| // [ 4] (s = 52) cache-control: private | |
| // Table size: 222 | |
| ASSERT_TRUE( | |
| VerifyDynamicTableContents({{":status", "307"}, | |
| {"location", "https://www.example.com"}, | |
| {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, | |
| {"cache-control", "private"}})); | |
| ASSERT_EQ(222u, current_header_table_size()); | |
| // C.5.3 Third Response | |
| // | |
| // Several header fields are evicted from the dynamic table during the | |
| // processing of this header list. | |
| // | |
| // Header list to encode: | |
| // | |
| // :status: 200 | |
| // cache-control: private | |
| // date: Mon, 21 Oct 2013 20:13:22 GMT | |
| // location: https://www.example.com | |
| // content-encoding: gzip | |
| // set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1 | |
| hpack_block = HpackExampleToStringOrDie(R"( | |
| 88 | == Indexed - Add == | |
| | idx = 8 | |
| | -> :status: 200 | |
| c1 | == Indexed - Add == | |
| | idx = 65 | |
| | -> cache-control: private | |
| 61 | == Literal indexed == | |
| | Indexed name (idx = 33) | |
| | date | |
| 1d | Literal value (len = 29) | |
| 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 | |
| 2032 303a 3133 3a32 3220 474d 54 | 20:13:22 GMT | |
| | - evict: cache-control: | |
| | private | |
| | -> date: Mon, 21 Oct 2013 | |
| | 20:13:22 GMT | |
| c0 | == Indexed - Add == | |
| | idx = 64 | |
| | -> location: | |
| | https://www.example.com | |
| 5a | == Literal indexed == | |
| | Indexed name (idx = 26) | |
| | content-encoding | |
| 04 | Literal value (len = 4) | |
| 677a 6970 | gzip | |
| | - evict: date: Mon, 21 Oct | |
| | 2013 20:13:21 GMT | |
| | -> content-encoding: gzip | |
| 77 | == Literal indexed == | |
| | Indexed name (idx = 55) | |
| | set-cookie | |
| 38 | Literal value (len = 56) | |
| 666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO | |
| 5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU; | |
| 206d 6178 2d61 6765 3d33 3630 303b 2076 | max-age=3600; v | |
| 6572 7369 6f6e 3d31 | ersion=1 | |
| | - evict: location: | |
| | https://www.example.com | |
| | - evict: :status: 307 | |
| | -> set-cookie: foo=ASDJKHQ | |
| | KBZXOQWEOPIUAXQWEOIU; ma | |
| | x-age=3600; version=1 | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT( | |
| header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":status", "200"}, | |
| HpackHeaderEntry{"cache-control", "private"}, | |
| HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:22 GMT"}, | |
| HpackHeaderEntry{"location", "https://www.example.com"}, | |
| HpackHeaderEntry{"content-encoding", "gzip"}, | |
| HpackHeaderEntry{ | |
| "set-cookie", | |
| "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; | |
| // max-age=3600; version=1 | |
| // [ 2] (s = 52) content-encoding: gzip | |
| // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT | |
| // Table size: 215 | |
| ASSERT_TRUE(VerifyDynamicTableContents( | |
| {{"set-cookie", | |
| "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, | |
| {"content-encoding", "gzip"}, | |
| {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}})); | |
| ASSERT_EQ(215u, current_header_table_size()); | |
| } | |
| // Test based on RFC 7541, section C.6: Response Examples with Huffman Coding. | |
| // This section shows the same examples as the previous section but uses Huffman | |
| // encoding for the literal values. The HTTP/2 setting parameter | |
| // SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing some | |
| // evictions to occur. The eviction mechanism uses the length of the decoded | |
| // literal values, so the same evictions occur as in the previous section. | |
| // http://httpwg.org/specs/rfc7541.html#rfc.section.C.6 | |
| TEST_P(HpackDecoderTest, C6_ResponseExamplesWithHuffmanEncoding) { | |
| set_header_table_size_limit(256); | |
| // C.5.1 First Response | |
| // | |
| // Header list to encode: | |
| // | |
| // :status: 302 | |
| // cache-control: private | |
| // date: Mon, 21 Oct 2013 20:13:21 GMT | |
| // location: https://www.example.com | |
| std::string hpack_block = HpackExampleToStringOrDie(R"( | |
| 48 | == Literal indexed == | |
| | Indexed name (idx = 8) | |
| | :status | |
| 03 | Literal value (len = 3) | |
| 3330 32 | 302 | |
| | -> :status: 302 | |
| 58 | == Literal indexed == | |
| | Indexed name (idx = 24) | |
| | cache-control | |
| 07 | Literal value (len = 7) | |
| 7072 6976 6174 65 | private | |
| | -> cache-control: private | |
| 61 | == Literal indexed == | |
| | Indexed name (idx = 33) | |
| | date | |
| 1d | Literal value (len = 29) | |
| 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 | |
| 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT | |
| | -> date: Mon, 21 Oct 2013 | |
| | 20:13:21 GMT | |
| 6e | == Literal indexed == | |
| | Indexed name (idx = 46) | |
| | location | |
| 17 | Literal value (len = 23) | |
| 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam | |
| 706c 652e 636f 6d | ple.com | |
| | -> location: | |
| | https://www.example.com | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT(header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":status", "302"}, | |
| HpackHeaderEntry{"cache-control", "private"}, | |
| HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, | |
| HpackHeaderEntry{"location", "https://www.example.com"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 63) location: https://www.example.com | |
| // [ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT | |
| // [ 3] (s = 52) cache-control: private | |
| // [ 4] (s = 42) :status: 302 | |
| // Table size: 222 | |
| ASSERT_TRUE( | |
| VerifyDynamicTableContents({{"location", "https://www.example.com"}, | |
| {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, | |
| {"cache-control", "private"}, | |
| {":status", "302"}})); | |
| ASSERT_EQ(222u, current_header_table_size()); | |
| // C.5.2 Second Response | |
| // | |
| // The (":status", "302") header field is evicted from the dynamic table to | |
| // free space to allow adding the (":status", "307") header field. | |
| // | |
| // Header list to encode: | |
| // | |
| // :status: 307 | |
| // cache-control: private | |
| // date: Mon, 21 Oct 2013 20:13:21 GMT | |
| // location: https://www.example.com | |
| hpack_block = HpackExampleToStringOrDie(R"( | |
| 48 | == Literal indexed == | |
| | Indexed name (idx = 8) | |
| | :status | |
| 03 | Literal value (len = 3) | |
| 3330 37 | 307 | |
| | - evict: :status: 302 | |
| | -> :status: 307 | |
| c1 | == Indexed - Add == | |
| | idx = 65 | |
| | -> cache-control: private | |
| c0 | == Indexed - Add == | |
| | idx = 64 | |
| | -> date: Mon, 21 Oct 2013 | |
| | 20:13:21 GMT | |
| bf | == Indexed - Add == | |
| | idx = 63 | |
| | -> location: | |
| | https://www.example.com | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT(header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":status", "307"}, | |
| HpackHeaderEntry{"cache-control", "private"}, | |
| HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, | |
| HpackHeaderEntry{"location", "https://www.example.com"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 42) :status: 307 | |
| // [ 2] (s = 63) location: https://www.example.com | |
| // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT | |
| // [ 4] (s = 52) cache-control: private | |
| // Table size: 222 | |
| ASSERT_TRUE( | |
| VerifyDynamicTableContents({{":status", "307"}, | |
| {"location", "https://www.example.com"}, | |
| {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, | |
| {"cache-control", "private"}})); | |
| ASSERT_EQ(222u, current_header_table_size()); | |
| // C.5.3 Third Response | |
| // | |
| // Several header fields are evicted from the dynamic table during the | |
| // processing of this header list. | |
| // | |
| // Header list to encode: | |
| // | |
| // :status: 200 | |
| // cache-control: private | |
| // date: Mon, 21 Oct 2013 20:13:22 GMT | |
| // location: https://www.example.com | |
| // content-encoding: gzip | |
| // set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1 | |
| hpack_block = HpackExampleToStringOrDie(R"( | |
| 88 | == Indexed - Add == | |
| | idx = 8 | |
| | -> :status: 200 | |
| c1 | == Indexed - Add == | |
| | idx = 65 | |
| | -> cache-control: private | |
| 61 | == Literal indexed == | |
| | Indexed name (idx = 33) | |
| | date | |
| 1d | Literal value (len = 29) | |
| 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 | |
| 2032 303a 3133 3a32 3220 474d 54 | 20:13:22 GMT | |
| | - evict: cache-control: | |
| | private | |
| | -> date: Mon, 21 Oct 2013 | |
| | 20:13:22 GMT | |
| c0 | == Indexed - Add == | |
| | idx = 64 | |
| | -> location: | |
| | https://www.example.com | |
| 5a | == Literal indexed == | |
| | Indexed name (idx = 26) | |
| | content-encoding | |
| 04 | Literal value (len = 4) | |
| 677a 6970 | gzip | |
| | - evict: date: Mon, 21 Oct | |
| | 2013 20:13:21 GMT | |
| | -> content-encoding: gzip | |
| 77 | == Literal indexed == | |
| | Indexed name (idx = 55) | |
| | set-cookie | |
| 38 | Literal value (len = 56) | |
| 666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO | |
| 5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU; | |
| 206d 6178 2d61 6765 3d33 3630 303b 2076 | max-age=3600; v | |
| 6572 7369 6f6e 3d31 | ersion=1 | |
| | - evict: location: | |
| | https://www.example.com | |
| | - evict: :status: 307 | |
| | -> set-cookie: foo=ASDJKHQ | |
| | KBZXOQWEOPIUAXQWEOIU; ma | |
| | x-age=3600; version=1 | |
| )"); | |
| EXPECT_TRUE(DecodeBlock(hpack_block)); | |
| ASSERT_THAT( | |
| header_entries_, | |
| ElementsAreArray({ | |
| HpackHeaderEntry{":status", "200"}, | |
| HpackHeaderEntry{"cache-control", "private"}, | |
| HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:22 GMT"}, | |
| HpackHeaderEntry{"location", "https://www.example.com"}, | |
| HpackHeaderEntry{"content-encoding", "gzip"}, | |
| HpackHeaderEntry{ | |
| "set-cookie", | |
| "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, | |
| })); | |
| // Dynamic Table (after decoding): | |
| // | |
| // [ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; | |
| // max-age=3600; version=1 | |
| // [ 2] (s = 52) content-encoding: gzip | |
| // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT | |
| // Table size: 215 | |
| ASSERT_TRUE(VerifyDynamicTableContents( | |
| {{"set-cookie", | |
| "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, | |
| {"content-encoding", "gzip"}, | |
| {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}})); | |
| ASSERT_EQ(215u, current_header_table_size()); | |
| } | |
| // Confirm that the table size can be changed, but at most twice. | |
| TEST_P(HpackDecoderTest, ProcessesOptionalTableSizeUpdates) { | |
| EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), | |
| header_table_size_limit()); | |
| // One update allowed. | |
| { | |
| HpackBlockBuilder hbb; | |
| hbb.AppendDynamicTableSizeUpdate(3000); | |
| EXPECT_TRUE(DecodeBlock(hbb.buffer())); | |
| EXPECT_EQ(3000u, header_table_size_limit()); | |
| EXPECT_EQ(0u, current_header_table_size()); | |
| EXPECT_TRUE(header_entries_.empty()); | |
| } | |
| // Two updates allowed. | |
| { | |
| HpackBlockBuilder hbb; | |
| hbb.AppendDynamicTableSizeUpdate(2000); | |
| hbb.AppendDynamicTableSizeUpdate(2500); | |
| EXPECT_TRUE(DecodeBlock(hbb.buffer())); | |
| EXPECT_EQ(2500u, header_table_size_limit()); | |
| EXPECT_EQ(0u, current_header_table_size()); | |
| EXPECT_TRUE(header_entries_.empty()); | |
| } | |
| // A third update in the same HPACK block is rejected, so the final | |
| // size is 1000, not 500. | |
| { | |
| HpackBlockBuilder hbb; | |
| hbb.AppendDynamicTableSizeUpdate(1500); | |
| hbb.AppendDynamicTableSizeUpdate(1000); | |
| hbb.AppendDynamicTableSizeUpdate(500); | |
| EXPECT_FALSE(DecodeBlock(hbb.buffer())); | |
| EXPECT_EQ(HpackDecodingError::kDynamicTableSizeUpdateNotAllowed, | |
| decoder_.error()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| EXPECT_THAT(error_messages_[0], | |
| Eq("Dynamic table size update not allowed")); | |
| EXPECT_EQ(1000u, header_table_size_limit()); | |
| EXPECT_EQ(0u, current_header_table_size()); | |
| EXPECT_TRUE(header_entries_.empty()); | |
| } | |
| // An error has been detected, so calls to HpackDecoder::DecodeFragment | |
| // should return immediately. | |
| DecodeBuffer db("\x80"); | |
| EXPECT_FALSE(decoder_.DecodeFragment(&db)); | |
| EXPECT_EQ(0u, db.Offset()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| } | |
| // Confirm that the table size can be changed when required, but at most twice. | |
| TEST_P(HpackDecoderTest, ProcessesRequiredTableSizeUpdate) { | |
| EXPECT_EQ(4096u, decoder_.GetCurrentHeaderTableSizeSetting()); | |
| // One update required, two allowed, one provided, followed by a header. | |
| decoder_.ApplyHeaderTableSizeSetting(1024); | |
| decoder_.ApplyHeaderTableSizeSetting(2048); | |
| EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), | |
| header_table_size_limit()); | |
| EXPECT_EQ(2048u, decoder_.GetCurrentHeaderTableSizeSetting()); | |
| { | |
| HpackBlockBuilder hbb; | |
| hbb.AppendDynamicTableSizeUpdate(1024); | |
| hbb.AppendIndexedHeader(4); // :path: / | |
| EXPECT_TRUE(DecodeBlock(hbb.buffer())); | |
| EXPECT_THAT(header_entries_, | |
| ElementsAreArray({HpackHeaderEntry{":path", "/"}})); | |
| EXPECT_EQ(1024u, header_table_size_limit()); | |
| EXPECT_EQ(0u, current_header_table_size()); | |
| } | |
| // One update required, two allowed, two provided, followed by a header. | |
| decoder_.ApplyHeaderTableSizeSetting(1000); | |
| decoder_.ApplyHeaderTableSizeSetting(1500); | |
| EXPECT_EQ(1500u, decoder_.GetCurrentHeaderTableSizeSetting()); | |
| { | |
| HpackBlockBuilder hbb; | |
| hbb.AppendDynamicTableSizeUpdate(500); | |
| hbb.AppendDynamicTableSizeUpdate(1250); | |
| hbb.AppendIndexedHeader(5); // :path: /index.html | |
| EXPECT_TRUE(DecodeBlock(hbb.buffer())); | |
| EXPECT_THAT(header_entries_, | |
| ElementsAreArray({HpackHeaderEntry{":path", "/index.html"}})); | |
| EXPECT_EQ(1250u, header_table_size_limit()); | |
| EXPECT_EQ(0u, current_header_table_size()); | |
| } | |
| // One update required, two allowed, three provided, followed by a header. | |
| // The third update is rejected, so the final size is 1000, not 500. | |
| decoder_.ApplyHeaderTableSizeSetting(500); | |
| decoder_.ApplyHeaderTableSizeSetting(1000); | |
| EXPECT_EQ(1000u, decoder_.GetCurrentHeaderTableSizeSetting()); | |
| { | |
| HpackBlockBuilder hbb; | |
| hbb.AppendDynamicTableSizeUpdate(200); | |
| hbb.AppendDynamicTableSizeUpdate(700); | |
| hbb.AppendDynamicTableSizeUpdate(900); | |
| hbb.AppendIndexedHeader(5); // Not decoded. | |
| EXPECT_FALSE(DecodeBlock(hbb.buffer())); | |
| EXPECT_FALSE(saw_end_); | |
| EXPECT_EQ(HpackDecodingError::kDynamicTableSizeUpdateNotAllowed, | |
| decoder_.error()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| EXPECT_THAT(error_messages_[0], | |
| Eq("Dynamic table size update not allowed")); | |
| EXPECT_EQ(700u, header_table_size_limit()); | |
| EXPECT_EQ(0u, current_header_table_size()); | |
| EXPECT_TRUE(header_entries_.empty()); | |
| } | |
| EXPECT_EQ(1000u, decoder_.GetCurrentHeaderTableSizeSetting()); | |
| // Now that an error has been detected, StartDecodingBlock should return | |
| // false. | |
| EXPECT_FALSE(decoder_.StartDecodingBlock()); | |
| } | |
| // Confirm that required size updates are validated. | |
| TEST_P(HpackDecoderTest, InvalidRequiredSizeUpdate) { | |
| // Require a size update, but provide one that isn't small enough (must be | |
| // zero or one, in this case). | |
| decoder_.ApplyHeaderTableSizeSetting(1); | |
| decoder_.ApplyHeaderTableSizeSetting(1024); | |
| HpackBlockBuilder hbb; | |
| hbb.AppendDynamicTableSizeUpdate(2); | |
| EXPECT_TRUE(decoder_.StartDecodingBlock()); | |
| DecodeBuffer db(hbb.buffer()); | |
| EXPECT_FALSE(decoder_.DecodeFragment(&db)); | |
| EXPECT_FALSE(saw_end_); | |
| EXPECT_EQ( | |
| HpackDecodingError::kInitialDynamicTableSizeUpdateIsAboveLowWaterMark, | |
| decoder_.error()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| EXPECT_THAT(error_messages_[0], | |
| Eq("Initial dynamic table size update is above low water mark")); | |
| EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), | |
| header_table_size_limit()); | |
| } | |
| // Confirm that required size updates are indeed required before the end. | |
| TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeEnd) { | |
| decoder_.ApplyHeaderTableSizeSetting(1024); | |
| EXPECT_FALSE(DecodeBlock("")); | |
| EXPECT_EQ(HpackDecodingError::kMissingDynamicTableSizeUpdate, | |
| decoder_.error()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| EXPECT_THAT(error_messages_[0], Eq("Missing dynamic table size update")); | |
| EXPECT_FALSE(saw_end_); | |
| } | |
| // Confirm that required size updates are indeed required before an | |
| // indexed header. | |
| TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeader) { | |
| decoder_.ApplyHeaderTableSizeSetting(1024); | |
| HpackBlockBuilder hbb; | |
| hbb.AppendIndexedHeader(1); | |
| EXPECT_FALSE(DecodeBlock(hbb.buffer())); | |
| EXPECT_EQ(HpackDecodingError::kMissingDynamicTableSizeUpdate, | |
| decoder_.error()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| EXPECT_THAT(error_messages_[0], Eq("Missing dynamic table size update")); | |
| EXPECT_FALSE(saw_end_); | |
| EXPECT_TRUE(header_entries_.empty()); | |
| } | |
| // Confirm that required size updates are indeed required before an indexed | |
| // header name. | |
| // TODO(jamessynge): Move some of these to hpack_decoder_state_test.cc. | |
| TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeaderName) { | |
| decoder_.ApplyHeaderTableSizeSetting(1024); | |
| HpackBlockBuilder hbb; | |
| hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 2, | |
| false, "PUT"); | |
| EXPECT_FALSE(DecodeBlock(hbb.buffer())); | |
| EXPECT_EQ(HpackDecodingError::kMissingDynamicTableSizeUpdate, | |
| decoder_.error()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| EXPECT_THAT(error_messages_[0], Eq("Missing dynamic table size update")); | |
| EXPECT_FALSE(saw_end_); | |
| EXPECT_TRUE(header_entries_.empty()); | |
| } | |
| // Confirm that required size updates are indeed required before a literal | |
| // header name. | |
| TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeLiteralName) { | |
| decoder_.ApplyHeaderTableSizeSetting(1024); | |
| HpackBlockBuilder hbb; | |
| hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, | |
| false, "name", false, "some data."); | |
| EXPECT_FALSE(DecodeBlock(hbb.buffer())); | |
| EXPECT_EQ(HpackDecodingError::kMissingDynamicTableSizeUpdate, | |
| decoder_.error()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| EXPECT_THAT(error_messages_[0], Eq("Missing dynamic table size update")); | |
| EXPECT_FALSE(saw_end_); | |
| EXPECT_TRUE(header_entries_.empty()); | |
| } | |
| // Confirm that an excessively long varint is detected, in this case an | |
| // index of 127, but with lots of additional high-order 0 bits provided, | |
| // too many to be allowed. | |
| TEST_P(HpackDecoderTest, InvalidIndexedHeaderVarint) { | |
| EXPECT_TRUE(decoder_.StartDecodingBlock()); | |
| DecodeBuffer db("\xff\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x00"); | |
| EXPECT_FALSE(decoder_.DecodeFragment(&db)); | |
| EXPECT_TRUE(decoder_.DetectError()); | |
| EXPECT_FALSE(saw_end_); | |
| EXPECT_EQ(HpackDecodingError::kIndexVarintError, decoder_.error()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| EXPECT_THAT(error_messages_[0], | |
| Eq("Index varint beyond implementation limit")); | |
| EXPECT_TRUE(header_entries_.empty()); | |
| // Now that an error has been detected, EndDecodingBlock should not succeed. | |
| EXPECT_FALSE(decoder_.EndDecodingBlock()); | |
| } | |
| // Confirm that an invalid index into the tables is detected, in this case an | |
| // index of 0. | |
| TEST_P(HpackDecoderTest, InvalidIndex) { | |
| EXPECT_TRUE(decoder_.StartDecodingBlock()); | |
| DecodeBuffer db("\x80"); | |
| EXPECT_FALSE(decoder_.DecodeFragment(&db)); | |
| EXPECT_TRUE(decoder_.DetectError()); | |
| EXPECT_FALSE(saw_end_); | |
| EXPECT_EQ(HpackDecodingError::kInvalidIndex, decoder_.error()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| EXPECT_THAT(error_messages_[0], | |
| Eq("Invalid index in indexed header field representation")); | |
| EXPECT_TRUE(header_entries_.empty()); | |
| // Now that an error has been detected, EndDecodingBlock should not succeed. | |
| EXPECT_FALSE(decoder_.EndDecodingBlock()); | |
| } | |
| // Confirm that EndDecodingBlock detects a truncated HPACK block. | |
| TEST_P(HpackDecoderTest, TruncatedBlock) { | |
| HpackBlockBuilder hbb; | |
| hbb.AppendDynamicTableSizeUpdate(3000); | |
| EXPECT_EQ(3u, hbb.size()); | |
| hbb.AppendDynamicTableSizeUpdate(4000); | |
| EXPECT_EQ(6u, hbb.size()); | |
| // Decodes this block if the whole thing is provided. | |
| EXPECT_TRUE(DecodeBlock(hbb.buffer())); | |
| EXPECT_EQ(4000u, header_table_size_limit()); | |
| // Multiple times even. | |
| EXPECT_TRUE(DecodeBlock(hbb.buffer())); | |
| EXPECT_EQ(4000u, header_table_size_limit()); | |
| // But not if the block is truncated. | |
| EXPECT_FALSE(DecodeBlock(hbb.buffer().substr(0, hbb.size() - 1))); | |
| EXPECT_FALSE(saw_end_); | |
| EXPECT_EQ(HpackDecodingError::kTruncatedBlock, decoder_.error()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| EXPECT_THAT(error_messages_[0], | |
| Eq("Block ends in the middle of an instruction")); | |
| // The first update was decoded. | |
| EXPECT_EQ(3000u, header_table_size_limit()); | |
| EXPECT_EQ(0u, current_header_table_size()); | |
| EXPECT_TRUE(header_entries_.empty()); | |
| } | |
| // Confirm that an oversized string is detected, ending decoding. | |
| TEST_P(HpackDecoderTest, OversizeStringDetected) { | |
| HpackBlockBuilder hbb; | |
| hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, | |
| false, "name", false, "some data."); | |
| hbb.AppendLiteralNameAndValue(HpackEntryType::kUnindexedLiteralHeader, false, | |
| "name2", false, "longer data"); | |
| // Normally able to decode this block. | |
| EXPECT_TRUE(DecodeBlock(hbb.buffer())); | |
| EXPECT_THAT(header_entries_, | |
| ElementsAreArray({HpackHeaderEntry{"name", "some data."}, | |
| HpackHeaderEntry{"name2", "longer data"}})); | |
| // But not if the maximum size of strings is less than the longest string. | |
| decoder_.set_max_string_size_bytes(10); | |
| EXPECT_FALSE(DecodeBlock(hbb.buffer())); | |
| EXPECT_THAT(header_entries_, | |
| ElementsAreArray({HpackHeaderEntry{"name", "some data."}})); | |
| EXPECT_FALSE(saw_end_); | |
| EXPECT_EQ(HpackDecodingError::kValueTooLong, decoder_.error()); | |
| EXPECT_EQ(1u, error_messages_.size()); | |
| EXPECT_THAT(error_messages_[0], Eq("Value length exceeds buffer limit")); | |
| } | |
| } // namespace | |
| } // namespace test | |
| } // namespace http2 |