Index: net/http2/decoder/decode_buffer_test.cc |
diff --git a/net/http2/decoder/decode_buffer_test.cc b/net/http2/decoder/decode_buffer_test.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..35a9fbbf8c7c2f980cf82790afd19e59bd90e2a8 |
--- /dev/null |
+++ b/net/http2/decoder/decode_buffer_test.cc |
@@ -0,0 +1,406 @@ |
+// 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_buffer.h" |
+ |
+#include <string> |
+ |
+#include "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/callback.h" |
+#include "base/logging.h" |
+#include "base/strings/string_piece.h" |
+#include "net/http2/tools/http2_random.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using base::StringPiece; |
+using std::string; |
+ |
+namespace net { |
+namespace test { |
+ |
+enum class TestEnumClass32 { |
+ kValue1 = 1, |
+ kValue99 = 99, |
+ kValue1M = 1000000, |
+}; |
+ |
+enum class TestEnumClass8 { |
+ kValue1 = 1, |
+ kValue2 = 1, |
+ kValue99 = 99, |
+ kValue255 = 255, |
+}; |
+ |
+enum TestEnum8 { |
+ kMaskLo = 0x01, |
+ kMaskHi = 0x80, |
+}; |
+ |
+struct TestStruct { |
+ uint8_t f1; |
+ uint16_t f2; |
+ uint32_t f3; // Decoded as a uint24 |
+ uint32_t f4; |
+ uint32_t f5; // Decoded as if uint31 |
+ TestEnumClass32 f6; |
+ TestEnumClass8 f7; |
+ TestEnum8 f8; |
+}; |
+ |
+const size_t kF1Offset = 0; |
+const size_t kF2Offset = 1; |
+const size_t kF3Offset = 3; |
+const size_t kF4Offset = 6; |
+const size_t kF5Offset = 10; |
+const size_t kF6Offset = 14; |
+const size_t kF7Offset = 18; |
+const size_t kF8Offset = 19; |
+ |
+class DecodeBufferTest : public ::testing::Test { |
+ public: |
+ DecodeBufferTest() {} |
+ |
+ protected: |
+ // Double checks the call fn(f). |
+ template <typename T> |
+ bool SlowDecodeField(DecodeBuffer* b, |
+ size_t field_size, |
+ size_t field_offset, |
+ const base::Callback<bool(DecodeBuffer*)>& fn, |
+ T* f) { |
+ VLOG(2) << "Remaining: " << b->Remaining(); |
+ VLOG(2) << "field_size: " << field_size; |
+ VLOG(2) << "field_offset: " << field_offset; |
+ VLOG(2) << "decode_offset_: " << decode_offset_; |
+ EXPECT_GE(decode_offset_, field_offset); |
+ bool had_data = b->HasData(); |
+ VLOG(2) << "had_data: " << had_data; |
+ uint32_t old = static_cast<uint32_t>(*f); |
+ VLOG(2) << "old: " << old; |
+ size_t old_decode_offset = decode_offset_; |
+ bool done = fn.Run(b); |
+ VLOG(2) << "done: " << done; |
+ if (old_decode_offset == decode_offset_) { |
+ // Didn't do any decoding (may have no input, or may have already |
+ // decoded this field). |
+ if (done) { |
+ EXPECT_LE(field_offset + field_size, decode_offset_); |
+ // Shouldn't have modified already decoded field. |
+ EXPECT_EQ(old, static_cast<uint32_t>(*f)); |
+ } else { |
+ EXPECT_TRUE(!had_data); |
+ } |
+ } else { |
+ // Did some decoding. |
+ EXPECT_TRUE(had_data); |
+ EXPECT_LT(old_decode_offset, decode_offset_); |
+ if (done) { |
+ EXPECT_EQ(field_offset + field_size, decode_offset_); |
+ } else { |
+ EXPECT_GT(field_offset + field_size, decode_offset_); |
+ } |
+ } |
+ VLOG(2) << "---------------------------------------"; |
+ return done; |
+ } |
+ |
+ bool decode_f1(TestStruct* p, DecodeBuffer* db) { |
+ return db->SlowDecodeUInt8(kF1Offset, &decode_offset_, &p->f1); |
+ } |
+ bool decode_f2(TestStruct* p, DecodeBuffer* db) { |
+ return db->SlowDecodeUInt16(kF2Offset, &decode_offset_, &p->f2); |
+ } |
+ bool decode_f3(TestStruct* p, DecodeBuffer* db) { |
+ return db->SlowDecodeUInt24(kF3Offset, &decode_offset_, &p->f3); |
+ } |
+ bool decode_f4(TestStruct* p, DecodeBuffer* db) { |
+ return db->SlowDecodeUInt32(kF4Offset, &decode_offset_, &p->f4); |
+ } |
+ bool decode_f5(TestStruct* p, DecodeBuffer* db) { |
+ return db->SlowDecodeUInt31(kF5Offset, &decode_offset_, &p->f5); |
+ } |
+ bool decode_f6(TestStruct* p, DecodeBuffer* db) { |
+ return db->SlowDecodeEnum(4, kF6Offset, &decode_offset_, &p->f6); |
+ } |
+ bool decode_f7(TestStruct* p, DecodeBuffer* db) { |
+ return db->SlowDecodeEnum(1, kF7Offset, &decode_offset_, &p->f7); |
+ } |
+ bool decode_f8(TestStruct* p, DecodeBuffer* db) { |
+ return db->SlowDecodeEnum(1, kF8Offset, &decode_offset_, &p->f8); |
+ } |
+ |
+ void SlowDecodeTestStruct(StringPiece input, TestStruct* p) { |
+ VLOG(2) << "############################################################"; |
+ EXPECT_LE(10u, input.size()); |
+ decode_offset_ = 0; |
+ while (input.size() > 0) { |
+ size_t size = input.size(); |
+ // Sometimes check that zero length input is OK. |
+ auto r = random_.Next(); |
+ if (r % 100 == 0) { |
+ size = 0; |
+ } else if (size > 1) { |
+ auto r = random_.Next(); |
+ size = (r % size) + 1; |
+ } |
+ VLOG(2) << "================= input size " << size; |
+ DecodeBuffer b(input.data(), size); |
+ size_t old_decode_offset = decode_offset_; |
+ if (SlowDecodeField(&b, 1, kF1Offset, |
+ base::Bind(&DecodeBufferTest::decode_f1, |
+ base::Unretained(this), p), |
+ &p->f1) && |
+ SlowDecodeField(&b, 2, kF2Offset, |
+ base::Bind(&DecodeBufferTest::decode_f2, |
+ base::Unretained(this), p), |
+ &p->f2) && |
+ SlowDecodeField(&b, 3, kF3Offset, |
+ base::Bind(&DecodeBufferTest::decode_f3, |
+ base::Unretained(this), p), |
+ &p->f3) && |
+ SlowDecodeField(&b, 4, kF4Offset, |
+ base::Bind(&DecodeBufferTest::decode_f4, |
+ base::Unretained(this), p), |
+ &p->f4) && |
+ SlowDecodeField(&b, 4, kF5Offset, |
+ base::Bind(&DecodeBufferTest::decode_f5, |
+ base::Unretained(this), p), |
+ &p->f5) && |
+ SlowDecodeField(&b, 4, kF6Offset, |
+ base::Bind(&DecodeBufferTest::decode_f6, |
+ base::Unretained(this), p), |
+ &p->f6) && |
+ SlowDecodeField(&b, 1, kF7Offset, |
+ base::Bind(&DecodeBufferTest::decode_f7, |
+ base::Unretained(this), p), |
+ &p->f7) && |
+ SlowDecodeField(&b, 1, kF8Offset, |
+ base::Bind(&DecodeBufferTest::decode_f8, |
+ base::Unretained(this), p), |
+ &p->f8)) { |
+ EXPECT_TRUE(b.Empty()); |
+ EXPECT_EQ(size, input.size()); |
+ EXPECT_EQ(input.size(), b.Offset()); // All input consumed. |
+ return; |
+ } |
+ EXPECT_EQ(old_decode_offset + size, decode_offset_); |
+ EXPECT_TRUE(b.Empty()); |
+ EXPECT_EQ(size, b.Offset()); // All input consumed. |
+ EXPECT_LT(size, input.size()); // More remains. |
+ input = StringPiece(input.data() + size, input.size() - size); |
+ } |
+ ADD_FAILURE() << "Ran out of input! decode_offset_ = " << decode_offset_; |
+ } |
+ |
+ Http2Random random_; |
+ uint32_t decode_offset_; |
+}; |
+ |
+TEST_F(DecodeBufferTest, DecodesFixedInts) { |
+ const char data[] = "\x01\x12\x23\x34\x45\x56\x67\x78\x89\x9a"; |
+ DecodeBuffer b1(data, strlen(data)); |
+ EXPECT_EQ(1, b1.DecodeUInt8()); |
+ EXPECT_EQ(0x1223u, b1.DecodeUInt16()); |
+ EXPECT_EQ(0x344556u, b1.DecodeUInt24()); |
+ EXPECT_EQ(0x6778899Au, b1.DecodeUInt32()); |
+ |
+ DecodeBuffer b2(data, strlen(data)); |
+ uint8_t b; |
+ decode_offset_ = 0; |
+ EXPECT_TRUE(b2.SlowDecodeUInt8(0, &decode_offset_, &b)); |
+ EXPECT_EQ(1, b); |
+ uint16_t s; |
+ decode_offset_ = 0; |
+ EXPECT_TRUE(b2.SlowDecodeUInt16(0, &decode_offset_, &s)); |
+ EXPECT_EQ(0x1223, s); |
+ uint32_t i; |
+ decode_offset_ = 0; |
+ EXPECT_TRUE(b2.SlowDecodeUInt24(0, &decode_offset_, &i)); |
+ // EXPECT_EQ(0x344556, b1.DecodeUInt24()); |
+ // EXPECT_EQ(0x6778899a, b1.DecodeUInt32()); |
+} |
+ |
+// Decode the structure many times, where we'll pass different partitions |
+// into DecodeSlowly. |
+TEST_F(DecodeBufferTest, SlowDecodeTestStruct) { |
+ // clang-format off |
+ const char data[] = { |
+ 0x12u, // f1 |
+ 0x23u, 0x34u, // f2 |
+ 0x45u, 0x56u, 0x67u, // f3 |
+ 0x78u, 0x89u, 0x9au, 0xabu, // f4 |
+ 0xfeu, 0xedu, 0xdcu, 0xcbu, // f5 (high-bit will be cleared.) |
+ 0x00u, 0x0fu, 0x42u, 0x40u, // f6 (kValue1M) |
+ 0x63u, // f7 (kValue99) |
+ 0x81u, // f8 (kMaskLo | kMaskHi) |
+ }; |
+ // clang-format on |
+ StringPiece input(data, sizeof data); |
+ for (int i = 0; i < 200; ++i) { |
+ TestStruct ts; |
+ // Init the struct to random garbage. |
+ ts.f1 = random_.Rand8(); |
+ ts.f2 = random_.Rand16(); |
+ ts.f3 = random_.Rand32(); |
+ ts.f4 = random_.Rand32(); |
+ // Ensure high-bit is set. |
+ ts.f5 = 0x80000000 | random_.Rand32(); // Ensure high-bit is set. |
+ ts.f6 = static_cast<TestEnumClass32>(random_.Rand32()); |
+ ts.f7 = static_cast<TestEnumClass8>(random_.Rand8()); |
+ ts.f8 = static_cast<TestEnum8>(random_.Rand8()); |
+ SlowDecodeTestStruct(input, &ts); |
+ ASSERT_EQ(0x12u, ts.f1); |
+ ASSERT_EQ(0x2334u, ts.f2); |
+ ASSERT_EQ(0x455667u, ts.f3); |
+ ASSERT_EQ(0x78899AABu, ts.f4); |
+ ASSERT_EQ(0x7EEDDCCBu, ts.f5); |
+ ASSERT_EQ(TestEnumClass32::kValue1M, ts.f6); |
+ ASSERT_EQ(TestEnumClass8::kValue99, ts.f7); |
+ ASSERT_EQ(kMaskLo | kMaskHi, ts.f8); |
+ } |
+} |
+ |
+// Make sure that DecodeBuffer is not copying input, just pointing into |
+// provided input buffer. |
+TEST_F(DecodeBufferTest, HasNotCopiedInput) { |
+ const char data[] = "ab"; |
+ DecodeBuffer b1(data, 2); |
+ |
+ EXPECT_EQ(2u, b1.Remaining()); |
+ EXPECT_EQ(0u, b1.Offset()); |
+ EXPECT_FALSE(b1.Empty()); |
+ EXPECT_EQ(data, b1.cursor()); // cursor points to input buffer |
+ EXPECT_TRUE(b1.HasData()); |
+ |
+ b1.AdvanceCursor(1); |
+ |
+ EXPECT_EQ(1u, b1.Remaining()); |
+ EXPECT_EQ(1u, b1.Offset()); |
+ EXPECT_FALSE(b1.Empty()); |
+ EXPECT_EQ(&data[1], b1.cursor()); |
+ EXPECT_TRUE(b1.HasData()); |
+ |
+ b1.AdvanceCursor(1); |
+ |
+ EXPECT_EQ(0u, b1.Remaining()); |
+ EXPECT_EQ(2u, b1.Offset()); |
+ EXPECT_TRUE(b1.Empty()); |
+ EXPECT_EQ(&data[2], b1.cursor()); |
+ EXPECT_FALSE(b1.HasData()); |
+ |
+ DecodeBuffer b2(data, 0); |
+ |
+ EXPECT_EQ(0u, b2.Remaining()); |
+ EXPECT_EQ(0u, b2.Offset()); |
+ EXPECT_TRUE(b2.Empty()); |
+ EXPECT_EQ(data, b2.cursor()); |
+ EXPECT_FALSE(b2.HasData()); |
+} |
+ |
+// DecodeBufferSubset can't go beyond the end of the base buffer. |
+TEST_F(DecodeBufferTest, DecodeBufferSubsetLimited) { |
+ const char data[] = "abc"; |
+ DecodeBuffer base(data, 3); |
+ base.AdvanceCursor(1); |
+ DecodeBufferSubset subset(&base, 100); |
+ EXPECT_EQ(2u, subset.FullSize()); |
+} |
+ |
+// DecodeBufferSubset advances the cursor of its base upon destruction. |
+TEST_F(DecodeBufferTest, DecodeBufferSubsetAdvancesCursor) { |
+ const char data[] = "abc"; |
+ const size_t size = sizeof(data) - 1; |
+ EXPECT_EQ(3u, size); |
+ DecodeBuffer base(data, size); |
+ { |
+ // First no change to the cursor. |
+ DecodeBufferSubset subset(&base, size + 100); |
+ EXPECT_EQ(size, subset.FullSize()); |
+ EXPECT_EQ(base.FullSize(), subset.FullSize()); |
+ EXPECT_EQ(0u, subset.Offset()); |
+ } |
+ EXPECT_EQ(0u, base.Offset()); |
+ EXPECT_EQ(size, base.Remaining()); |
+} |
+ |
+// Make sure that DecodeBuffer ctor complains about bad args. |
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) |
+TEST(DecodeBufferDeathTest, NonNullBufferRequired) { |
+ EXPECT_DEBUG_DEATH({ DecodeBuffer b(nullptr, 3); }, "nullptr"); |
+} |
+ |
+// Make sure that DecodeBuffer ctor complains about bad args. |
+TEST(DecodeBufferDeathTest, ModestBufferSizeRequired) { |
+ EXPECT_DEBUG_DEATH( |
+ { |
+ const char data[] = "abc"; |
+ size_t len = 0; |
+ DecodeBuffer b(data, ~len); |
+ }, |
+ "Max.*Length"); |
+} |
+ |
+// Make sure that DecodeBuffer detects advance beyond end, in debug mode. |
+TEST(DecodeBufferDeathTest, LimitedAdvance) { |
+ { |
+ // Advance right up to end is OK. |
+ const char data[] = "abc"; |
+ DecodeBuffer b(data, 3); |
+ b.AdvanceCursor(3); // OK |
+ EXPECT_TRUE(b.Empty()); |
+ } |
+ EXPECT_DEBUG_DEATH( |
+ { |
+ // Going beyond is not OK. |
+ const char data[] = "abc"; |
+ DecodeBuffer b(data, 3); |
+ b.AdvanceCursor(4); |
+ }, |
+ "4 vs. 3"); |
+} |
+ |
+// Make sure that DecodeBuffer detects decode beyond end, in debug mode. |
+TEST(DecodeBufferDeathTest, DecodeUInt8PastEnd) { |
+ const char data[] = {0x12, 0x23}; |
+ DecodeBuffer b(data, sizeof data); |
+ EXPECT_EQ(2u, b.FullSize()); |
+ EXPECT_EQ(0x1223, b.DecodeUInt16()); |
+ EXPECT_DEBUG_DEATH({ b.DecodeUInt8(); }, "1 vs. 0"); |
+} |
+ |
+// Make sure that DecodeBuffer detects decode beyond end, in debug mode. |
+TEST(DecodeBufferDeathTest, DecodeUInt16OverEnd) { |
+ const char data[] = {0x12, 0x23, 0x34}; |
+ DecodeBuffer b(data, sizeof data); |
+ EXPECT_EQ(3u, b.FullSize()); |
+ EXPECT_EQ(0x1223, b.DecodeUInt16()); |
+ EXPECT_DEBUG_DEATH({ b.DecodeUInt16(); }, "2 vs. 1"); |
+} |
+ |
+// Make sure that DecodeBuffer doesn't agree with having two subsets. |
+TEST(DecodeBufferSubsetDeathTest, TwoSubsets) { |
+ const char data[] = "abc"; |
+ DecodeBuffer base(data, 3); |
+ DecodeBufferSubset subset1(&base, 1); |
+ EXPECT_DEBUG_DEATH({ DecodeBufferSubset subset2(&base, 1); }, |
+ "There is already a subset"); |
+} |
+ |
+// Make sure that DecodeBufferSubset notices when the base's cursor has moved. |
+TEST(DecodeBufferSubsetDeathTest, BaseCursorAdvanced) { |
+ const char data[] = "abc"; |
+ DecodeBuffer base(data, 3); |
+ base.AdvanceCursor(1); |
+ EXPECT_DEBUG_DEATH( |
+ { |
+ DecodeBufferSubset subset1(&base, 2); |
+ base.AdvanceCursor(1); |
+ }, |
+ "Access via subset only when present"); |
+} |
+#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) |
+ |
+} // namespace test |
+} // namespace net |