| // Copyright 2016 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/http2/hpack/decoder/hpack_whole_entry_buffer.h" |
| |
| // Tests of HpackWholeEntryBuffer: does it buffer correctly, and does it |
| // detect Huffman decoding errors and oversize string errors? |
| |
| #include "quiche/common/platform/api/quiche_test.h" |
| |
| using ::testing::_; |
| using ::testing::AllOf; |
| using ::testing::InSequence; |
| using ::testing::Property; |
| using ::testing::StrictMock; |
| |
| namespace http2 { |
| namespace test { |
| namespace { |
| |
| constexpr size_t kMaxStringSize = 20; |
| |
| class MockHpackWholeEntryListener : public HpackWholeEntryListener { |
| public: |
| ~MockHpackWholeEntryListener() override = default; |
| |
| MOCK_METHOD(void, OnIndexedHeader, (size_t index), (override)); |
| MOCK_METHOD(void, OnNameIndexAndLiteralValue, |
| (HpackEntryType entry_type, size_t name_index, |
| HpackDecoderStringBuffer* value_buffer), |
| (override)); |
| MOCK_METHOD(void, OnLiteralNameAndValue, |
| (HpackEntryType entry_type, HpackDecoderStringBuffer* name_buffer, |
| HpackDecoderStringBuffer* value_buffer), |
| (override)); |
| MOCK_METHOD(void, OnDynamicTableSizeUpdate, (size_t size), (override)); |
| MOCK_METHOD(void, OnHpackDecodeError, |
| (HpackDecodingError error, std::string detailed_error), |
| (override)); |
| }; |
| |
| class HpackWholeEntryBufferTest : public quiche::test::QuicheTest { |
| protected: |
| HpackWholeEntryBufferTest() : entry_buffer_(&listener_, kMaxStringSize) {} |
| ~HpackWholeEntryBufferTest() override = default; |
| |
| StrictMock<MockHpackWholeEntryListener> listener_; |
| HpackWholeEntryBuffer entry_buffer_; |
| }; |
| |
| // OnIndexedHeader is an immediate pass through. |
| TEST_F(HpackWholeEntryBufferTest, OnIndexedHeader) { |
| { |
| InSequence seq; |
| EXPECT_CALL(listener_, OnIndexedHeader(17)); |
| entry_buffer_.OnIndexedHeader(17); |
| } |
| { |
| InSequence seq; |
| EXPECT_CALL(listener_, OnIndexedHeader(62)); |
| entry_buffer_.OnIndexedHeader(62); |
| } |
| { |
| InSequence seq; |
| EXPECT_CALL(listener_, OnIndexedHeader(62)); |
| entry_buffer_.OnIndexedHeader(62); |
| } |
| { |
| InSequence seq; |
| EXPECT_CALL(listener_, OnIndexedHeader(128)); |
| entry_buffer_.OnIndexedHeader(128); |
| } |
| StrictMock<MockHpackWholeEntryListener> listener2; |
| entry_buffer_.set_listener(&listener2); |
| { |
| InSequence seq; |
| EXPECT_CALL(listener2, OnIndexedHeader(100)); |
| entry_buffer_.OnIndexedHeader(100); |
| } |
| } |
| |
| // OnDynamicTableSizeUpdate is an immediate pass through. |
| TEST_F(HpackWholeEntryBufferTest, OnDynamicTableSizeUpdate) { |
| { |
| InSequence seq; |
| EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(4096)); |
| entry_buffer_.OnDynamicTableSizeUpdate(4096); |
| } |
| { |
| InSequence seq; |
| EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(0)); |
| entry_buffer_.OnDynamicTableSizeUpdate(0); |
| } |
| { |
| InSequence seq; |
| EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024)); |
| entry_buffer_.OnDynamicTableSizeUpdate(1024); |
| } |
| { |
| InSequence seq; |
| EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024)); |
| entry_buffer_.OnDynamicTableSizeUpdate(1024); |
| } |
| StrictMock<MockHpackWholeEntryListener> listener2; |
| entry_buffer_.set_listener(&listener2); |
| { |
| InSequence seq; |
| EXPECT_CALL(listener2, OnDynamicTableSizeUpdate(0)); |
| entry_buffer_.OnDynamicTableSizeUpdate(0); |
| } |
| } |
| |
| TEST_F(HpackWholeEntryBufferTest, OnNameIndexAndLiteralValue) { |
| entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader, |
| 123); |
| entry_buffer_.OnValueStart(false, 10); |
| entry_buffer_.OnValueData("some data.", 10); |
| |
| // Force the value to be buffered. |
| entry_buffer_.BufferStringsIfUnbuffered(); |
| |
| EXPECT_CALL( |
| listener_, |
| OnNameIndexAndLiteralValue( |
| HpackEntryType::kNeverIndexedLiteralHeader, 123, |
| AllOf(Property(&HpackDecoderStringBuffer::str, "some data."), |
| Property(&HpackDecoderStringBuffer::BufferedLength, 10)))); |
| |
| entry_buffer_.OnValueEnd(); |
| } |
| |
| TEST_F(HpackWholeEntryBufferTest, OnLiteralNameAndValue) { |
| entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0); |
| // Force the name to be buffered by delivering it in two pieces. |
| entry_buffer_.OnNameStart(false, 9); |
| entry_buffer_.OnNameData("some-", 5); |
| entry_buffer_.OnNameData("name", 4); |
| entry_buffer_.OnNameEnd(); |
| entry_buffer_.OnValueStart(false, 12); |
| entry_buffer_.OnValueData("Header Value", 12); |
| |
| EXPECT_CALL( |
| listener_, |
| OnLiteralNameAndValue( |
| HpackEntryType::kIndexedLiteralHeader, |
| AllOf(Property(&HpackDecoderStringBuffer::str, "some-name"), |
| Property(&HpackDecoderStringBuffer::BufferedLength, 9)), |
| AllOf(Property(&HpackDecoderStringBuffer::str, "Header Value"), |
| Property(&HpackDecoderStringBuffer::BufferedLength, 0)))); |
| |
| entry_buffer_.OnValueEnd(); |
| } |
| |
| // Verify that a name longer than the allowed size generates an error. |
| TEST_F(HpackWholeEntryBufferTest, NameTooLong) { |
| entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0); |
| EXPECT_CALL(listener_, |
| OnHpackDecodeError(HpackDecodingError::kNameTooLong, _)); |
| entry_buffer_.OnNameStart(false, kMaxStringSize + 1); |
| } |
| |
| // Verify that a value longer than the allowed size generates an error. |
| TEST_F(HpackWholeEntryBufferTest, ValueTooLong) { |
| entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0); |
| EXPECT_CALL(listener_, |
| OnHpackDecodeError( |
| HpackDecodingError::kValueTooLong, |
| "Value length (21) of [path] is longer than permitted (20)")); |
| entry_buffer_.OnNameStart(false, 4); |
| entry_buffer_.OnNameData("path", 4); |
| entry_buffer_.OnNameEnd(); |
| entry_buffer_.OnValueStart(false, kMaxStringSize + 1); |
| } |
| |
| // Regression test for b/162141899. |
| TEST_F(HpackWholeEntryBufferTest, ValueTooLongWithoutName) { |
| entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 1); |
| EXPECT_CALL(listener_, |
| OnHpackDecodeError( |
| HpackDecodingError::kValueTooLong, |
| "Value length (21) of [] is longer than permitted (20)")); |
| entry_buffer_.OnValueStart(false, kMaxStringSize + 1); |
| } |
| |
| // Verify that a Huffman encoded name with an explicit EOS generates an error |
| // for an explicit EOS. |
| TEST_F(HpackWholeEntryBufferTest, NameHuffmanError) { |
| const char data[] = "\xff\xff\xff"; |
| entry_buffer_.OnStartLiteralHeader(HpackEntryType::kUnindexedLiteralHeader, |
| 0); |
| entry_buffer_.OnNameStart(true, 4); |
| entry_buffer_.OnNameData(data, 3); |
| |
| EXPECT_CALL(listener_, |
| OnHpackDecodeError(HpackDecodingError::kNameHuffmanError, _)); |
| |
| entry_buffer_.OnNameData(data, 1); |
| |
| // After an error is reported, the listener is not called again. |
| EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(8096)).Times(0); |
| entry_buffer_.OnDynamicTableSizeUpdate(8096); |
| } |
| |
| // Verify that a Huffman encoded value that isn't properly terminated with |
| // a partial EOS symbol generates an error. |
| TEST_F(HpackWholeEntryBufferTest, ValueHuffmanError) { |
| const char data[] = "\x00\x00\x00"; |
| entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader, |
| 61); |
| entry_buffer_.OnValueStart(true, 3); |
| entry_buffer_.OnValueData(data, 3); |
| |
| EXPECT_CALL(listener_, |
| OnHpackDecodeError(HpackDecodingError::kValueHuffmanError, _)); |
| |
| entry_buffer_.OnValueEnd(); |
| |
| // After an error is reported, the listener is not called again. |
| EXPECT_CALL(listener_, OnIndexedHeader(17)).Times(0); |
| entry_buffer_.OnIndexedHeader(17); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace http2 |