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