|  | // 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h" | 
|  |  | 
|  | // Tests of HpackDecoder. | 
|  |  | 
|  | #include <string> | 
|  | #include <tuple> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" | 
|  | #include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h" | 
|  | #include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h" | 
|  | #include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" | 
|  | #include "net/third_party/quiche/src/http2/hpack/hpack_string.h" | 
|  | #include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" | 
|  | #include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" | 
|  | #include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h" | 
|  | #include "net/third_party/quiche/src/http2/http2_constants.h" | 
|  | #include "net/third_party/quiche/src/http2/platform/api/http2_logging.h" | 
|  | #include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" | 
|  | #include "net/third_party/quiche/src/http2/test_tools/http2_random.h" | 
|  | #include "net/third_party/quiche/src/http2/tools/random_util.h" | 
|  | #include "net/third_party/quiche/src/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_METHOD0(OnHeaderListStart, void()); | 
|  | MOCK_METHOD2(OnHeader, | 
|  | void(const HpackString& name, const HpackString& value)); | 
|  | MOCK_METHOD0(OnHeaderListEnd, void()); | 
|  | 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 HpackString& name, const HpackString& value) override { | 
|  | ASSERT_TRUE(saw_start_); | 
|  | ASSERT_FALSE(saw_end_); | 
|  | header_entries_.emplace_back(name.ToString(), value.ToString()); | 
|  | } | 
|  |  | 
|  | // 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.ToStringPiece(), name); | 
|  | VERIFY_EQ(entry->value.ToStringPiece(), 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) { | 
|  | // 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()); | 
|  | { | 
|  | 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); | 
|  | { | 
|  | 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); | 
|  | { | 
|  | 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()); | 
|  | } | 
|  | // 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 |