blob: 7d1044bbef0f79fc639e12e78c1da27857e4c877 [file] [log] [blame]
QUICHE teama6ef0a62019-03-07 20:34:33 -05001// Copyright (c) 2018 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/third_party/quiche/src/quic/core/qpack/qpack_header_table.h"
6
QUICHE teama6ef0a62019-03-07 20:34:33 -05007#include "net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h"
8#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
9#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
10#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
11
bnc7a1c21c2019-07-08 19:09:50 -070012using ::testing::Mock;
13using ::testing::StrictMock;
14
QUICHE teama6ef0a62019-03-07 20:34:33 -050015namespace quic {
16namespace test {
17namespace {
18
19const uint64_t kMaximumDynamicTableCapacityForTesting = 1024 * 1024;
20
bnc7a1c21c2019-07-08 19:09:50 -070021class MockObserver : public QpackHeaderTable::Observer {
22 public:
23 ~MockObserver() override = default;
24
25 MOCK_METHOD0(OnInsertCountReachedThreshold, void());
26};
27
QUICHE teama6ef0a62019-03-07 20:34:33 -050028class QpackHeaderTableTest : public QuicTest {
29 protected:
30 QpackHeaderTableTest() {
31 table_.SetMaximumDynamicTableCapacity(
32 kMaximumDynamicTableCapacityForTesting);
33 }
34 ~QpackHeaderTableTest() override = default;
35
36 void ExpectEntryAtIndex(bool is_static,
37 uint64_t index,
38 QuicStringPiece expected_name,
39 QuicStringPiece expected_value) const {
40 const auto* entry = table_.LookupEntry(is_static, index);
41 ASSERT_TRUE(entry);
42 EXPECT_EQ(expected_name, entry->name());
43 EXPECT_EQ(expected_value, entry->value());
44 }
45
46 void ExpectNoEntryAtIndex(bool is_static, uint64_t index) const {
47 EXPECT_FALSE(table_.LookupEntry(is_static, index));
48 }
49
50 void ExpectMatch(QuicStringPiece name,
51 QuicStringPiece value,
52 QpackHeaderTable::MatchType expected_match_type,
53 bool expected_is_static,
54 uint64_t expected_index) const {
55 // Initialize outparams to a value different from the expected to ensure
56 // that FindHeaderField() sets them.
57 bool is_static = !expected_is_static;
58 uint64_t index = expected_index + 1;
59
60 QpackHeaderTable::MatchType matchtype =
61 table_.FindHeaderField(name, value, &is_static, &index);
62
63 EXPECT_EQ(expected_match_type, matchtype) << name << ": " << value;
64 EXPECT_EQ(expected_is_static, is_static) << name << ": " << value;
65 EXPECT_EQ(expected_index, index) << name << ": " << value;
66 }
67
68 void ExpectNoMatch(QuicStringPiece name, QuicStringPiece value) const {
69 bool is_static = false;
70 uint64_t index = 0;
71
72 QpackHeaderTable::MatchType matchtype =
73 table_.FindHeaderField(name, value, &is_static, &index);
74
75 EXPECT_EQ(QpackHeaderTable::MatchType::kNoMatch, matchtype)
76 << name << ": " << value;
77 }
78
79 void InsertEntry(QuicStringPiece name, QuicStringPiece value) {
80 EXPECT_TRUE(table_.InsertEntry(name, value));
81 }
82
83 void ExpectToFailInsertingEntry(QuicStringPiece name, QuicStringPiece value) {
84 EXPECT_FALSE(table_.InsertEntry(name, value));
85 }
86
87 bool SetDynamicTableCapacity(uint64_t capacity) {
88 return table_.SetDynamicTableCapacity(capacity);
89 }
90
bnc7a1c21c2019-07-08 19:09:50 -070091 void RegisterObserver(QpackHeaderTable::Observer* observer,
92 uint64_t required_insert_count) {
93 table_.RegisterObserver(observer, required_insert_count);
94 }
95
QUICHE teama6ef0a62019-03-07 20:34:33 -050096 uint64_t max_entries() const { return table_.max_entries(); }
97 uint64_t inserted_entry_count() const {
98 return table_.inserted_entry_count();
99 }
100 uint64_t dropped_entry_count() const { return table_.dropped_entry_count(); }
101
102 private:
103 QpackHeaderTable table_;
104};
105
106TEST_F(QpackHeaderTableTest, LookupStaticEntry) {
107 ExpectEntryAtIndex(/* is_static = */ true, 0, ":authority", "");
108
109 ExpectEntryAtIndex(/* is_static = */ true, 1, ":path", "/");
110
111 // 98 is the last entry.
112 ExpectEntryAtIndex(/* is_static = */ true, 98, "x-frame-options",
113 "sameorigin");
114
115 ASSERT_EQ(99u, QpackStaticTableVector().size());
116 ExpectNoEntryAtIndex(/* is_static = */ true, 99);
117}
118
119TEST_F(QpackHeaderTableTest, InsertAndLookupDynamicEntry) {
120 // Dynamic table is initially entry.
121 ExpectNoEntryAtIndex(/* is_static = */ false, 0);
122 ExpectNoEntryAtIndex(/* is_static = */ false, 1);
123 ExpectNoEntryAtIndex(/* is_static = */ false, 2);
124 ExpectNoEntryAtIndex(/* is_static = */ false, 3);
125
126 // Insert one entry.
127 InsertEntry("foo", "bar");
128
129 ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar");
130
131 ExpectNoEntryAtIndex(/* is_static = */ false, 1);
132 ExpectNoEntryAtIndex(/* is_static = */ false, 2);
133 ExpectNoEntryAtIndex(/* is_static = */ false, 3);
134
135 // Insert a different entry.
136 InsertEntry("baz", "bing");
137
138 ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar");
139
140 ExpectEntryAtIndex(/* is_static = */ false, 1, "baz", "bing");
141
142 ExpectNoEntryAtIndex(/* is_static = */ false, 2);
143 ExpectNoEntryAtIndex(/* is_static = */ false, 3);
144
145 // Insert an entry identical to the most recently inserted one.
146 InsertEntry("baz", "bing");
147
148 ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar");
149
150 ExpectEntryAtIndex(/* is_static = */ false, 1, "baz", "bing");
151
152 ExpectEntryAtIndex(/* is_static = */ false, 2, "baz", "bing");
153
154 ExpectNoEntryAtIndex(/* is_static = */ false, 3);
155}
156
157TEST_F(QpackHeaderTableTest, FindStaticHeaderField) {
158 // A header name that has multiple entries with different values.
159 ExpectMatch(":method", "GET", QpackHeaderTable::MatchType::kNameAndValue,
160 true, 17u);
161
162 ExpectMatch(":method", "POST", QpackHeaderTable::MatchType::kNameAndValue,
163 true, 20u);
164
165 ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kName, true,
166 15u);
167
168 // A header name that has a single entry with non-empty value.
169 ExpectMatch("accept-encoding", "gzip, deflate, br",
170 QpackHeaderTable::MatchType::kNameAndValue, true, 31u);
171
172 ExpectMatch("accept-encoding", "compress", QpackHeaderTable::MatchType::kName,
173 true, 31u);
174
175 ExpectMatch("accept-encoding", "", QpackHeaderTable::MatchType::kName, true,
176 31u);
177
178 // A header name that has a single entry with empty value.
179 ExpectMatch("location", "", QpackHeaderTable::MatchType::kNameAndValue, true,
180 12u);
181
182 ExpectMatch("location", "foo", QpackHeaderTable::MatchType::kName, true, 12u);
183
184 // No matching header name.
185 ExpectNoMatch("foo", "");
186 ExpectNoMatch("foo", "bar");
187}
188
189TEST_F(QpackHeaderTableTest, FindDynamicHeaderField) {
190 // Dynamic table is initially entry.
191 ExpectNoMatch("foo", "bar");
192 ExpectNoMatch("foo", "baz");
193
194 // Insert one entry.
195 InsertEntry("foo", "bar");
196
197 // Match name and value.
198 ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, false,
199 0u);
200
201 // Match name only.
202 ExpectMatch("foo", "baz", QpackHeaderTable::MatchType::kName, false, 0u);
203
204 // Insert an identical entry. FindHeaderField() should return the index of
205 // the most recently inserted matching entry.
206 InsertEntry("foo", "bar");
207
208 // Match name and value.
209 ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, false,
210 1u);
211
212 // Match name only.
213 ExpectMatch("foo", "baz", QpackHeaderTable::MatchType::kName, false, 1u);
214}
215
216TEST_F(QpackHeaderTableTest, FindHeaderFieldPrefersStaticTable) {
217 // Insert an entry to the dynamic table that exists in the static table.
218 InsertEntry(":method", "GET");
219
220 // Insertion works.
221 ExpectEntryAtIndex(/* is_static = */ false, 0, ":method", "GET");
222
223 // FindHeaderField() prefers static table if both have name-and-value match.
224 ExpectMatch(":method", "GET", QpackHeaderTable::MatchType::kNameAndValue,
225 true, 17u);
226
227 // FindHeaderField() prefers static table if both have name match but no value
228 // match, and prefers the first entry with matching name.
229 ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kName, true,
230 15u);
231
232 // Add new entry to the dynamic table.
233 InsertEntry(":method", "TRACE");
234
235 // FindHeaderField prefers name-and-value match in dynamic table over name
236 // only match in static table.
237 ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kNameAndValue,
238 false, 1u);
239}
240
241// MaxEntries is determined by maximum dynamic table capacity,
242// which is set at construction time.
243TEST_F(QpackHeaderTableTest, MaxEntries) {
244 QpackHeaderTable table1;
245 table1.SetMaximumDynamicTableCapacity(1024);
246 EXPECT_EQ(32u, table1.max_entries());
247
248 QpackHeaderTable table2;
249 table2.SetMaximumDynamicTableCapacity(500);
250 EXPECT_EQ(15u, table2.max_entries());
251}
252
253TEST_F(QpackHeaderTableTest, SetDynamicTableCapacity) {
254 // Dynamic table capacity does not affect MaxEntries.
255 EXPECT_TRUE(SetDynamicTableCapacity(1024));
256 EXPECT_EQ(32u * 1024, max_entries());
257
258 EXPECT_TRUE(SetDynamicTableCapacity(500));
259 EXPECT_EQ(32u * 1024, max_entries());
260
261 // Dynamic table capacity cannot exceed maximum dynamic table capacity.
262 EXPECT_FALSE(
263 SetDynamicTableCapacity(2 * kMaximumDynamicTableCapacityForTesting));
264}
265
266TEST_F(QpackHeaderTableTest, EvictByInsertion) {
267 EXPECT_TRUE(SetDynamicTableCapacity(40));
268
269 // Entry size is 3 + 3 + 32 = 38.
270 InsertEntry("foo", "bar");
271 EXPECT_EQ(1u, inserted_entry_count());
272 EXPECT_EQ(0u, dropped_entry_count());
273
274 ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
275 /* expected_is_static = */ false, 0u);
276
277 // Inserting second entry evicts the first one.
278 InsertEntry("baz", "qux");
279 EXPECT_EQ(2u, inserted_entry_count());
280 EXPECT_EQ(1u, dropped_entry_count());
281
282 ExpectNoMatch("foo", "bar");
283 ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
284 /* expected_is_static = */ false, 1u);
285
286 // Inserting an entry that does not fit results in error.
287 ExpectToFailInsertingEntry("foobar", "foobar");
288}
289
290TEST_F(QpackHeaderTableTest, EvictByUpdateTableSize) {
291 // Entry size is 3 + 3 + 32 = 38.
292 InsertEntry("foo", "bar");
293 InsertEntry("baz", "qux");
294 EXPECT_EQ(2u, inserted_entry_count());
295 EXPECT_EQ(0u, dropped_entry_count());
296
297 ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
298 /* expected_is_static = */ false, 0u);
299 ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
300 /* expected_is_static = */ false, 1u);
301
302 EXPECT_TRUE(SetDynamicTableCapacity(40));
303 EXPECT_EQ(2u, inserted_entry_count());
304 EXPECT_EQ(1u, dropped_entry_count());
305
306 ExpectNoMatch("foo", "bar");
307 ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
308 /* expected_is_static = */ false, 1u);
309
310 EXPECT_TRUE(SetDynamicTableCapacity(20));
311 EXPECT_EQ(2u, inserted_entry_count());
312 EXPECT_EQ(2u, dropped_entry_count());
313
314 ExpectNoMatch("foo", "bar");
315 ExpectNoMatch("baz", "qux");
316}
317
318TEST_F(QpackHeaderTableTest, EvictOldestOfIdentical) {
319 EXPECT_TRUE(SetDynamicTableCapacity(80));
320
321 // Entry size is 3 + 3 + 32 = 38.
322 // Insert same entry twice.
323 InsertEntry("foo", "bar");
324 InsertEntry("foo", "bar");
325 EXPECT_EQ(2u, inserted_entry_count());
326 EXPECT_EQ(0u, dropped_entry_count());
327
328 // Find most recently inserted entry.
329 ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
330 /* expected_is_static = */ false, 1u);
331
332 // Inserting third entry evicts the first one, not the second.
333 InsertEntry("baz", "qux");
334 EXPECT_EQ(3u, inserted_entry_count());
335 EXPECT_EQ(1u, dropped_entry_count());
336
337 ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
338 /* expected_is_static = */ false, 1u);
339 ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
340 /* expected_is_static = */ false, 2u);
341}
342
343TEST_F(QpackHeaderTableTest, EvictOldestOfSameName) {
344 EXPECT_TRUE(SetDynamicTableCapacity(80));
345
346 // Entry size is 3 + 3 + 32 = 38.
347 // Insert two entries with same name but different values.
348 InsertEntry("foo", "bar");
349 InsertEntry("foo", "baz");
350 EXPECT_EQ(2u, inserted_entry_count());
351 EXPECT_EQ(0u, dropped_entry_count());
352
353 // Find most recently inserted entry with matching name.
354 ExpectMatch("foo", "foo", QpackHeaderTable::MatchType::kName,
355 /* expected_is_static = */ false, 1u);
356
357 // Inserting third entry evicts the first one, not the second.
358 InsertEntry("baz", "qux");
359 EXPECT_EQ(3u, inserted_entry_count());
360 EXPECT_EQ(1u, dropped_entry_count());
361
362 ExpectMatch("foo", "foo", QpackHeaderTable::MatchType::kName,
363 /* expected_is_static = */ false, 1u);
364 ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
365 /* expected_is_static = */ false, 2u);
366}
367
bnc7a1c21c2019-07-08 19:09:50 -0700368TEST_F(QpackHeaderTableTest, Observer) {
369 StrictMock<MockObserver> observer1;
370 RegisterObserver(&observer1, 1);
371 EXPECT_CALL(observer1, OnInsertCountReachedThreshold);
372 InsertEntry("foo", "bar");
373 EXPECT_EQ(1u, inserted_entry_count());
374 Mock::VerifyAndClearExpectations(&observer1);
375
376 // Registration order does not matter.
377 StrictMock<MockObserver> observer2;
378 StrictMock<MockObserver> observer3;
379 RegisterObserver(&observer3, 3);
380 RegisterObserver(&observer2, 2);
381
382 EXPECT_CALL(observer2, OnInsertCountReachedThreshold);
383 InsertEntry("foo", "bar");
384 EXPECT_EQ(2u, inserted_entry_count());
385 Mock::VerifyAndClearExpectations(&observer3);
386
387 EXPECT_CALL(observer3, OnInsertCountReachedThreshold);
388 InsertEntry("foo", "bar");
389 EXPECT_EQ(3u, inserted_entry_count());
390 Mock::VerifyAndClearExpectations(&observer2);
391
392 // Multiple observers with identical |required_insert_count| should all be
393 // notified.
394 StrictMock<MockObserver> observer4;
395 StrictMock<MockObserver> observer5;
396 RegisterObserver(&observer4, 4);
397 RegisterObserver(&observer5, 4);
398
399 EXPECT_CALL(observer4, OnInsertCountReachedThreshold);
400 EXPECT_CALL(observer5, OnInsertCountReachedThreshold);
401 InsertEntry("foo", "bar");
402 EXPECT_EQ(4u, inserted_entry_count());
403 Mock::VerifyAndClearExpectations(&observer4);
404 Mock::VerifyAndClearExpectations(&observer5);
405}
406
bncb34a7ec2019-07-25 08:53:44 -0700407TEST_F(QpackHeaderTableTest, DrainingIndex) {
408 QpackHeaderTable table;
409 table.SetMaximumDynamicTableCapacity(4 * QpackEntry::Size("foo", "bar"));
410
411 // Empty table: no draining entry.
412 EXPECT_EQ(0u, table.draining_index(0.0));
413 EXPECT_EQ(0u, table.draining_index(1.0));
414
415 // Table with one entry.
416 EXPECT_TRUE(table.InsertEntry("foo", "bar"));
417 // Any entry can be referenced if none of the table is draining.
418 EXPECT_EQ(0u, table.draining_index(0.0));
419 // No entry can be referenced if all of the table is draining.
420 EXPECT_EQ(1u, table.draining_index(1.0));
421
422 // Table with two entries is at half capacity.
423 EXPECT_TRUE(table.InsertEntry("foo", "bar"));
424 // Any entry can be referenced if at most half of the table is draining,
425 // because current entries only take up half of total capacity.
426 EXPECT_EQ(0u, table.draining_index(0.0));
427 EXPECT_EQ(0u, table.draining_index(0.5));
428 // No entry can be referenced if all of the table is draining.
429 EXPECT_EQ(2u, table.draining_index(1.0));
430
431 // Table with four entries is full.
432 EXPECT_TRUE(table.InsertEntry("foo", "bar"));
433 EXPECT_TRUE(table.InsertEntry("foo", "bar"));
434 // Any entry can be referenced if none of the table is draining.
435 EXPECT_EQ(0u, table.draining_index(0.0));
436 // In a full table with identically sized entries, |draining_fraction| of all
437 // entries are draining.
438 EXPECT_EQ(2u, table.draining_index(0.5));
439 // No entry can be referenced if all of the table is draining.
440 EXPECT_EQ(4u, table.draining_index(1.0));
441}
442
QUICHE teama6ef0a62019-03-07 20:34:33 -0500443} // namespace
444} // namespace test
445} // namespace quic