OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "net/http2/hpack/decoder/hpack_decoder_tables.h" |
| 6 |
| 7 #include <algorithm> |
| 8 #include <string> |
| 9 #include <tuple> |
| 10 #include <vector> |
| 11 |
| 12 #include "base/logging.h" |
| 13 #include "net/http2/tools/failure.h" |
| 14 #include "net/http2/tools/http2_random.h" |
| 15 #include "net/http2/tools/random_util.h" |
| 16 #include "testing/gmock/include/gmock/gmock.h" |
| 17 #include "testing/gtest/include/gtest/gtest.h" |
| 18 |
| 19 using ::testing::AssertionResult; |
| 20 using ::testing::AssertionSuccess; |
| 21 using std::string; |
| 22 |
| 23 namespace net { |
| 24 namespace test { |
| 25 class HpackDecoderTablesPeer { |
| 26 public: |
| 27 static size_t num_dynamic_entries(const HpackDecoderTables& tables) { |
| 28 return tables.dynamic_table_.table_.size(); |
| 29 } |
| 30 }; |
| 31 |
| 32 namespace { |
| 33 struct StaticEntry { |
| 34 const char* name; |
| 35 const char* value; |
| 36 size_t index; |
| 37 }; |
| 38 |
| 39 std::vector<StaticEntry> MakeSpecStaticEntries() { |
| 40 std::vector<StaticEntry> static_entries; |
| 41 |
| 42 #define STATIC_TABLE_ENTRY(name, value, index) \ |
| 43 DCHECK_EQ(static_entries.size() + 1, index); \ |
| 44 static_entries.push_back({name, value, index}); |
| 45 |
| 46 #include "net/http2/hpack/hpack_static_table_entries.inc" |
| 47 |
| 48 #undef STATIC_TABLE_ENTRY |
| 49 |
| 50 return static_entries; |
| 51 } |
| 52 |
| 53 template <class C> |
| 54 void ShuffleCollection(C* collection, RandomBase* r) { |
| 55 std::shuffle(collection->begin(), collection->end(), *r); |
| 56 } |
| 57 |
| 58 class HpackDecoderStaticTableTest : public ::testing::Test { |
| 59 protected: |
| 60 HpackDecoderStaticTableTest() {} |
| 61 |
| 62 std::vector<StaticEntry> shuffled_static_entries() { |
| 63 std::vector<StaticEntry> entries = MakeSpecStaticEntries(); |
| 64 ShuffleCollection(&entries, &random_); |
| 65 return entries; |
| 66 } |
| 67 |
| 68 // This test is in a function so that it can be applied to both the static |
| 69 // table and the combined static+dynamic tables. |
| 70 AssertionResult VerifyStaticTableContents() { |
| 71 for (const auto& expected : shuffled_static_entries()) { |
| 72 const HpackStringPair* found = Lookup(expected.index); |
| 73 VERIFY_NE(found, nullptr); |
| 74 VERIFY_EQ(expected.name, found->name) << expected.index; |
| 75 VERIFY_EQ(expected.value, found->value) << expected.index; |
| 76 } |
| 77 |
| 78 // There should be no entry with index 0. |
| 79 VERIFY_EQ(nullptr, Lookup(0)); |
| 80 return AssertionSuccess(); |
| 81 } |
| 82 |
| 83 virtual const HpackStringPair* Lookup(size_t index) { |
| 84 return static_table_.Lookup(index); |
| 85 } |
| 86 |
| 87 RandomBase* RandomPtr() { return &random_; } |
| 88 |
| 89 private: |
| 90 Http2Random random_; |
| 91 HpackDecoderStaticTable static_table_; |
| 92 }; |
| 93 |
| 94 TEST_F(HpackDecoderStaticTableTest, StaticTableContents) { |
| 95 EXPECT_TRUE(VerifyStaticTableContents()); |
| 96 } |
| 97 |
| 98 size_t Size(const string& name, const string& value) { |
| 99 return name.size() + value.size() + 32; |
| 100 } |
| 101 |
| 102 // To support tests with more than a few of hand crafted changes to the dynamic |
| 103 // table, we have another, exceedingly simple, implementation of the HPACK |
| 104 // dynamic table containing FakeHpackEntry instances. We can thus compare the |
| 105 // contents of the actual table with those in fake_dynamic_table_. |
| 106 |
| 107 typedef std::tuple<string, string, size_t> FakeHpackEntry; |
| 108 const string& Name(const FakeHpackEntry& entry) { |
| 109 return std::get<0>(entry); |
| 110 } |
| 111 const string& Value(const FakeHpackEntry& entry) { |
| 112 return std::get<1>(entry); |
| 113 } |
| 114 size_t Size(const FakeHpackEntry& entry) { |
| 115 return std::get<2>(entry); |
| 116 } |
| 117 |
| 118 class HpackDecoderTablesTest : public HpackDecoderStaticTableTest { |
| 119 protected: |
| 120 const HpackStringPair* Lookup(size_t index) override { |
| 121 return tables_.Lookup(index); |
| 122 } |
| 123 |
| 124 size_t dynamic_size_limit() const { |
| 125 return tables_.header_table_size_limit(); |
| 126 } |
| 127 size_t current_dynamic_size() const { |
| 128 return tables_.current_header_table_size(); |
| 129 } |
| 130 size_t num_dynamic_entries() const { |
| 131 return HpackDecoderTablesPeer::num_dynamic_entries(tables_); |
| 132 } |
| 133 |
| 134 // Insert the name and value into fake_dynamic_table_. |
| 135 void FakeInsert(const string& name, const string& value) { |
| 136 FakeHpackEntry entry(name, value, Size(name, value)); |
| 137 fake_dynamic_table_.insert(fake_dynamic_table_.begin(), entry); |
| 138 } |
| 139 |
| 140 // Add up the size of all entries in fake_dynamic_table_. |
| 141 size_t FakeSize() { |
| 142 size_t sz = 0; |
| 143 for (const auto& entry : fake_dynamic_table_) { |
| 144 sz += Size(entry); |
| 145 } |
| 146 return sz; |
| 147 } |
| 148 |
| 149 // If the total size of the fake_dynamic_table_ is greater than limit, |
| 150 // keep the first N entries such that those N entries have a size not |
| 151 // greater than limit, and such that keeping entry N+1 would have a size |
| 152 // greater than limit. Returns the count of removed bytes. |
| 153 size_t FakeTrim(size_t limit) { |
| 154 size_t original_size = FakeSize(); |
| 155 size_t total_size = 0; |
| 156 for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) { |
| 157 total_size += Size(fake_dynamic_table_[ndx]); |
| 158 if (total_size > limit) { |
| 159 // Need to get rid of ndx and all following entries. |
| 160 fake_dynamic_table_.erase(fake_dynamic_table_.begin() + ndx, |
| 161 fake_dynamic_table_.end()); |
| 162 return original_size - FakeSize(); |
| 163 } |
| 164 } |
| 165 return 0; |
| 166 } |
| 167 |
| 168 // Verify that the contents of the actual dynamic table match those in |
| 169 // fake_dynamic_table_. |
| 170 AssertionResult VerifyDynamicTableContents() { |
| 171 VERIFY_EQ(current_dynamic_size(), FakeSize()); |
| 172 VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size()); |
| 173 |
| 174 for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) { |
| 175 const HpackStringPair* found = Lookup(ndx + kFirstDynamicTableIndex); |
| 176 VERIFY_NE(found, nullptr); |
| 177 |
| 178 const auto& expected = fake_dynamic_table_[ndx]; |
| 179 VERIFY_EQ(Name(expected), found->name); |
| 180 VERIFY_EQ(Value(expected), found->value); |
| 181 } |
| 182 |
| 183 // Make sure there are no more entries. |
| 184 VERIFY_EQ(nullptr, |
| 185 Lookup(fake_dynamic_table_.size() + kFirstDynamicTableIndex)); |
| 186 return AssertionSuccess(); |
| 187 } |
| 188 |
| 189 // Apply an update to the limit on the maximum size of the dynamic table. |
| 190 AssertionResult DynamicTableSizeUpdate(size_t size_limit) { |
| 191 VERIFY_EQ(current_dynamic_size(), FakeSize()); |
| 192 if (size_limit < current_dynamic_size()) { |
| 193 // Will need to trim the dynamic table's oldest entries. |
| 194 tables_.DynamicTableSizeUpdate(size_limit); |
| 195 FakeTrim(size_limit); |
| 196 return VerifyDynamicTableContents(); |
| 197 } |
| 198 // Shouldn't change the size. |
| 199 tables_.DynamicTableSizeUpdate(size_limit); |
| 200 return VerifyDynamicTableContents(); |
| 201 } |
| 202 |
| 203 // Insert an entry into the dynamic table, confirming that trimming of entries |
| 204 // occurs if the total size is greater than the limit, and that older entries |
| 205 // move up by 1 index. |
| 206 AssertionResult Insert(const string& name, const string& value) { |
| 207 size_t old_count = num_dynamic_entries(); |
| 208 if (tables_.Insert(HpackString(name), HpackString(value))) { |
| 209 VERIFY_GT(current_dynamic_size(), 0u); |
| 210 VERIFY_GT(num_dynamic_entries(), 0u); |
| 211 } else { |
| 212 VERIFY_EQ(current_dynamic_size(), 0u); |
| 213 VERIFY_EQ(num_dynamic_entries(), 0u); |
| 214 } |
| 215 FakeInsert(name, value); |
| 216 VERIFY_EQ(old_count + 1, fake_dynamic_table_.size()); |
| 217 FakeTrim(dynamic_size_limit()); |
| 218 VERIFY_EQ(current_dynamic_size(), FakeSize()); |
| 219 VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size()); |
| 220 return VerifyDynamicTableContents(); |
| 221 } |
| 222 |
| 223 private: |
| 224 HpackDecoderTables tables_; |
| 225 |
| 226 std::vector<FakeHpackEntry> fake_dynamic_table_; |
| 227 }; |
| 228 |
| 229 TEST_F(HpackDecoderTablesTest, StaticTableContents) { |
| 230 EXPECT_TRUE(VerifyStaticTableContents()); |
| 231 } |
| 232 |
| 233 // Generate a bunch of random header entries, insert them, and confirm they |
| 234 // present, as required by the RFC, using VerifyDynamicTableContents above on |
| 235 // each Insert. Also apply various resizings of the dynamic table. |
| 236 TEST_F(HpackDecoderTablesTest, RandomDynamicTable) { |
| 237 EXPECT_EQ(0u, current_dynamic_size()); |
| 238 EXPECT_TRUE(VerifyStaticTableContents()); |
| 239 EXPECT_TRUE(VerifyDynamicTableContents()); |
| 240 |
| 241 std::vector<size_t> table_sizes; |
| 242 table_sizes.push_back(dynamic_size_limit()); |
| 243 table_sizes.push_back(0); |
| 244 table_sizes.push_back(dynamic_size_limit() / 2); |
| 245 table_sizes.push_back(dynamic_size_limit()); |
| 246 table_sizes.push_back(dynamic_size_limit() / 2); |
| 247 table_sizes.push_back(0); |
| 248 table_sizes.push_back(dynamic_size_limit()); |
| 249 |
| 250 for (size_t limit : table_sizes) { |
| 251 ASSERT_TRUE(DynamicTableSizeUpdate(limit)); |
| 252 for (int insert_count = 0; insert_count < 100; ++insert_count) { |
| 253 string name = GenerateHttp2HeaderName( |
| 254 GenerateUniformInRange(2, 40, RandomPtr()), RandomPtr()); |
| 255 string value = GenerateWebSafeString( |
| 256 GenerateUniformInRange(2, 600, RandomPtr()), RandomPtr()); |
| 257 ASSERT_TRUE(Insert(name, value)); |
| 258 } |
| 259 EXPECT_TRUE(VerifyStaticTableContents()); |
| 260 } |
| 261 } |
| 262 |
| 263 } // namespace |
| 264 } // namespace test |
| 265 } // namespace net |
OLD | NEW |