| // Copyright (c) 2018 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/quic/core/qpack/qpack_decoder.h" |
| |
| #include <algorithm> |
| |
| #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_test.h" |
| #include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_decoder_test_utils.h" |
| #include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_test_utils.h" |
| #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" |
| #include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h" |
| #include "net/third_party/quiche/src/spdy/core/spdy_header_block.h" |
| |
| using ::testing::_; |
| using ::testing::Eq; |
| using ::testing::Invoke; |
| using ::testing::Mock; |
| using ::testing::Sequence; |
| using ::testing::StrictMock; |
| using ::testing::Values; |
| |
| namespace quic { |
| namespace test { |
| namespace { |
| |
| // Header Acknowledgement decoder stream instruction with stream_id = 1. |
| const char* const kHeaderAcknowledgement = "\x81"; |
| |
| const uint64_t kMaximumDynamicTableCapacity = 1024; |
| const uint64_t kMaximumBlockedStreams = 1; |
| |
| class QpackDecoderTest : public QuicTestWithParam<FragmentMode> { |
| protected: |
| QpackDecoderTest() |
| : qpack_decoder_(kMaximumDynamicTableCapacity, |
| kMaximumBlockedStreams, |
| &encoder_stream_error_delegate_), |
| fragment_mode_(GetParam()) { |
| qpack_decoder_.set_qpack_stream_sender_delegate( |
| &decoder_stream_sender_delegate_); |
| } |
| |
| ~QpackDecoderTest() override = default; |
| |
| void SetUp() override { |
| // Destroy QpackProgressiveDecoder on error to test that it does not crash. |
| // See https://crbug.com/1025209. |
| ON_CALL(handler_, OnDecodingErrorDetected(_)) |
| .WillByDefault( |
| Invoke([this](quiche::QuicheStringPiece /* error_message */) { |
| progressive_decoder_.reset(); |
| })); |
| } |
| |
| void DecodeEncoderStreamData(quiche::QuicheStringPiece data) { |
| qpack_decoder_.encoder_stream_receiver()->Decode(data); |
| } |
| |
| std::unique_ptr<QpackProgressiveDecoder> CreateProgressiveDecoder( |
| QuicStreamId stream_id) { |
| return qpack_decoder_.CreateProgressiveDecoder(stream_id, &handler_); |
| } |
| |
| // Set up |progressive_decoder_|. |
| void StartDecoding() { |
| progressive_decoder_ = CreateProgressiveDecoder(/* stream_id = */ 1); |
| } |
| |
| // Pass header block data to QpackProgressiveDecoder::Decode() |
| // in fragments dictated by |fragment_mode_|. |
| void DecodeData(quiche::QuicheStringPiece data) { |
| auto fragment_size_generator = |
| FragmentModeToFragmentSizeGenerator(fragment_mode_); |
| while (progressive_decoder_ && !data.empty()) { |
| size_t fragment_size = std::min(fragment_size_generator(), data.size()); |
| progressive_decoder_->Decode(data.substr(0, fragment_size)); |
| data = data.substr(fragment_size); |
| } |
| } |
| |
| // Signal end of header block to QpackProgressiveDecoder. |
| void EndDecoding() { |
| if (progressive_decoder_) { |
| progressive_decoder_->EndHeaderBlock(); |
| } |
| // If no error was detected, |*progressive_decoder_| is kept alive so that |
| // it can handle callbacks later in case of blocked decoding. |
| } |
| |
| // Decode an entire header block. |
| void DecodeHeaderBlock(quiche::QuicheStringPiece data) { |
| StartDecoding(); |
| DecodeData(data); |
| EndDecoding(); |
| } |
| |
| StrictMock<MockEncoderStreamErrorDelegate> encoder_stream_error_delegate_; |
| StrictMock<MockQpackStreamSenderDelegate> decoder_stream_sender_delegate_; |
| StrictMock<MockHeadersHandler> handler_; |
| |
| private: |
| QpackDecoder qpack_decoder_; |
| const FragmentMode fragment_mode_; |
| std::unique_ptr<QpackProgressiveDecoder> progressive_decoder_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| QpackDecoderTest, |
| Values(FragmentMode::kSingleChunk, |
| FragmentMode::kOctetByOctet)); |
| |
| TEST_P(QpackDecoderTest, NoPrefix) { |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("Incomplete header data prefix."))); |
| |
| // Header Data Prefix is at least two bytes long. |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("00")); |
| } |
| |
| // Regression test for https://1025209: QpackProgressiveDecoder must not crash |
| // in Decode() if it is destroyed by handler_.OnDecodingErrorDetected(). |
| TEST_P(QpackDecoderTest, InvalidPrefix) { |
| StartDecoding(); |
| |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("Encoded integer too large."))); |
| |
| // Encoded Required Insert Count in Header Data Prefix is too large. |
| DecodeData( |
| quiche::QuicheTextUtils::HexDecode("ffffffffffffffffffffffffffff")); |
| } |
| |
| TEST_P(QpackDecoderTest, EmptyHeaderBlock) { |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("0000")); |
| } |
| |
| TEST_P(QpackDecoderTest, LiteralEntryEmptyName) { |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(""), Eq("foo"))); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("00002003666f6f")); |
| } |
| |
| TEST_P(QpackDecoderTest, LiteralEntryEmptyValue) { |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq(""))); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("000023666f6f00")); |
| } |
| |
| TEST_P(QpackDecoderTest, LiteralEntryEmptyNameAndValue) { |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(""), Eq(""))); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("00002000")); |
| } |
| |
| TEST_P(QpackDecoderTest, SimpleLiteralEntry) { |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("000023666f6f03626172")); |
| } |
| |
| TEST_P(QpackDecoderTest, MultipleLiteralEntries) { |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); |
| std::string str(127, 'a'); |
| EXPECT_CALL(handler_, |
| OnHeaderDecoded(Eq("foobaar"), quiche::QuicheStringPiece(str))); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0000" // prefix |
| "23666f6f03626172" // foo: bar |
| "2700666f6f62616172" // 7 octet long header name, the smallest number |
| // that does not fit on a 3-bit prefix. |
| "7f0061616161616161" // 127 octet long header value, the smallest number |
| "616161616161616161" // that does not fit on a 7-bit prefix. |
| "6161616161616161616161616161616161616161616161616161616161616161616161" |
| "6161616161616161616161616161616161616161616161616161616161616161616161" |
| "6161616161616161616161616161616161616161616161616161616161616161616161" |
| "616161616161")); |
| } |
| |
| // Name Length value is too large for varint decoder to decode. |
| TEST_P(QpackDecoderTest, NameLenTooLargeForVarintDecoder) { |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("Encoded integer too large."))); |
| |
| DecodeHeaderBlock( |
| quiche::QuicheTextUtils::HexDecode("000027ffffffffffffffffffff")); |
| } |
| |
| // Name Length value can be decoded by varint decoder but exceeds 1 MB limit. |
| TEST_P(QpackDecoderTest, NameLenExceedsLimit) { |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("String literal too long."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("000027ffff7f")); |
| } |
| |
| // Value Length value is too large for varint decoder to decode. |
| TEST_P(QpackDecoderTest, ValueLenTooLargeForVarintDecoder) { |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("Encoded integer too large."))); |
| |
| DecodeHeaderBlock( |
| quiche::QuicheTextUtils::HexDecode("000023666f6f7fffffffffffffffffffff")); |
| } |
| |
| // Value Length value can be decoded by varint decoder but exceeds 1 MB limit. |
| TEST_P(QpackDecoderTest, ValueLenExceedsLimit) { |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("String literal too long."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("000023666f6f7fffff7f")); |
| } |
| |
| TEST_P(QpackDecoderTest, IncompleteHeaderBlock) { |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("Incomplete header block."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("00002366")); |
| } |
| |
| TEST_P(QpackDecoderTest, HuffmanSimple) { |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("custom-key"), Eq("custom-value"))); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "00002f0125a849e95ba97d7f8925a849e95bb8e8b4bf")); |
| } |
| |
| TEST_P(QpackDecoderTest, AlternatingHuffmanNonHuffman) { |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("custom-key"), Eq("custom-value"))) |
| .Times(4); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0000" // Prefix. |
| "2f0125a849e95ba97d7f" // Huffman-encoded name. |
| "8925a849e95bb8e8b4bf" // Huffman-encoded value. |
| "2703637573746f6d2d6b6579" // Non-Huffman encoded name. |
| "0c637573746f6d2d76616c7565" // Non-Huffman encoded value. |
| "2f0125a849e95ba97d7f" // Huffman-encoded name. |
| "0c637573746f6d2d76616c7565" // Non-Huffman encoded value. |
| "2703637573746f6d2d6b6579" // Non-Huffman encoded name. |
| "8925a849e95bb8e8b4bf")); // Huffman-encoded value. |
| } |
| |
| TEST_P(QpackDecoderTest, HuffmanNameDoesNotHaveEOSPrefix) { |
| EXPECT_CALL(handler_, OnDecodingErrorDetected(quiche::QuicheStringPiece( |
| "Error in Huffman-encoded string."))); |
| |
| // 'y' ends in 0b0 on the most significant bit of the last byte. |
| // The remaining 7 bits must be a prefix of EOS, which is all 1s. |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "00002f0125a849e95ba97d7e8925a849e95bb8e8b4bf")); |
| } |
| |
| TEST_P(QpackDecoderTest, HuffmanValueDoesNotHaveEOSPrefix) { |
| EXPECT_CALL(handler_, OnDecodingErrorDetected(quiche::QuicheStringPiece( |
| "Error in Huffman-encoded string."))); |
| |
| // 'e' ends in 0b101, taking up the 3 most significant bits of the last byte. |
| // The remaining 5 bits must be a prefix of EOS, which is all 1s. |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "00002f0125a849e95ba97d7f8925a849e95bb8e8b4be")); |
| } |
| |
| TEST_P(QpackDecoderTest, HuffmanNameEOSPrefixTooLong) { |
| EXPECT_CALL(handler_, OnDecodingErrorDetected(quiche::QuicheStringPiece( |
| "Error in Huffman-encoded string."))); |
| |
| // The trailing EOS prefix must be at most 7 bits long. Appending one octet |
| // with value 0xff is invalid, even though 0b111111111111111 (15 bits) is a |
| // prefix of EOS. |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "00002f0225a849e95ba97d7fff8925a849e95bb8e8b4bf")); |
| } |
| |
| TEST_P(QpackDecoderTest, HuffmanValueEOSPrefixTooLong) { |
| EXPECT_CALL(handler_, OnDecodingErrorDetected(quiche::QuicheStringPiece( |
| "Error in Huffman-encoded string."))); |
| |
| // The trailing EOS prefix must be at most 7 bits long. Appending one octet |
| // with value 0xff is invalid, even though 0b1111111111111 (13 bits) is a |
| // prefix of EOS. |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "00002f0125a849e95ba97d7f8a25a849e95bb8e8b4bfff")); |
| } |
| |
| TEST_P(QpackDecoderTest, StaticTable) { |
| // A header name that has multiple entries with different values. |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("GET"))); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("POST"))); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("TRACE"))); |
| |
| // A header name that has a single entry with non-empty value. |
| EXPECT_CALL(handler_, |
| OnHeaderDecoded(Eq("accept-encoding"), Eq("gzip, deflate, br"))); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("accept-encoding"), Eq("compress"))); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("accept-encoding"), Eq(""))); |
| |
| // A header name that has a single entry with empty value. |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("location"), Eq(""))); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("location"), Eq("foo"))); |
| |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0000d1dfccd45f108621e9aec2a11f5c8294e75f000554524143455f1000")); |
| } |
| |
| TEST_P(QpackDecoderTest, TooHighStaticTableIndex) { |
| // This is the last entry in the static table with index 98. |
| EXPECT_CALL(handler_, |
| OnHeaderDecoded(Eq("x-frame-options"), Eq("sameorigin"))); |
| |
| // Addressing entry 99 should trigger an error. |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("Static table entry not found."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("0000ff23ff24")); |
| } |
| |
| TEST_P(QpackDecoderTest, DynamicTable) { |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode( |
| "3fe107" // Set dynamic table capacity to 1024. |
| "6294e703626172" // Add literal entry with name "foo" and value "bar". |
| "80035a5a5a" // Add entry with name of dynamic table entry index 0 |
| // (relative index) and value "ZZZ". |
| "cf8294e7" // Add entry with name of static table entry index 15 |
| // and value "foo". |
| "01")); // Duplicate entry with relative index 1. |
| |
| // Now there are four entries in the dynamic table. |
| // Entry 0: "foo", "bar" |
| // Entry 1: "foo", "ZZZ" |
| // Entry 2: ":method", "foo" |
| // Entry 3: "foo", "ZZZ" |
| |
| // Use a Sequence to test that mock methods are called in order. |
| Sequence s; |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))).InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("foo"))) |
| .InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("ZZ"))).InSequence(s); |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(kHeaderAcknowledgement))) |
| .InSequence(s); |
| EXPECT_CALL(handler_, OnDecodingCompleted()).InSequence(s); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0500" // Required Insert Count 4 and Delta Base 0. |
| // Base is 4 + 0 = 4. |
| "83" // Dynamic table entry with relative index 3, absolute index 0. |
| "82" // Dynamic table entry with relative index 2, absolute index 1. |
| "81" // Dynamic table entry with relative index 1, absolute index 2. |
| "80" // Dynamic table entry with relative index 0, absolute index 3. |
| "41025a5a")); // Name of entry 1 (relative index) from dynamic table, |
| // with value "ZZ". |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))).InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("foo"))) |
| .InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("ZZ"))).InSequence(s); |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(kHeaderAcknowledgement))) |
| .InSequence(s); |
| EXPECT_CALL(handler_, OnDecodingCompleted()).InSequence(s); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0502" // Required Insert Count 4 and Delta Base 2. |
| // Base is 4 + 2 = 6. |
| "85" // Dynamic table entry with relative index 5, absolute index 0. |
| "84" // Dynamic table entry with relative index 4, absolute index 1. |
| "83" // Dynamic table entry with relative index 3, absolute index 2. |
| "82" // Dynamic table entry with relative index 2, absolute index 3. |
| "43025a5a")); // Name of entry 3 (relative index) from dynamic table, |
| // with value "ZZ". |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))).InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("foo"))) |
| .InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("ZZ"))).InSequence(s); |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(kHeaderAcknowledgement))) |
| .InSequence(s); |
| EXPECT_CALL(handler_, OnDecodingCompleted()).InSequence(s); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0582" // Required Insert Count 4 and Delta Base 2 with sign bit set. |
| // Base is 4 - 2 - 1 = 1. |
| "80" // Dynamic table entry with relative index 0, absolute index 0. |
| "10" // Dynamic table entry with post-base index 0, absolute index 1. |
| "11" // Dynamic table entry with post-base index 1, absolute index 2. |
| "12" // Dynamic table entry with post-base index 2, absolute index 3. |
| "01025a5a")); // Name of entry 1 (post-base index) from dynamic table, |
| // with value "ZZ". |
| } |
| |
| TEST_P(QpackDecoderTest, DecreasingDynamicTableCapacityEvictsEntries) { |
| // Set dynamic table capacity to 1024. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe107")); |
| // Add literal entry with name "foo" and value "bar". |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(kHeaderAcknowledgement))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0200" // Required Insert Count 1 and Delta Base 0. |
| // Base is 1 + 0 = 1. |
| "80")); // Dynamic table entry with relative index 0, absolute index 0. |
| |
| // Change dynamic table capacity to 32 bytes, smaller than the entry. |
| // This must cause the entry to be evicted. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3f01")); |
| |
| EXPECT_CALL(handler_, OnDecodingErrorDetected( |
| Eq("Dynamic table entry already evicted."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0200" // Required Insert Count 1 and Delta Base 0. |
| // Base is 1 + 0 = 1. |
| "80")); // Dynamic table entry with relative index 0, absolute index 0. |
| } |
| |
| TEST_P(QpackDecoderTest, EncoderStreamErrorEntryTooLarge) { |
| EXPECT_CALL(encoder_stream_error_delegate_, |
| OnEncoderStreamError(Eq("Error inserting literal entry."))); |
| |
| // Set dynamic table capacity to 34. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3f03")); |
| // Add literal entry with name "foo" and value "bar", size is 32 + 3 + 3 = 38. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| } |
| |
| TEST_P(QpackDecoderTest, EncoderStreamErrorInvalidStaticTableEntry) { |
| EXPECT_CALL(encoder_stream_error_delegate_, |
| OnEncoderStreamError(Eq("Invalid static table entry."))); |
| |
| // Address invalid static table entry index 99. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("ff2400")); |
| } |
| |
| TEST_P(QpackDecoderTest, EncoderStreamErrorInvalidDynamicTableEntry) { |
| EXPECT_CALL(encoder_stream_error_delegate_, |
| OnEncoderStreamError(Eq("Invalid relative index."))); |
| |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode( |
| "3fe107" // Set dynamic table capacity to 1024. |
| "6294e703626172" // Add literal entry with name "foo" and value "bar". |
| "8100")); // Address dynamic table entry with relative index 1. Such |
| // entry does not exist. The most recently added and only |
| // dynamic table entry has relative index 0. |
| } |
| |
| TEST_P(QpackDecoderTest, EncoderStreamErrorDuplicateInvalidEntry) { |
| EXPECT_CALL(encoder_stream_error_delegate_, |
| OnEncoderStreamError(Eq("Invalid relative index."))); |
| |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode( |
| "3fe107" // Set dynamic table capacity to 1024. |
| "6294e703626172" // Add literal entry with name "foo" and value "bar". |
| "01")); // Duplicate dynamic table entry with relative index 1. Such |
| // entry does not exist. The most recently added and only |
| // dynamic table entry has relative index 0. |
| } |
| |
| TEST_P(QpackDecoderTest, EncoderStreamErrorTooLargeInteger) { |
| EXPECT_CALL(encoder_stream_error_delegate_, |
| OnEncoderStreamError(Eq("Encoded integer too large."))); |
| |
| DecodeEncoderStreamData( |
| quiche::QuicheTextUtils::HexDecode("3fffffffffffffffffffff")); |
| } |
| |
| TEST_P(QpackDecoderTest, InvalidDynamicEntryWhenBaseIsZero) { |
| EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Invalid relative index."))); |
| |
| // Set dynamic table capacity to 1024. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe107")); |
| // Add literal entry with name "foo" and value "bar". |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0280" // Required Insert Count is 1. Base 1 - 1 - 0 = 0 is explicitly |
| // permitted by the spec. |
| "80")); // However, addressing entry with relative index 0 would point to |
| // absolute index -1, which is invalid. |
| } |
| |
| TEST_P(QpackDecoderTest, InvalidNegativeBase) { |
| EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Error calculating Base."))); |
| |
| // Required Insert Count 1, Delta Base 1 with sign bit set, Base would |
| // be 1 - 1 - 1 = -1, but it is not allowed to be negative. |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("0281")); |
| } |
| |
| TEST_P(QpackDecoderTest, InvalidDynamicEntryByRelativeIndex) { |
| // Set dynamic table capacity to 1024. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe107")); |
| // Add literal entry with name "foo" and value "bar". |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| |
| EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Invalid relative index."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0200" // Required Insert Count 1 and Delta Base 0. |
| // Base is 1 + 0 = 1. |
| "81")); // Indexed Header Field instruction addressing relative index 1. |
| // This is absolute index -1, which is invalid. |
| |
| EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Invalid relative index."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0200" // Required Insert Count 1 and Delta Base 0. |
| // Base is 1 + 0 = 1. |
| "4100")); // Literal Header Field with Name Reference instruction |
| // addressing relative index 1. This is absolute index -1, |
| // which is invalid. |
| } |
| |
| TEST_P(QpackDecoderTest, EvictedDynamicTableEntry) { |
| // Update dynamic table capacity to 128. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3f61")); |
| |
| // Add literal entry with name "foo" and value "bar", size 32 + 3 + 3 = 38. |
| // This fits in the table three times. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| // Duplicate entry four times. This evicts the first two instances. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("00000000")); |
| |
| EXPECT_CALL(handler_, OnDecodingErrorDetected( |
| Eq("Dynamic table entry already evicted."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0500" // Required Insert Count 4 and Delta Base 0. |
| // Base is 4 + 0 = 4. |
| "82")); // Indexed Header Field instruction addressing relative index 2. |
| // This is absolute index 1. Such entry does not exist. |
| |
| EXPECT_CALL(handler_, OnDecodingErrorDetected( |
| Eq("Dynamic table entry already evicted."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0500" // Required Insert Count 4 and Delta Base 0. |
| // Base is 4 + 0 = 4. |
| "4200")); // Literal Header Field with Name Reference instruction |
| // addressing relative index 2. This is absolute index 1. Such |
| // entry does not exist. |
| |
| EXPECT_CALL(handler_, OnDecodingErrorDetected( |
| Eq("Dynamic table entry already evicted."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0380" // Required Insert Count 2 and Delta Base 0 with sign bit set. |
| // Base is 2 - 0 - 1 = 1 |
| "10")); // Indexed Header Field instruction addressing dynamic table |
| // entry with post-base index 0, absolute index 1. Such entry |
| // does not exist. |
| |
| EXPECT_CALL(handler_, OnDecodingErrorDetected( |
| Eq("Dynamic table entry already evicted."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0380" // Required Insert Count 2 and Delta Base 0 with sign bit set. |
| // Base is 2 - 0 - 1 = 1 |
| "0000")); // Literal Header Field With Name Reference instruction |
| // addressing dynamic table entry with post-base index 0, |
| // absolute index 1. Such entry does not exist. |
| } |
| |
| TEST_P(QpackDecoderTest, TableCapacityMustNotExceedMaximum) { |
| EXPECT_CALL( |
| encoder_stream_error_delegate_, |
| OnEncoderStreamError(Eq("Error updating dynamic table capacity."))); |
| |
| // Try to update dynamic table capacity to 2048, which exceeds the maximum. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe10f")); |
| } |
| |
| TEST_P(QpackDecoderTest, SetDynamicTableCapacity) { |
| // Update dynamic table capacity to 128, which does not exceed the maximum. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3f61")); |
| } |
| |
| TEST_P(QpackDecoderTest, InvalidEncodedRequiredInsertCount) { |
| // Maximum dynamic table capacity is 1024. |
| // MaxEntries is 1024 / 32 = 32. |
| // Required Insert Count is decoded modulo 2 * MaxEntries, that is, modulo 64. |
| // A value of 1 cannot be encoded as 65 even though it has the same remainder. |
| EXPECT_CALL(handler_, OnDecodingErrorDetected( |
| Eq("Error decoding Required Insert Count."))); |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("4100")); |
| } |
| |
| // Regression test for https://crbug.com/970218: Decoder must stop processing |
| // after a Header Block Prefix with an invalid Encoded Required Insert Count. |
| TEST_P(QpackDecoderTest, DataAfterInvalidEncodedRequiredInsertCount) { |
| EXPECT_CALL(handler_, OnDecodingErrorDetected( |
| Eq("Error decoding Required Insert Count."))); |
| // Header Block Prefix followed by some extra data. |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode("410000")); |
| } |
| |
| TEST_P(QpackDecoderTest, WrappedRequiredInsertCount) { |
| // Maximum dynamic table capacity is 1024. |
| // MaxEntries is 1024 / 32 = 32. |
| |
| // Set dynamic table capacity to 1024. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe107")); |
| // Add literal entry with name "foo" and a 600 byte long value. This will fit |
| // in the dynamic table once but not twice. |
| DecodeEncoderStreamData( |
| quiche::QuicheTextUtils::HexDecode("6294e7" // Name "foo". |
| "7fd903")); // Value length 600. |
| std::string header_value(600, 'Z'); |
| DecodeEncoderStreamData(header_value); |
| |
| // Duplicate most recent entry 200 times. |
| DecodeEncoderStreamData(std::string(200, '\x00')); |
| |
| // Now there is only one entry in the dynamic table, with absolute index 200. |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq(header_value))); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(kHeaderAcknowledgement))); |
| |
| // Send header block with Required Insert Count = 201. |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0a00" // Encoded Required Insert Count 10, Required Insert Count 201, |
| // Delta Base 0, Base 201. |
| "80")); // Emit dynamic table entry with relative index 0. |
| } |
| |
| TEST_P(QpackDecoderTest, NonZeroRequiredInsertCountButNoDynamicEntries) { |
| // Set dynamic table capacity to 1024. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe107")); |
| // Add literal entry with name "foo" and value "bar". |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("GET"))); |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("Required Insert Count too large."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0200" // Required Insert Count is 1. |
| "d1")); // But the only instruction references the static table. |
| } |
| |
| TEST_P(QpackDecoderTest, AddressEntryNotAllowedByRequiredInsertCount) { |
| // Set dynamic table capacity to 1024. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe107")); |
| // Add literal entry with name "foo" and value "bar". |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| |
| EXPECT_CALL( |
| handler_, |
| OnDecodingErrorDetected( |
| Eq("Absolute Index must be smaller than Required Insert Count."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0201" // Required Insert Count 1 and Delta Base 1. |
| // Base is 1 + 1 = 2. |
| "80")); // Indexed Header Field instruction addressing dynamic table |
| // entry with relative index 0, absolute index 1. This is not |
| // allowed by Required Insert Count. |
| |
| EXPECT_CALL( |
| handler_, |
| OnDecodingErrorDetected( |
| Eq("Absolute Index must be smaller than Required Insert Count."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0201" // Required Insert Count 1 and Delta Base 1. |
| // Base is 1 + 1 = 2. |
| "4000")); // Literal Header Field with Name Reference instruction |
| // addressing dynamic table entry with relative index 0, |
| // absolute index 1. This is not allowed by Required Index |
| // Count. |
| |
| EXPECT_CALL( |
| handler_, |
| OnDecodingErrorDetected( |
| Eq("Absolute Index must be smaller than Required Insert Count."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0200" // Required Insert Count 1 and Delta Base 0. |
| // Base is 1 + 0 = 1. |
| "10")); // Indexed Header Field with Post-Base Index instruction |
| // addressing dynamic table entry with post-base index 0, |
| // absolute index 1. This is not allowed by Required Insert |
| // Count. |
| |
| EXPECT_CALL( |
| handler_, |
| OnDecodingErrorDetected( |
| Eq("Absolute Index must be smaller than Required Insert Count."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0200" // Required Insert Count 1 and Delta Base 0. |
| // Base is 1 + 0 = 1. |
| "0000")); // Literal Header Field with Post-Base Name Reference |
| // instruction addressing dynamic table entry with post-base |
| // index 0, absolute index 1. This is not allowed by Required |
| // Index Count. |
| } |
| |
| TEST_P(QpackDecoderTest, PromisedRequiredInsertCountLargerThanActual) { |
| // Set dynamic table capacity to 1024. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe107")); |
| // Add literal entry with name "foo" and value "bar". |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| // Duplicate entry twice so that decoding of header blocks with Required |
| // Insert Count not exceeding 3 is not blocked. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("00")); |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("00")); |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("Required Insert Count too large."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0300" // Required Insert Count 2 and Delta Base 0. |
| // Base is 2 + 0 = 2. |
| "81")); // Indexed Header Field instruction addressing dynamic table |
| // entry with relative index 1, absolute index 0. Header block |
| // requires insert count of 1, even though Required Insert Count |
| // is 2. |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq(""))); |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("Required Insert Count too large."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0300" // Required Insert Count 2 and Delta Base 0. |
| // Base is 2 + 0 = 2. |
| "4100")); // Literal Header Field with Name Reference instruction |
| // addressing dynamic table entry with relative index 1, |
| // absolute index 0. Header block requires insert count of 1, |
| // even though Required Insert Count is 2. |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("Required Insert Count too large."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0481" // Required Insert Count 3 and Delta Base 1 with sign bit set. |
| // Base is 3 - 1 - 1 = 1. |
| "10")); // Indexed Header Field with Post-Base Index instruction |
| // addressing dynamic table entry with post-base index 0, |
| // absolute index 1. Header block requires insert count of 2, |
| // even though Required Insert Count is 3. |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq(""))); |
| EXPECT_CALL(handler_, |
| OnDecodingErrorDetected(Eq("Required Insert Count too large."))); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0481" // Required Insert Count 3 and Delta Base 1 with sign bit set. |
| // Base is 3 - 1 - 1 = 1. |
| "0000")); // Literal Header Field with Post-Base Name Reference |
| // instruction addressing dynamic table entry with post-base |
| // index 0, absolute index 1. Header block requires insert |
| // count of 2, even though Required Insert Count is 3. |
| } |
| |
| TEST_P(QpackDecoderTest, BlockedDecoding) { |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0200" // Required Insert Count 1 and Delta Base 0. |
| // Base is 1 + 0 = 1. |
| "80")); // Indexed Header Field instruction addressing dynamic table |
| // entry with relative index 0, absolute index 0. |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(kHeaderAcknowledgement))); |
| |
| // Set dynamic table capacity to 1024. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe107")); |
| // Add literal entry with name "foo" and value "bar". |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| } |
| |
| TEST_P(QpackDecoderTest, BlockedDecodingUnblockedBeforeEndOfHeaderBlock) { |
| StartDecoding(); |
| DecodeData(quiche::QuicheTextUtils::HexDecode( |
| "0200" // Required Insert Count 1 and Delta Base 0. |
| // Base is 1 + 0 = 1. |
| "80" // Indexed Header Field instruction addressing dynamic table |
| // entry with relative index 0, absolute index 0. |
| "d1")); // Static table entry with index 17. |
| |
| // Set dynamic table capacity to 1024. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe107")); |
| |
| // Add literal entry with name "foo" and value "bar". Decoding is now |
| // unblocked because dynamic table Insert Count reached the Required Insert |
| // Count of the header block. |handler_| methods are called immediately for |
| // the already consumed part of the header block. |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("GET"))); |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| Mock::VerifyAndClearExpectations(&handler_); |
| |
| // Rest of header block is processed by QpackProgressiveDecoder |
| // in the unblocked state. |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":scheme"), Eq("https"))); |
| DecodeData(quiche::QuicheTextUtils::HexDecode( |
| "80" // Indexed Header Field instruction addressing dynamic table |
| // entry with relative index 0, absolute index 0. |
| "d7")); // Static table entry with index 23. |
| Mock::VerifyAndClearExpectations(&handler_); |
| |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(kHeaderAcknowledgement))); |
| EndDecoding(); |
| } |
| |
| // Regression test for https://crbug.com/1024263. |
| TEST_P(QpackDecoderTest, |
| BlockedDecodingUnblockedAndErrorBeforeEndOfHeaderBlock) { |
| StartDecoding(); |
| DecodeData(quiche::QuicheTextUtils::HexDecode( |
| "0200" // Required Insert Count 1 and Delta Base 0. |
| // Base is 1 + 0 = 1. |
| "80" // Indexed Header Field instruction addressing dynamic table |
| // entry with relative index 0, absolute index 0. |
| "81")); // Relative index 1 is equal to Base, therefore invalid. |
| |
| // Set dynamic table capacity to 1024. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe107")); |
| |
| // Add literal entry with name "foo" and value "bar". Decoding is now |
| // unblocked because dynamic table Insert Count reached the Required Insert |
| // Count of the header block. |handler_| methods are called immediately for |
| // the already consumed part of the header block. |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); |
| EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Invalid relative index."))); |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| } |
| |
| // Make sure that Required Insert Count is compared to Insert Count, |
| // not size of dynamic table. |
| TEST_P(QpackDecoderTest, BlockedDecodingAndEvictedEntries) { |
| // Update dynamic table capacity to 128. |
| // At most three non-empty entries fit in the dynamic table. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3f61")); |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0700" // Required Insert Count 6 and Delta Base 0. |
| // Base is 6 + 0 = 6. |
| "80")); // Indexed Header Field instruction addressing dynamic table |
| // entry with relative index 0, absolute index 5. |
| |
| // Add literal entry with name "foo" and value "bar". |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e703626172")); |
| |
| // Duplicate entry four times. This evicts the first two instances. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("00000000")); |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("baz"))); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(kHeaderAcknowledgement))); |
| |
| // Add literal entry with name "foo" and value "bar". |
| // Insert Count is now 6, reaching Required Insert Count of the header block. |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("6294e70362617a")); |
| } |
| |
| TEST_P(QpackDecoderTest, TooManyBlockedStreams) { |
| // Required Insert Count 1 and Delta Base 0. |
| // Without any dynamic table entries received, decoding is blocked. |
| std::string data = quiche::QuicheTextUtils::HexDecode("0200"); |
| |
| auto progressive_decoder1 = CreateProgressiveDecoder(/* stream_id = */ 1); |
| progressive_decoder1->Decode(data); |
| |
| EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq( |
| "Limit on number of blocked streams exceeded."))); |
| |
| auto progressive_decoder2 = CreateProgressiveDecoder(/* stream_id = */ 2); |
| progressive_decoder2->Decode(data); |
| } |
| |
| TEST_P(QpackDecoderTest, InsertCountIncrement) { |
| DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode( |
| "3fe107" // Set dynamic table capacity to 1024. |
| "6294e703626172" // Add literal entry with name "foo" and value "bar". |
| "00")); // Duplicate entry. |
| |
| EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); |
| EXPECT_CALL(handler_, OnDecodingCompleted()); |
| |
| // Decoder received two insertions, but Header Acknowledgement only increases |
| // Known Insert Count to one. Decoder should send an Insert Count Increment |
| // instruction with increment of one to update Known Insert Count to two. |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(quiche::QuicheTextUtils::HexDecode( |
| "81" // Header Acknowledgement on stream 1 |
| "01")))); // Insert Count Increment with increment of one |
| |
| DecodeHeaderBlock(quiche::QuicheTextUtils::HexDecode( |
| "0200" // Required Insert Count 1 and Delta Base 0. |
| // Base is 1 + 0 = 1. |
| "80")); // Dynamic table entry with relative index 0, absolute index 0. |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace quic |