Index: net/http2/decoder/decode_http2_structures_test.cc |
diff --git a/net/http2/decoder/decode_http2_structures_test.cc b/net/http2/decoder/decode_http2_structures_test.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4b09e7a7b83691fb79fb483948403a04be4d0e1c |
--- /dev/null |
+++ b/net/http2/decoder/decode_http2_structures_test.cc |
@@ -0,0 +1,542 @@ |
+// 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/http2/decoder/decode_http2_structures.h" |
+ |
+// Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined |
+// in net/http2/http2_structures.h). |
+ |
+// TODO(jamessynge): Combine tests of DoDecode, MaybeDecode, SlowDecode and |
+// Http2StructureDecoder test using gUnit's support for tests parameterized |
+// by type. |
+ |
+#include <stddef.h> |
+#include <string> |
+ |
+#include "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/logging.h" |
+#include "base/strings/string_piece.h" |
+#include "net/http2/decoder/decode_buffer.h" |
+#include "net/http2/decoder/decode_status.h" |
+#include "net/http2/http2_constants.h" |
+#include "net/http2/http2_structures_test_util.h" |
+#include "net/http2/tools/http2_frame_builder.h" |
+#include "net/http2/tools/random_decoder_test.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using ::testing::AssertionFailure; |
+using ::testing::AssertionResult; |
+using ::testing::AssertionSuccess; |
+using base::StringPiece; |
+using std::string; |
+ |
+namespace net { |
+namespace test { |
+namespace { |
+ |
+template <class S> |
+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 RandomDecoderTest { |
+ protected: |
+ typedef S Structure; |
+ |
+ StructureDecoderTest() { |
+ // IF the test adds more data after the encoded structure, stop as |
+ // soon as the structure is decoded. |
+ stop_decode_on_done_ = true; |
+ } |
+ |
+ // Reset the decoding to the start of the structure, and overwrite the |
+ // current contents of |structure_|, in to which we'll decode the buffer. |
+ DecodeStatus StartDecoding(DecodeBuffer* b) override { |
+ decode_offset_ = 0; |
+ Randomize(&structure_); |
+ return ResumeDecoding(b); |
+ } |
+ |
+ DecodeStatus ResumeDecoding(DecodeBuffer* b) override { |
+ // If we're at the start... |
+ if (decode_offset_ == 0) { |
+ const uint32_t start_offset = b->Offset(); |
+ const char* const start_cursor = b->cursor(); |
+ // ... attempt to decode the entire structure. |
+ if (MaybeDecode(&structure_, b)) { |
+ ++fast_decode_count_; |
+ EXPECT_EQ(S::EncodedSize(), b->Offset() - start_offset); |
+ |
+ if (!HasFailure()) { |
+ // Success. Confirm that SlowDecode produces the same result. |
+ DecodeBuffer b2(start_cursor, b->Offset() - start_offset); |
+ S second; |
+ Randomize(&second); |
+ uint32_t second_offset = 0; |
+ EXPECT_TRUE(SlowDecode(&second, &b2, &second_offset)); |
+ EXPECT_EQ(S::EncodedSize(), second_offset); |
+ EXPECT_EQ(structure_, second); |
+ } |
+ |
+ // Test can't easily tell if MaybeDecode or SlowDecode is used, so |
+ // update decode_offset_ as if SlowDecode had been used to completely |
+ // decode. |
+ decode_offset_ = S::EncodedSize(); |
+ return DecodeStatus::kDecodeDone; |
+ } |
+ } |
+ |
+ // We didn't have enough in the first buffer to decode everything, so we'll |
+ // reach here multiple times until we've completely decoded the structure. |
+ if (SlowDecode(&structure_, b, &decode_offset_)) { |
+ ++slow_decode_count_; |
+ EXPECT_EQ(S::EncodedSize(), decode_offset_); |
+ return DecodeStatus::kDecodeDone; |
+ } |
+ |
+ // Drained the input buffer, but not yet done. |
+ EXPECT_TRUE(b->Empty()); |
+ EXPECT_GT(S::EncodedSize(), decode_offset_); |
+ |
+ return DecodeStatus::kDecodeInProgress; |
+ } |
+ |
+ // Set the fields of |*p| to random values. |
+ void Randomize(S* p) { ::net::test::Randomize(p, RandomPtr()); } |
+ |
+ AssertionResult ValidatorForDecodeLeadingStructure(const S* expected, |
+ const DecodeBuffer& db, |
+ DecodeStatus status) { |
+ if (expected != nullptr && *expected != structure_) { |
+ return AssertionFailure() |
+ << "Expected structs to be equal\nExpected: " << *expected |
+ << "\n Actual: " << structure_; |
+ } |
+ return AssertionSuccess(); |
+ } |
+ |
+ // Fully decodes the Structure at the start of data, and confirms it matches |
+ // *expected (if provided). |
+ void DecodeLeadingStructure(const S* expected, StringPiece data) { |
+ ASSERT_LE(S::EncodedSize(), data.size()); |
+ DecodeBuffer original(data); |
+ |
+ // The validator is called after each of the several times that the input |
+ // DecodeBuffer is decoded, each with a different segmentation of the input. |
+ // Validate that structure_ matches the expected value, if provided. |
+ Validator validator = |
+ base::Bind(&StructureDecoderTest::ValidatorForDecodeLeadingStructure, |
+ base::Unretained(this), expected); |
+ |
+ // First validate that decoding is done and that we've advanced the cursor |
+ // the expected amount. |
+ Validator wrapped_validator = |
+ ValidateDoneAndOffset(S::EncodedSize(), validator); |
+ |
+ // Decode several times, with several segmentations of the input buffer. |
+ fast_decode_count_ = 0; |
+ slow_decode_count_ = 0; |
+ EXPECT_TRUE(DecodeAndValidateSeveralWays( |
+ &original, false /*return_non_zero_on_first*/, wrapped_validator)); |
+ |
+ if (!HasFailure()) { |
+ EXPECT_EQ(S::EncodedSize(), decode_offset_); |
+ EXPECT_EQ(S::EncodedSize(), original.Offset()); |
+ EXPECT_LT(0u, fast_decode_count_); |
+ EXPECT_LT(0u, slow_decode_count_); |
+ if (expected != nullptr) { |
+ DVLOG(1) << "DecodeLeadingStructure expected: " << *expected; |
+ DVLOG(1) << "DecodeLeadingStructure actual: " << structure_; |
+ EXPECT_EQ(*expected, structure_); |
+ } |
+ } |
+ } |
+ |
+ template <size_t N> |
+ void DecodeLeadingStructure(const char (&data)[N]) { |
+ DecodeLeadingStructure(nullptr, StringPiece(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) { |
+ 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); |
+ } |
+ } |
+ |
+ 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[] = { |
+ 0x00, 0x00, 0x05, // Payload length: 5 |
+ 0x01, // Frame type: HEADERS |
+ 0x08, // Flags: PADDED |
+ 0x00, 0x00, 0x00, 0x01, // Stream ID: 1 |
+ 0x04, // Padding length: 4 |
+ 0x00, 0x00, 0x00, 0x00, // Padding bytes |
+ }; |
+ DecodeLeadingStructure(kData); |
+ if (!HasFailure()) { |
+ EXPECT_EQ(5u, structure_.payload_length); |
+ EXPECT_EQ(Http2FrameType::HEADERS, structure_.type); |
+ EXPECT_EQ(Http2FrameFlag::FLAG_PADDED, structure_.flags); |
+ EXPECT_EQ(1u, structure_.stream_id); |
+ } |
+ } |
+ { |
+ // Unlikely input. |
+ const char kData[] = { |
+ 0xffu, 0xffu, 0xffu, // Payload length: uint24 max |
+ 0xffu, // Frame type: Unknown |
+ 0xffu, // Flags: Unknown/All |
+ 0xffu, 0xffu, 0xffu, 0xffu, // 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(100); |
+} |
+ |
+//------------------------------------------------------------------------------ |
+ |
+class PriorityFieldsDecoderTest |
+ : public StructureDecoderTest<Http2PriorityFields> {}; |
+ |
+TEST_F(PriorityFieldsDecoderTest, DecodesLiteral) { |
+ { |
+ const char kData[] = { |
+ 0x80u, 0x00, 0x00, 0x05, // Exclusive (yes) and Dependency (5) |
+ 0xffu, // 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[] = { |
+ 0x7f, 0xffu, |
+ 0xffu, 0xffu, // Exclusive (no) and Dependency (0x7fffffff) |
+ 0x00, // 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(100); |
+} |
+ |
+//------------------------------------------------------------------------------ |
+ |
+class RstStreamFieldsDecoderTest |
+ : public StructureDecoderTest<Http2RstStreamFields> {}; |
+ |
+TEST_F(RstStreamFieldsDecoderTest, DecodesLiteral) { |
+ { |
+ const char kData[] = { |
+ 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR |
+ }; |
+ DecodeLeadingStructure(kData); |
+ if (!HasFailure()) { |
+ EXPECT_TRUE(structure_.IsSupportedErrorCode()); |
+ EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code); |
+ } |
+ } |
+ { |
+ const char kData[] = { |
+ 0xffu, 0xffu, 0xffu, 0xffu, // 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(100); |
+} |
+ |
+//------------------------------------------------------------------------------ |
+ |
+class SettingFieldsDecoderTest |
+ : public StructureDecoderTest<Http2SettingFields> {}; |
+ |
+TEST_F(SettingFieldsDecoderTest, DecodesLiteral) { |
+ { |
+ const char kData[] = { |
+ 0x00, 0x01, // Setting: HEADER_TABLE_SIZE |
+ 0x00, 0x00, 0x40, 0x00, // 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[] = { |
+ 0x00, 0x00, // Setting: Unknown (0) |
+ 0xffu, 0xffu, 0xffu, 0xffu, // Value: max uint32 |
+ }; |
+ DecodeLeadingStructure(kData); |
+ if (!HasFailure()) { |
+ EXPECT_FALSE(structure_.IsSupportedParameter()); |
+ EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter); |
+ } |
+ } |
+} |
+ |
+TEST_F(SettingFieldsDecoderTest, DecodesRandomized) { |
+ TestDecodingRandomizedStructures(100); |
+} |
+ |
+//------------------------------------------------------------------------------ |
+ |
+class PushPromiseFieldsDecoderTest |
+ : public StructureDecoderTest<Http2PushPromiseFields> {}; |
+ |
+TEST_F(PushPromiseFieldsDecoderTest, DecodesLiteral) { |
+ { |
+ const char kData[] = { |
+ 0x00, 0x01, 0x8au, 0x92u, // 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[] = { |
+ 0xffu, 0xffu, 0xffu, 0xffu, // Promised Stream ID: max uint31 and R-bit |
+ }; |
+ DecodeLeadingStructure(kData); |
+ if (!HasFailure()) { |
+ EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id); |
+ } |
+ } |
+} |
+ |
+TEST_F(PushPromiseFieldsDecoderTest, DecodesRandomized) { |
+ TestDecodingRandomizedStructures(100); |
+} |
+ |
+//------------------------------------------------------------------------------ |
+ |
+class PingFieldsDecoderTest : public StructureDecoderTest<Http2PingFields> {}; |
+ |
+TEST_F(PingFieldsDecoderTest, DecodesLiteral) { |
+ { |
+ // Each byte is different, so can detect if order changed. |
+ const char kData[] = { |
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, |
+ }; |
+ DecodeLeadingStructure(kData); |
+ if (!HasFailure()) { |
+ EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); |
+ } |
+ } |
+ { |
+ // All zeros, detect problems handling NULs. |
+ const char kData[] = { |
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
+ }; |
+ DecodeLeadingStructure(kData); |
+ if (!HasFailure()) { |
+ EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); |
+ } |
+ } |
+ { |
+ const char kData[] = { |
+ 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, |
+ }; |
+ DecodeLeadingStructure(kData); |
+ if (!HasFailure()) { |
+ EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); |
+ } |
+ } |
+} |
+ |
+TEST_F(PingFieldsDecoderTest, DecodesRandomized) { |
+ TestDecodingRandomizedStructures(100); |
+} |
+ |
+//------------------------------------------------------------------------------ |
+ |
+class GoAwayFieldsDecoderTest : public StructureDecoderTest<Http2GoAwayFields> { |
+}; |
+ |
+TEST_F(GoAwayFieldsDecoderTest, DecodesLiteral) { |
+ { |
+ const char kData[] = { |
+ 0x00, 0x00, 0x00, 0x00, // Last Stream ID: 0 |
+ 0x00, 0x00, 0x00, 0x00, // 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[] = { |
+ 0x00, 0x00, 0x00, 0x01, // Last Stream ID: 1 |
+ 0x00, 0x00, 0x00, 0x0d, // 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[] = { |
+ 0xffu, 0xffu, 0xffu, 0xffu, // Last Stream ID: max uint31 and R-bit |
+ 0xffu, 0xffu, 0xffu, 0xffu, // 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(100); |
+} |
+ |
+//------------------------------------------------------------------------------ |
+ |
+class WindowUpdateFieldsDecoderTest |
+ : public StructureDecoderTest<Http2WindowUpdateFields> {}; |
+ |
+TEST_F(WindowUpdateFieldsDecoderTest, DecodesLiteral) { |
+ { |
+ const char kData[] = { |
+ 0x00, 0x01, 0x00, 0x00, // 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[] = { |
+ 0x00, 0x00, 0x00, 0x00, // 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. |
+ const char kData[] = { |
+ 0xffu, 0xffu, 0xffu, |
+ 0xffu, // Window Size Increment: max uint31 and R-bit |
+ }; |
+ DecodeLeadingStructure(kData); |
+ if (!HasFailure()) { |
+ EXPECT_EQ(StreamIdMask(), structure_.window_size_increment); |
+ } |
+ } |
+} |
+ |
+TEST_F(WindowUpdateFieldsDecoderTest, DecodesRandomized) { |
+ TestDecodingRandomizedStructures(100); |
+} |
+ |
+//------------------------------------------------------------------------------ |
+ |
+class AltSvcFieldsDecoderTest : public StructureDecoderTest<Http2AltSvcFields> { |
+}; |
+ |
+TEST_F(AltSvcFieldsDecoderTest, DecodesLiteral) { |
+ { |
+ const char kData[] = { |
+ 0x00, 0x00, // Origin Length: 0 |
+ }; |
+ DecodeLeadingStructure(kData); |
+ if (!HasFailure()) { |
+ EXPECT_EQ(0, structure_.origin_length); |
+ } |
+ } |
+ { |
+ const char kData[] = { |
+ 0x00, 0x14, // Origin Length: 20 |
+ }; |
+ DecodeLeadingStructure(kData); |
+ if (!HasFailure()) { |
+ EXPECT_EQ(20, structure_.origin_length); |
+ } |
+ } |
+ { |
+ const char kData[] = { |
+ 0xffu, 0xffu, // Origin Length: uint16 max |
+ }; |
+ DecodeLeadingStructure(kData); |
+ if (!HasFailure()) { |
+ EXPECT_EQ(65535, structure_.origin_length); |
+ } |
+ } |
+} |
+ |
+TEST_F(AltSvcFieldsDecoderTest, DecodesRandomized) { |
+ TestDecodingRandomizedStructures(100); |
+} |
+ |
+} // namespace |
+} // namespace test |
+} // namespace net |