| // 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 "http2/decoder/decode_http2_structures.h" | |
| // Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined | |
| // in http2/http2_structures.h). | |
| #include <stddef.h> | |
| #include <string> | |
| #include "absl/strings/string_view.h" | |
| #include "http2/decoder/decode_buffer.h" | |
| #include "http2/decoder/decode_status.h" | |
| #include "http2/http2_constants.h" | |
| #include "http2/http2_structures_test_util.h" | |
| #include "http2/platform/api/http2_logging.h" | |
| #include "http2/platform/api/http2_test_helpers.h" | |
| #include "http2/test_tools/http2_random.h" | |
| #include "http2/tools/http2_frame_builder.h" | |
| #include "common/platform/api/quiche_test.h" | |
| using ::testing::AssertionResult; | |
| namespace http2 { | |
| namespace test { | |
| namespace { | |
| template <typename T, size_t N> | |
| absl::string_view ToStringPiece(T (&data)[N]) { | |
| return absl::string_view(reinterpret_cast<const char*>(data), N * sizeof(T)); | |
| } | |
| template <class S> | |
| std::string SerializeStructure(const S& s) { | |
| Http2FrameBuilder fb; | |
| fb.Append(s); | |
| EXPECT_EQ(S::EncodedSize(), fb.size()); | |
| return fb.buffer(); | |
| } | |
| template <class S> | |
| class StructureDecoderTest : public QuicheTest { | |
| protected: | |
| typedef S Structure; | |
| StructureDecoderTest() : random_(), random_decode_count_(100) {} | |
| // Set the fields of |*p| to random values. | |
| void Randomize(S* p) { ::http2::test::Randomize(p, &random_); } | |
| // Fully decodes the Structure at the start of data, and confirms it matches | |
| // *expected (if provided). | |
| void DecodeLeadingStructure(const S* expected, absl::string_view data) { | |
| ASSERT_LE(S::EncodedSize(), data.size()); | |
| DecodeBuffer db(data); | |
| Randomize(&structure_); | |
| DoDecode(&structure_, &db); | |
| EXPECT_EQ(db.Offset(), S::EncodedSize()); | |
| if (expected != nullptr) { | |
| EXPECT_EQ(structure_, *expected); | |
| } | |
| } | |
| template <size_t N> | |
| void DecodeLeadingStructure(const char (&data)[N]) { | |
| DecodeLeadingStructure(nullptr, absl::string_view(data, N)); | |
| } | |
| // Encode the structure |in_s| into bytes, then decode the bytes | |
| // and validate that the decoder produced the same field values. | |
| void EncodeThenDecode(const S& in_s) { | |
| std::string bytes = SerializeStructure(in_s); | |
| EXPECT_EQ(S::EncodedSize(), bytes.size()); | |
| DecodeLeadingStructure(&in_s, bytes); | |
| } | |
| // Generate | |
| void TestDecodingRandomizedStructures(size_t count) { | |
| for (size_t i = 0; i < count && !HasFailure(); ++i) { | |
| Structure input; | |
| Randomize(&input); | |
| EncodeThenDecode(input); | |
| } | |
| } | |
| void TestDecodingRandomizedStructures() { | |
| TestDecodingRandomizedStructures(random_decode_count_); | |
| } | |
| Http2Random random_; | |
| const size_t random_decode_count_; | |
| uint32_t decode_offset_ = 0; | |
| S structure_; | |
| size_t fast_decode_count_ = 0; | |
| size_t slow_decode_count_ = 0; | |
| }; | |
| class FrameHeaderDecoderTest : public StructureDecoderTest<Http2FrameHeader> {}; | |
| TEST_F(FrameHeaderDecoderTest, DecodesLiteral) { | |
| { | |
| // Realistic input. | |
| const char kData[] = { | |
| '\x00', '\x00', '\x05', // Payload length: 5 | |
| '\x01', // Frame type: HEADERS | |
| '\x08', // Flags: PADDED | |
| '\x00', '\x00', '\x00', '\x01', // Stream ID: 1 | |
| '\x04', // Padding length: 4 | |
| '\x00', '\x00', '\x00', '\x00', // Padding bytes | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(5u, structure_.payload_length); | |
| EXPECT_EQ(Http2FrameType::HEADERS, structure_.type); | |
| EXPECT_EQ(Http2FrameFlag::PADDED, structure_.flags); | |
| EXPECT_EQ(1u, structure_.stream_id); | |
| } | |
| } | |
| { | |
| // Unlikely input. | |
| const char kData[] = { | |
| '\xff', '\xff', '\xff', // Payload length: uint24 max | |
| '\xff', // Frame type: Unknown | |
| '\xff', // Flags: Unknown/All | |
| '\xff', '\xff', '\xff', '\xff', // Stream ID: uint31 max, plus R-bit | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ((1u << 24) - 1, structure_.payload_length); | |
| EXPECT_EQ(static_cast<Http2FrameType>(255), structure_.type); | |
| EXPECT_EQ(255, structure_.flags); | |
| EXPECT_EQ(0x7FFFFFFFu, structure_.stream_id); | |
| } | |
| } | |
| } | |
| TEST_F(FrameHeaderDecoderTest, DecodesRandomized) { | |
| TestDecodingRandomizedStructures(); | |
| } | |
| //------------------------------------------------------------------------------ | |
| class PriorityFieldsDecoderTest | |
| : public StructureDecoderTest<Http2PriorityFields> {}; | |
| TEST_F(PriorityFieldsDecoderTest, DecodesLiteral) { | |
| { | |
| const char kData[] = { | |
| '\x80', '\x00', '\x00', '\x05', // Exclusive (yes) and Dependency (5) | |
| '\xff', // Weight: 256 (after adding 1) | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(5u, structure_.stream_dependency); | |
| EXPECT_EQ(256u, structure_.weight); | |
| EXPECT_EQ(true, structure_.is_exclusive); | |
| } | |
| } | |
| { | |
| const char kData[] = { | |
| '\x7f', '\xff', | |
| '\xff', '\xff', // Exclusive (no) and Dependency (0x7fffffff) | |
| '\x00', // Weight: 1 (after adding 1) | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(StreamIdMask(), structure_.stream_dependency); | |
| EXPECT_EQ(1u, structure_.weight); | |
| EXPECT_FALSE(structure_.is_exclusive); | |
| } | |
| } | |
| } | |
| TEST_F(PriorityFieldsDecoderTest, DecodesRandomized) { | |
| TestDecodingRandomizedStructures(); | |
| } | |
| //------------------------------------------------------------------------------ | |
| class RstStreamFieldsDecoderTest | |
| : public StructureDecoderTest<Http2RstStreamFields> {}; | |
| TEST_F(RstStreamFieldsDecoderTest, DecodesLiteral) { | |
| { | |
| const char kData[] = { | |
| '\x00', '\x00', '\x00', '\x01', // Error: PROTOCOL_ERROR | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_TRUE(structure_.IsSupportedErrorCode()); | |
| EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code); | |
| } | |
| } | |
| { | |
| const char kData[] = { | |
| '\xff', '\xff', '\xff', | |
| '\xff', // Error: max uint32 (Unknown error code) | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_FALSE(structure_.IsSupportedErrorCode()); | |
| EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); | |
| } | |
| } | |
| } | |
| TEST_F(RstStreamFieldsDecoderTest, DecodesRandomized) { | |
| TestDecodingRandomizedStructures(); | |
| } | |
| //------------------------------------------------------------------------------ | |
| class SettingFieldsDecoderTest | |
| : public StructureDecoderTest<Http2SettingFields> {}; | |
| TEST_F(SettingFieldsDecoderTest, DecodesLiteral) { | |
| { | |
| const char kData[] = { | |
| '\x00', '\x01', // Setting: HEADER_TABLE_SIZE | |
| '\x00', '\x00', '\x40', '\x00', // Value: 16K | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_TRUE(structure_.IsSupportedParameter()); | |
| EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE, | |
| structure_.parameter); | |
| EXPECT_EQ(1u << 14, structure_.value); | |
| } | |
| } | |
| { | |
| const char kData[] = { | |
| '\x00', '\x00', // Setting: Unknown (0) | |
| '\xff', '\xff', '\xff', '\xff', // Value: max uint32 | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_FALSE(structure_.IsSupportedParameter()); | |
| EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter); | |
| } | |
| } | |
| } | |
| TEST_F(SettingFieldsDecoderTest, DecodesRandomized) { | |
| TestDecodingRandomizedStructures(); | |
| } | |
| //------------------------------------------------------------------------------ | |
| class PushPromiseFieldsDecoderTest | |
| : public StructureDecoderTest<Http2PushPromiseFields> {}; | |
| TEST_F(PushPromiseFieldsDecoderTest, DecodesLiteral) { | |
| { | |
| const char kData[] = { | |
| '\x00', '\x01', '\x8a', '\x92', // Promised Stream ID: 101010 | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(101010u, structure_.promised_stream_id); | |
| } | |
| } | |
| { | |
| // Promised stream id has R-bit (reserved for future use) set, which | |
| // should be cleared by the decoder. | |
| const char kData[] = { | |
| '\xff', '\xff', '\xff', | |
| '\xff', // Promised Stream ID: max uint31 and R-bit | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id); | |
| } | |
| } | |
| } | |
| TEST_F(PushPromiseFieldsDecoderTest, DecodesRandomized) { | |
| TestDecodingRandomizedStructures(); | |
| } | |
| //------------------------------------------------------------------------------ | |
| class PingFieldsDecoderTest : public StructureDecoderTest<Http2PingFields> {}; | |
| TEST_F(PingFieldsDecoderTest, DecodesLiteral) { | |
| { | |
| // Each byte is different, so can detect if order changed. | |
| const char kData[] = { | |
| '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(absl::string_view(kData, 8), | |
| ToStringPiece(structure_.opaque_bytes)); | |
| } | |
| } | |
| { | |
| // All zeros, detect problems handling NULs. | |
| const char kData[] = { | |
| '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(absl::string_view(kData, 8), | |
| ToStringPiece(structure_.opaque_bytes)); | |
| } | |
| } | |
| { | |
| const char kData[] = { | |
| '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(absl::string_view(kData, 8), | |
| ToStringPiece(structure_.opaque_bytes)); | |
| } | |
| } | |
| } | |
| TEST_F(PingFieldsDecoderTest, DecodesRandomized) { | |
| TestDecodingRandomizedStructures(); | |
| } | |
| //------------------------------------------------------------------------------ | |
| class GoAwayFieldsDecoderTest : public StructureDecoderTest<Http2GoAwayFields> { | |
| }; | |
| TEST_F(GoAwayFieldsDecoderTest, DecodesLiteral) { | |
| { | |
| const char kData[] = { | |
| '\x00', '\x00', '\x00', '\x00', // Last Stream ID: 0 | |
| '\x00', '\x00', '\x00', '\x00', // Error: NO_ERROR (0) | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(0u, structure_.last_stream_id); | |
| EXPECT_TRUE(structure_.IsSupportedErrorCode()); | |
| EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, structure_.error_code); | |
| } | |
| } | |
| { | |
| const char kData[] = { | |
| '\x00', '\x00', '\x00', '\x01', // Last Stream ID: 1 | |
| '\x00', '\x00', '\x00', '\x0d', // Error: HTTP_1_1_REQUIRED | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(1u, structure_.last_stream_id); | |
| EXPECT_TRUE(structure_.IsSupportedErrorCode()); | |
| EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, structure_.error_code); | |
| } | |
| } | |
| { | |
| const char kData[] = { | |
| '\xff', '\xff', | |
| '\xff', '\xff', // Last Stream ID: max uint31 and R-bit | |
| '\xff', '\xff', | |
| '\xff', '\xff', // Error: max uint32 (Unknown error code) | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(StreamIdMask(), structure_.last_stream_id); // No high-bit. | |
| EXPECT_FALSE(structure_.IsSupportedErrorCode()); | |
| EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); | |
| } | |
| } | |
| } | |
| TEST_F(GoAwayFieldsDecoderTest, DecodesRandomized) { | |
| TestDecodingRandomizedStructures(); | |
| } | |
| //------------------------------------------------------------------------------ | |
| class WindowUpdateFieldsDecoderTest | |
| : public StructureDecoderTest<Http2WindowUpdateFields> {}; | |
| TEST_F(WindowUpdateFieldsDecoderTest, DecodesLiteral) { | |
| { | |
| const char kData[] = { | |
| '\x00', '\x01', '\x00', '\x00', // Window Size Increment: 2 ^ 16 | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(1u << 16, structure_.window_size_increment); | |
| } | |
| } | |
| { | |
| // Increment must be non-zero, but we need to be able to decode the invalid | |
| // zero to detect it. | |
| const char kData[] = { | |
| '\x00', '\x00', '\x00', '\x00', // Window Size Increment: 0 | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(0u, structure_.window_size_increment); | |
| } | |
| } | |
| { | |
| // Increment has R-bit (reserved for future use) set, which | |
| // should be cleared by the decoder. | |
| // clang-format off | |
| const char kData[] = { | |
| // Window Size Increment: max uint31 and R-bit | |
| '\xff', '\xff', '\xff', '\xff', | |
| }; | |
| // clang-format on | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(StreamIdMask(), structure_.window_size_increment); | |
| } | |
| } | |
| } | |
| TEST_F(WindowUpdateFieldsDecoderTest, DecodesRandomized) { | |
| TestDecodingRandomizedStructures(); | |
| } | |
| //------------------------------------------------------------------------------ | |
| class AltSvcFieldsDecoderTest : public StructureDecoderTest<Http2AltSvcFields> { | |
| }; | |
| TEST_F(AltSvcFieldsDecoderTest, DecodesLiteral) { | |
| { | |
| const char kData[] = { | |
| '\x00', '\x00', // Origin Length: 0 | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(0, structure_.origin_length); | |
| } | |
| } | |
| { | |
| const char kData[] = { | |
| '\x00', '\x14', // Origin Length: 20 | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(20, structure_.origin_length); | |
| } | |
| } | |
| { | |
| const char kData[] = { | |
| '\xff', '\xff', // Origin Length: uint16 max | |
| }; | |
| DecodeLeadingStructure(kData); | |
| if (!HasFailure()) { | |
| EXPECT_EQ(65535, structure_.origin_length); | |
| } | |
| } | |
| } | |
| TEST_F(AltSvcFieldsDecoderTest, DecodesRandomized) { | |
| TestDecodingRandomizedStructures(); | |
| } | |
| } // namespace | |
| } // namespace test | |
| } // namespace http2 |