diff options
Diffstat (limited to 'chromium/net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc')
-rw-r--r-- | chromium/net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc b/chromium/net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc new file mode 100644 index 00000000000..b3e54be0b0d --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc @@ -0,0 +1,249 @@ +// 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_decoder_test_utils.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 |