|  | // 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 "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h" | 
|  |  | 
|  | // Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined | 
|  | // in net/third_party/quiche/src/http2/http2_structures.h). | 
|  |  | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include <string> | 
|  |  | 
|  | #include "absl/strings/string_view.h" | 
|  | #include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" | 
|  | #include "net/third_party/quiche/src/http2/decoder/decode_status.h" | 
|  | #include "net/third_party/quiche/src/http2/http2_constants.h" | 
|  | #include "net/third_party/quiche/src/http2/http2_structures_test_util.h" | 
|  | #include "net/third_party/quiche/src/http2/platform/api/http2_logging.h" | 
|  | #include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" | 
|  | #include "net/third_party/quiche/src/http2/test_tools/http2_random.h" | 
|  | #include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" | 
|  | #include "net/third_party/quiche/src/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 |