Index: net/http2/hpack/decoder/hpack_decoder_tables_test.cc |
diff --git a/net/http2/hpack/decoder/hpack_decoder_tables_test.cc b/net/http2/hpack/decoder/hpack_decoder_tables_test.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..13bdb6f0a96d670ee2b75c3d39e70f2fed76e06f |
--- /dev/null |
+++ b/net/http2/hpack/decoder/hpack_decoder_tables_test.cc |
@@ -0,0 +1,265 @@ |
+// 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/hpack/decoder/hpack_decoder_tables.h" |
+ |
+#include <algorithm> |
+#include <string> |
+#include <tuple> |
+#include <vector> |
+ |
+#include "base/logging.h" |
+#include "net/http2/tools/failure.h" |
+#include "net/http2/tools/http2_random.h" |
+#include "net/http2/tools/random_util.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using ::testing::AssertionResult; |
+using ::testing::AssertionSuccess; |
+using std::string; |
+ |
+namespace net { |
+namespace test { |
+class HpackDecoderTablesPeer { |
+ public: |
+ static size_t num_dynamic_entries(const HpackDecoderTables& tables) { |
+ return tables.dynamic_table_.table_.size(); |
+ } |
+}; |
+ |
+namespace { |
+struct StaticEntry { |
+ const char* name; |
+ const char* value; |
+ size_t index; |
+}; |
+ |
+std::vector<StaticEntry> MakeSpecStaticEntries() { |
+ std::vector<StaticEntry> static_entries; |
+ |
+#define STATIC_TABLE_ENTRY(name, value, index) \ |
+ DCHECK_EQ(static_entries.size() + 1, index); \ |
+ static_entries.push_back({name, value, index}); |
+ |
+#include "net/http2/hpack/hpack_static_table_entries.inc" |
+ |
+#undef STATIC_TABLE_ENTRY |
+ |
+ return static_entries; |
+} |
+ |
+template <class C> |
+void ShuffleCollection(C* collection, RandomBase* r) { |
+ std::shuffle(collection->begin(), collection->end(), *r); |
+} |
+ |
+class HpackDecoderStaticTableTest : public ::testing::Test { |
+ protected: |
+ HpackDecoderStaticTableTest() {} |
+ |
+ std::vector<StaticEntry> shuffled_static_entries() { |
+ std::vector<StaticEntry> entries = MakeSpecStaticEntries(); |
+ ShuffleCollection(&entries, &random_); |
+ return entries; |
+ } |
+ |
+ // This test is in a function so that it can be applied to both the static |
+ // table and the combined static+dynamic tables. |
+ AssertionResult VerifyStaticTableContents() { |
+ for (const auto& expected : shuffled_static_entries()) { |
+ const HpackStringPair* found = Lookup(expected.index); |
+ VERIFY_NE(found, nullptr); |
+ VERIFY_EQ(expected.name, found->name) << expected.index; |
+ VERIFY_EQ(expected.value, found->value) << expected.index; |
+ } |
+ |
+ // There should be no entry with index 0. |
+ VERIFY_EQ(nullptr, Lookup(0)); |
+ return AssertionSuccess(); |
+ } |
+ |
+ virtual const HpackStringPair* Lookup(size_t index) { |
+ return static_table_.Lookup(index); |
+ } |
+ |
+ RandomBase* RandomPtr() { return &random_; } |
+ |
+ private: |
+ Http2Random random_; |
+ HpackDecoderStaticTable static_table_; |
+}; |
+ |
+TEST_F(HpackDecoderStaticTableTest, StaticTableContents) { |
+ EXPECT_TRUE(VerifyStaticTableContents()); |
+} |
+ |
+size_t Size(const string& name, const string& value) { |
+ return name.size() + value.size() + 32; |
+} |
+ |
+// To support tests with more than a few of hand crafted changes to the dynamic |
+// table, we have another, exceedingly simple, implementation of the HPACK |
+// dynamic table containing FakeHpackEntry instances. We can thus compare the |
+// contents of the actual table with those in fake_dynamic_table_. |
+ |
+typedef std::tuple<string, string, size_t> FakeHpackEntry; |
+const string& Name(const FakeHpackEntry& entry) { |
+ return std::get<0>(entry); |
+} |
+const string& Value(const FakeHpackEntry& entry) { |
+ return std::get<1>(entry); |
+} |
+size_t Size(const FakeHpackEntry& entry) { |
+ return std::get<2>(entry); |
+} |
+ |
+class HpackDecoderTablesTest : public HpackDecoderStaticTableTest { |
+ protected: |
+ const HpackStringPair* Lookup(size_t index) override { |
+ return tables_.Lookup(index); |
+ } |
+ |
+ size_t dynamic_size_limit() const { |
+ return tables_.header_table_size_limit(); |
+ } |
+ size_t current_dynamic_size() const { |
+ return tables_.current_header_table_size(); |
+ } |
+ size_t num_dynamic_entries() const { |
+ return HpackDecoderTablesPeer::num_dynamic_entries(tables_); |
+ } |
+ |
+ // Insert the name and value into fake_dynamic_table_. |
+ void FakeInsert(const string& name, const string& value) { |
+ FakeHpackEntry entry(name, value, Size(name, value)); |
+ fake_dynamic_table_.insert(fake_dynamic_table_.begin(), entry); |
+ } |
+ |
+ // Add up the size of all entries in fake_dynamic_table_. |
+ size_t FakeSize() { |
+ size_t sz = 0; |
+ for (const auto& entry : fake_dynamic_table_) { |
+ sz += Size(entry); |
+ } |
+ return sz; |
+ } |
+ |
+ // If the total size of the fake_dynamic_table_ is greater than limit, |
+ // keep the first N entries such that those N entries have a size not |
+ // greater than limit, and such that keeping entry N+1 would have a size |
+ // greater than limit. Returns the count of removed bytes. |
+ size_t FakeTrim(size_t limit) { |
+ size_t original_size = FakeSize(); |
+ size_t total_size = 0; |
+ for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) { |
+ total_size += Size(fake_dynamic_table_[ndx]); |
+ if (total_size > limit) { |
+ // Need to get rid of ndx and all following entries. |
+ fake_dynamic_table_.erase(fake_dynamic_table_.begin() + ndx, |
+ fake_dynamic_table_.end()); |
+ return original_size - FakeSize(); |
+ } |
+ } |
+ return 0; |
+ } |
+ |
+ // Verify that the contents of the actual dynamic table match those in |
+ // fake_dynamic_table_. |
+ AssertionResult VerifyDynamicTableContents() { |
+ VERIFY_EQ(current_dynamic_size(), FakeSize()); |
+ VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size()); |
+ |
+ for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) { |
+ const HpackStringPair* found = Lookup(ndx + kFirstDynamicTableIndex); |
+ VERIFY_NE(found, nullptr); |
+ |
+ const auto& expected = fake_dynamic_table_[ndx]; |
+ VERIFY_EQ(Name(expected), found->name); |
+ VERIFY_EQ(Value(expected), found->value); |
+ } |
+ |
+ // Make sure there are no more entries. |
+ VERIFY_EQ(nullptr, |
+ Lookup(fake_dynamic_table_.size() + kFirstDynamicTableIndex)); |
+ return AssertionSuccess(); |
+ } |
+ |
+ // Apply an update to the limit on the maximum size of the dynamic table. |
+ AssertionResult DynamicTableSizeUpdate(size_t size_limit) { |
+ VERIFY_EQ(current_dynamic_size(), FakeSize()); |
+ if (size_limit < current_dynamic_size()) { |
+ // Will need to trim the dynamic table's oldest entries. |
+ tables_.DynamicTableSizeUpdate(size_limit); |
+ FakeTrim(size_limit); |
+ return VerifyDynamicTableContents(); |
+ } |
+ // Shouldn't change the size. |
+ tables_.DynamicTableSizeUpdate(size_limit); |
+ return VerifyDynamicTableContents(); |
+ } |
+ |
+ // Insert an entry into the dynamic table, confirming that trimming of entries |
+ // occurs if the total size is greater than the limit, and that older entries |
+ // move up by 1 index. |
+ AssertionResult Insert(const string& name, const string& value) { |
+ size_t old_count = num_dynamic_entries(); |
+ if (tables_.Insert(HpackString(name), HpackString(value))) { |
+ VERIFY_GT(current_dynamic_size(), 0u); |
+ VERIFY_GT(num_dynamic_entries(), 0u); |
+ } else { |
+ VERIFY_EQ(current_dynamic_size(), 0u); |
+ VERIFY_EQ(num_dynamic_entries(), 0u); |
+ } |
+ FakeInsert(name, value); |
+ VERIFY_EQ(old_count + 1, fake_dynamic_table_.size()); |
+ FakeTrim(dynamic_size_limit()); |
+ VERIFY_EQ(current_dynamic_size(), FakeSize()); |
+ VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size()); |
+ return VerifyDynamicTableContents(); |
+ } |
+ |
+ private: |
+ HpackDecoderTables tables_; |
+ |
+ std::vector<FakeHpackEntry> fake_dynamic_table_; |
+}; |
+ |
+TEST_F(HpackDecoderTablesTest, StaticTableContents) { |
+ EXPECT_TRUE(VerifyStaticTableContents()); |
+} |
+ |
+// Generate a bunch of random header entries, insert them, and confirm they |
+// present, as required by the RFC, using VerifyDynamicTableContents above on |
+// each Insert. Also apply various resizings of the dynamic table. |
+TEST_F(HpackDecoderTablesTest, RandomDynamicTable) { |
+ EXPECT_EQ(0u, current_dynamic_size()); |
+ EXPECT_TRUE(VerifyStaticTableContents()); |
+ EXPECT_TRUE(VerifyDynamicTableContents()); |
+ |
+ std::vector<size_t> table_sizes; |
+ table_sizes.push_back(dynamic_size_limit()); |
+ table_sizes.push_back(0); |
+ table_sizes.push_back(dynamic_size_limit() / 2); |
+ table_sizes.push_back(dynamic_size_limit()); |
+ table_sizes.push_back(dynamic_size_limit() / 2); |
+ table_sizes.push_back(0); |
+ table_sizes.push_back(dynamic_size_limit()); |
+ |
+ for (size_t limit : table_sizes) { |
+ ASSERT_TRUE(DynamicTableSizeUpdate(limit)); |
+ for (int insert_count = 0; insert_count < 100; ++insert_count) { |
+ string name = GenerateHttp2HeaderName( |
+ GenerateUniformInRange(2, 40, RandomPtr()), RandomPtr()); |
+ string value = GenerateWebSafeString( |
+ GenerateUniformInRange(2, 600, RandomPtr()), RandomPtr()); |
+ ASSERT_TRUE(Insert(name, value)); |
+ } |
+ EXPECT_TRUE(VerifyStaticTableContents()); |
+ } |
+} |
+ |
+} // namespace |
+} // namespace test |
+} // namespace net |