| // Copyright (c) 2019 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 "quiche/quic/core/qpack/qpack_decoded_headers_accumulator.h" |
| |
| #include <cstring> |
| |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/string_view.h" |
| #include "quiche/quic/core/qpack/qpack_decoder.h" |
| #include "quiche/quic/platform/api/quic_test.h" |
| #include "quiche/quic/test_tools/qpack/qpack_test_utils.h" |
| |
| using ::testing::_; |
| using ::testing::ElementsAre; |
| using ::testing::Eq; |
| using ::testing::Pair; |
| using ::testing::SaveArg; |
| using ::testing::StrictMock; |
| |
| namespace quic { |
| namespace test { |
| namespace { |
| |
| // Arbitrary stream ID used for testing. |
| QuicStreamId kTestStreamId = 1; |
| |
| // Limit on header list size. |
| const size_t kMaxHeaderListSize = 100; |
| |
| // Maximum dynamic table capacity. |
| const size_t kMaxDynamicTableCapacity = 100; |
| |
| // Maximum number of blocked streams. |
| const uint64_t kMaximumBlockedStreams = 1; |
| |
| // Header Acknowledgement decoder stream instruction with stream_id = 1. |
| const char* const kHeaderAcknowledgement = "\x81"; |
| |
| class MockVisitor : public QpackDecodedHeadersAccumulator::Visitor { |
| public: |
| ~MockVisitor() override = default; |
| MOCK_METHOD(void, OnHeadersDecoded, |
| (QuicHeaderList headers, bool header_list_size_limit_exceeded), |
| (override)); |
| MOCK_METHOD(void, OnHeaderDecodingError, |
| (QuicErrorCode error_code, absl::string_view error_message), |
| (override)); |
| }; |
| |
| } // anonymous namespace |
| |
| class QpackDecodedHeadersAccumulatorTest : public QuicTest { |
| protected: |
| QpackDecodedHeadersAccumulatorTest() |
| : qpack_decoder_(kMaxDynamicTableCapacity, kMaximumBlockedStreams, |
| &encoder_stream_error_delegate_), |
| accumulator_(kTestStreamId, &qpack_decoder_, &visitor_, |
| kMaxHeaderListSize) { |
| qpack_decoder_.set_qpack_stream_sender_delegate( |
| &decoder_stream_sender_delegate_); |
| } |
| |
| NoopEncoderStreamErrorDelegate encoder_stream_error_delegate_; |
| StrictMock<MockQpackStreamSenderDelegate> decoder_stream_sender_delegate_; |
| QpackDecoder qpack_decoder_; |
| StrictMock<MockVisitor> visitor_; |
| QpackDecodedHeadersAccumulator accumulator_; |
| }; |
| |
| // HEADERS frame payload must have a complete Header Block Prefix. |
| TEST_F(QpackDecodedHeadersAccumulatorTest, EmptyPayload) { |
| EXPECT_CALL(visitor_, |
| OnHeaderDecodingError(QUIC_QPACK_DECOMPRESSION_FAILED, |
| Eq("Incomplete header data prefix."))); |
| accumulator_.EndHeaderBlock(); |
| } |
| |
| // HEADERS frame payload must have a complete Header Block Prefix. |
| TEST_F(QpackDecodedHeadersAccumulatorTest, TruncatedHeaderBlockPrefix) { |
| accumulator_.Decode(absl::HexStringToBytes("00")); |
| |
| EXPECT_CALL(visitor_, |
| OnHeaderDecodingError(QUIC_QPACK_DECOMPRESSION_FAILED, |
| Eq("Incomplete header data prefix."))); |
| accumulator_.EndHeaderBlock(); |
| } |
| |
| TEST_F(QpackDecodedHeadersAccumulatorTest, EmptyHeaderList) { |
| std::string encoded_data(absl::HexStringToBytes("0000")); |
| accumulator_.Decode(encoded_data); |
| |
| QuicHeaderList header_list; |
| EXPECT_CALL(visitor_, OnHeadersDecoded(_, false)) |
| .WillOnce(SaveArg<0>(&header_list)); |
| accumulator_.EndHeaderBlock(); |
| |
| EXPECT_EQ(0u, header_list.uncompressed_header_bytes()); |
| EXPECT_EQ(encoded_data.size(), header_list.compressed_header_bytes()); |
| EXPECT_TRUE(header_list.empty()); |
| } |
| |
| // This payload is the prefix of a valid payload, but EndHeaderBlock() is called |
| // before it can be completely decoded. |
| TEST_F(QpackDecodedHeadersAccumulatorTest, TruncatedPayload) { |
| accumulator_.Decode(absl::HexStringToBytes("00002366")); |
| |
| EXPECT_CALL(visitor_, OnHeaderDecodingError(QUIC_QPACK_DECOMPRESSION_FAILED, |
| Eq("Incomplete header block."))); |
| accumulator_.EndHeaderBlock(); |
| } |
| |
| // This payload is invalid because it refers to a non-existing static entry. |
| TEST_F(QpackDecodedHeadersAccumulatorTest, InvalidPayload) { |
| EXPECT_CALL(visitor_, |
| OnHeaderDecodingError(QUIC_QPACK_DECOMPRESSION_FAILED, |
| Eq("Static table entry not found."))); |
| accumulator_.Decode(absl::HexStringToBytes("0000ff23ff24")); |
| } |
| |
| TEST_F(QpackDecodedHeadersAccumulatorTest, Success) { |
| std::string encoded_data(absl::HexStringToBytes("000023666f6f03626172")); |
| accumulator_.Decode(encoded_data); |
| |
| QuicHeaderList header_list; |
| EXPECT_CALL(visitor_, OnHeadersDecoded(_, false)) |
| .WillOnce(SaveArg<0>(&header_list)); |
| accumulator_.EndHeaderBlock(); |
| |
| EXPECT_THAT(header_list, ElementsAre(Pair("foo", "bar"))); |
| EXPECT_EQ(strlen("foo") + strlen("bar"), |
| header_list.uncompressed_header_bytes()); |
| EXPECT_EQ(encoded_data.size(), header_list.compressed_header_bytes()); |
| } |
| |
| // Test that Decode() calls are not ignored after header list limit is exceeded, |
| // otherwise decoding could fail with "incomplete header block" error. |
| TEST_F(QpackDecodedHeadersAccumulatorTest, ExceedLimitThenSplitInstruction) { |
| // Total length of header list exceeds kMaxHeaderListSize. |
| accumulator_.Decode(absl::HexStringToBytes( |
| "0000" // header block prefix |
| "26666f6f626172" // header key: "foobar" |
| "7d61616161616161616161616161616161616161" // header value: 'a' 125 times |
| "616161616161616161616161616161616161616161616161616161616161616161616161" |
| "616161616161616161616161616161616161616161616161616161616161616161616161" |
| "61616161616161616161616161616161616161616161616161616161616161616161" |
| "ff")); // first byte of a two-byte long Indexed Header Field instruction |
| accumulator_.Decode(absl::HexStringToBytes( |
| "0f" // second byte of a two-byte long Indexed Header Field instruction |
| )); |
| |
| EXPECT_CALL(visitor_, OnHeadersDecoded(_, true)); |
| accumulator_.EndHeaderBlock(); |
| } |
| |
| // Test that header list limit enforcement works with blocked encoding. |
| TEST_F(QpackDecodedHeadersAccumulatorTest, ExceedLimitBlocked) { |
| // Total length of header list exceeds kMaxHeaderListSize. |
| accumulator_.Decode(absl::HexStringToBytes( |
| "0200" // header block prefix |
| "80" // reference to dynamic table entry not yet received |
| "26666f6f626172" // header key: "foobar" |
| "7d61616161616161616161616161616161616161" // header value: 'a' 125 times |
| "616161616161616161616161616161616161616161616161616161616161616161616161" |
| "616161616161616161616161616161616161616161616161616161616161616161616161" |
| "61616161616161616161616161616161616161616161616161616161616161616161")); |
| accumulator_.EndHeaderBlock(); |
| |
| // Set dynamic table capacity. |
| qpack_decoder_.OnSetDynamicTableCapacity(kMaxDynamicTableCapacity); |
| // Adding dynamic table entry unblocks decoding. |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(kHeaderAcknowledgement))); |
| |
| EXPECT_CALL(visitor_, OnHeadersDecoded(_, true)); |
| qpack_decoder_.OnInsertWithoutNameReference("foo", "bar"); |
| } |
| |
| TEST_F(QpackDecodedHeadersAccumulatorTest, BlockedDecoding) { |
| // Reference to dynamic table entry not yet received. |
| std::string encoded_data(absl::HexStringToBytes("020080")); |
| accumulator_.Decode(encoded_data); |
| accumulator_.EndHeaderBlock(); |
| |
| // Set dynamic table capacity. |
| qpack_decoder_.OnSetDynamicTableCapacity(kMaxDynamicTableCapacity); |
| // Adding dynamic table entry unblocks decoding. |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(kHeaderAcknowledgement))); |
| |
| QuicHeaderList header_list; |
| EXPECT_CALL(visitor_, OnHeadersDecoded(_, false)) |
| .WillOnce(SaveArg<0>(&header_list)); |
| qpack_decoder_.OnInsertWithoutNameReference("foo", "bar"); |
| |
| EXPECT_THAT(header_list, ElementsAre(Pair("foo", "bar"))); |
| EXPECT_EQ(strlen("foo") + strlen("bar"), |
| header_list.uncompressed_header_bytes()); |
| EXPECT_EQ(encoded_data.size(), header_list.compressed_header_bytes()); |
| } |
| |
| TEST_F(QpackDecodedHeadersAccumulatorTest, |
| BlockedDecodingUnblockedBeforeEndOfHeaderBlock) { |
| // Reference to dynamic table entry not yet received. |
| accumulator_.Decode(absl::HexStringToBytes("020080")); |
| |
| // Set dynamic table capacity. |
| qpack_decoder_.OnSetDynamicTableCapacity(kMaxDynamicTableCapacity); |
| // Adding dynamic table entry unblocks decoding. |
| qpack_decoder_.OnInsertWithoutNameReference("foo", "bar"); |
| |
| // Rest of header block: same entry again. |
| EXPECT_CALL(decoder_stream_sender_delegate_, |
| WriteStreamData(Eq(kHeaderAcknowledgement))); |
| accumulator_.Decode(absl::HexStringToBytes("80")); |
| |
| QuicHeaderList header_list; |
| EXPECT_CALL(visitor_, OnHeadersDecoded(_, false)) |
| .WillOnce(SaveArg<0>(&header_list)); |
| accumulator_.EndHeaderBlock(); |
| |
| EXPECT_THAT(header_list, ElementsAre(Pair("foo", "bar"), Pair("foo", "bar"))); |
| } |
| |
| // Regression test for https://crbug.com/1024263. |
| TEST_F(QpackDecodedHeadersAccumulatorTest, |
| BlockedDecodingUnblockedAndErrorBeforeEndOfHeaderBlock) { |
| // Required Insert Count higher than number of entries causes decoding to be |
| // blocked. |
| accumulator_.Decode(absl::HexStringToBytes("0200")); |
| // Indexed Header Field instruction addressing dynamic table entry with |
| // relative index 0, absolute index 0. |
| accumulator_.Decode(absl::HexStringToBytes("80")); |
| // Relative index larger than or equal to Base is invalid. |
| accumulator_.Decode(absl::HexStringToBytes("81")); |
| |
| // Set dynamic table capacity. |
| qpack_decoder_.OnSetDynamicTableCapacity(kMaxDynamicTableCapacity); |
| |
| // Adding dynamic table entry unblocks decoding. Error is detected. |
| EXPECT_CALL(visitor_, OnHeaderDecodingError(QUIC_QPACK_DECOMPRESSION_FAILED, |
| Eq("Invalid relative index."))); |
| qpack_decoder_.OnInsertWithoutNameReference("foo", "bar"); |
| } |
| |
| } // namespace test |
| } // namespace quic |