blob: 2ac0a5f8db4208b9ef202c4ac1bd05d0acdf6c58 [file] [log] [blame]
QUICHE team82dee2f2019-01-18 12:35:12 -05001// Copyright 2014 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/spdy/core/hpack/hpack_encoder.h"
6
7#include <cstdint>
8#include <map>
9
QUICHE team82dee2f2019-01-18 12:35:12 -050010#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
QUICHE teamf3c80c92020-02-12 09:47:55 -080011#include "net/third_party/quiche/src/common/platform/api/quiche_test.h"
QUICHE team82dee2f2019-01-18 12:35:12 -050012#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
QUICHE teamb6a87632019-12-10 10:18:13 -080013#include "net/third_party/quiche/src/spdy/core/spdy_simple_arena.h"
QUICHE team82dee2f2019-01-18 12:35:12 -050014
15namespace spdy {
16
17namespace test {
18
19class HpackHeaderTablePeer {
20 public:
21 explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {}
22
23 HpackHeaderTable::EntryTable* dynamic_entries() {
24 return &table_->dynamic_entries_;
25 }
26
27 private:
28 HpackHeaderTable* table_;
29};
30
31class HpackEncoderPeer {
32 public:
33 typedef HpackEncoder::Representation Representation;
34 typedef HpackEncoder::Representations Representations;
35
36 explicit HpackEncoderPeer(HpackEncoder* encoder) : encoder_(encoder) {}
37
38 bool compression_enabled() const { return encoder_->enable_compression_; }
39 HpackHeaderTable* table() { return &encoder_->header_table_; }
40 HpackHeaderTablePeer table_peer() { return HpackHeaderTablePeer(table()); }
41 const HpackHuffmanTable& huffman_table() const {
42 return encoder_->huffman_table_;
43 }
bnc7f82d042020-01-03 12:18:53 -080044 void EmitString(quiche::QuicheStringPiece str) { encoder_->EmitString(str); }
bnc44712912019-08-15 18:58:14 -070045 void TakeString(std::string* out) {
46 encoder_->output_stream_.TakeString(out);
47 }
bnc7f82d042020-01-03 12:18:53 -080048 static void CookieToCrumbs(quiche::QuicheStringPiece cookie,
49 std::vector<quiche::QuicheStringPiece>* out) {
QUICHE team82dee2f2019-01-18 12:35:12 -050050 Representations tmp;
51 HpackEncoder::CookieToCrumbs(std::make_pair("", cookie), &tmp);
52
53 out->clear();
54 for (size_t i = 0; i != tmp.size(); ++i) {
55 out->push_back(tmp[i].second);
56 }
57 }
bnc7f82d042020-01-03 12:18:53 -080058 static void DecomposeRepresentation(
59 quiche::QuicheStringPiece value,
60 std::vector<quiche::QuicheStringPiece>* out) {
QUICHE team82dee2f2019-01-18 12:35:12 -050061 Representations tmp;
62 HpackEncoder::DecomposeRepresentation(std::make_pair("foobar", value),
63 &tmp);
64
65 out->clear();
66 for (size_t i = 0; i != tmp.size(); ++i) {
67 out->push_back(tmp[i].second);
68 }
69 }
70
71 // TODO(dahollings): Remove or clean up these methods when deprecating
72 // non-incremental encoding path.
73 static bool EncodeHeaderSet(HpackEncoder* encoder,
74 const SpdyHeaderBlock& header_set,
QUICHE team6ddfcae2019-12-11 21:31:23 -080075 std::string* output) {
76 return encoder->EncodeHeaderSet(header_set, output);
QUICHE team82dee2f2019-01-18 12:35:12 -050077 }
78
79 static bool EncodeIncremental(HpackEncoder* encoder,
80 const SpdyHeaderBlock& header_set,
bnc44712912019-08-15 18:58:14 -070081 std::string* output) {
QUICHE team82dee2f2019-01-18 12:35:12 -050082 std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator =
83 encoder->EncodeHeaderSet(header_set);
bnc44712912019-08-15 18:58:14 -070084 std::string output_buffer;
QUICHE team82dee2f2019-01-18 12:35:12 -050085 http2::test::Http2Random random;
86 encoderator->Next(random.UniformInRange(0, 16), &output_buffer);
87 while (encoderator->HasNext()) {
bnc44712912019-08-15 18:58:14 -070088 std::string second_buffer;
QUICHE team82dee2f2019-01-18 12:35:12 -050089 encoderator->Next(random.UniformInRange(0, 16), &second_buffer);
90 output_buffer.append(second_buffer);
91 }
92 *output = std::move(output_buffer);
93 return true;
94 }
95
QUICHE team6ddfcae2019-12-11 21:31:23 -080096 static bool EncodeRepresentations(HpackEncoder* encoder,
97 const Representations& representations,
98 std::string* output) {
99 std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator =
100 encoder->EncodeRepresentations(representations);
101 std::string output_buffer;
102 http2::test::Http2Random random;
103 encoderator->Next(random.UniformInRange(0, 16), &output_buffer);
104 while (encoderator->HasNext()) {
105 std::string second_buffer;
106 encoderator->Next(random.UniformInRange(0, 16), &second_buffer);
107 output_buffer.append(second_buffer);
108 }
109 *output = std::move(output_buffer);
110 return true;
111 }
112
QUICHE team82dee2f2019-01-18 12:35:12 -0500113 private:
114 HpackEncoder* encoder_;
115};
116
117} // namespace test
118
119namespace {
120
121using testing::ElementsAre;
122using testing::Pair;
123
QUICHE team6ddfcae2019-12-11 21:31:23 -0800124enum EncodeStrategy {
125 kDefault,
126 kIncremental,
127 kRepresentations,
128};
129
QUICHE teamf3c80c92020-02-12 09:47:55 -0800130class HpackEncoderTestBase : public QuicheTest {
QUICHE team82dee2f2019-01-18 12:35:12 -0500131 protected:
132 typedef test::HpackEncoderPeer::Representations Representations;
133
QUICHE team6ddfcae2019-12-11 21:31:23 -0800134 HpackEncoderTestBase()
QUICHE team82dee2f2019-01-18 12:35:12 -0500135 : encoder_(ObtainHpackHuffmanTable()),
136 peer_(&encoder_),
137 static_(peer_.table()->GetByIndex(1)),
138 headers_storage_(1024 /* block size */) {}
139
140 void SetUp() override {
QUICHE team82dee2f2019-01-18 12:35:12 -0500141 // Populate dynamic entries into the table fixture. For simplicity each
142 // entry has name.size() + value.size() == 10.
143 key_1_ = peer_.table()->TryAddEntry("key1", "value1");
144 key_2_ = peer_.table()->TryAddEntry("key2", "value2");
145 cookie_a_ = peer_.table()->TryAddEntry("cookie", "a=bb");
146 cookie_c_ = peer_.table()->TryAddEntry("cookie", "c=dd");
147
148 // No further insertions may occur without evictions.
149 peer_.table()->SetMaxSize(peer_.table()->size());
150 }
151
bnc7f82d042020-01-03 12:18:53 -0800152 void SaveHeaders(quiche::QuicheStringPiece name,
153 quiche::QuicheStringPiece value) {
154 quiche::QuicheStringPiece n(
155 headers_storage_.Memdup(name.data(), name.size()), name.size());
156 quiche::QuicheStringPiece v(
157 headers_storage_.Memdup(value.data(), value.size()), value.size());
QUICHE team82dee2f2019-01-18 12:35:12 -0500158 headers_observed_.push_back(std::make_pair(n, v));
159 }
160
161 void ExpectIndex(size_t index) {
162 expected_.AppendPrefix(kIndexedOpcode);
163 expected_.AppendUint32(index);
164 }
165 void ExpectIndexedLiteral(const HpackEntry* key_entry,
bnc7f82d042020-01-03 12:18:53 -0800166 quiche::QuicheStringPiece value) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500167 expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
168 expected_.AppendUint32(IndexOf(key_entry));
169 ExpectString(&expected_, value);
170 }
bnc7f82d042020-01-03 12:18:53 -0800171 void ExpectIndexedLiteral(quiche::QuicheStringPiece name,
172 quiche::QuicheStringPiece value) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500173 expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
174 expected_.AppendUint32(0);
175 ExpectString(&expected_, name);
176 ExpectString(&expected_, value);
177 }
bnc7f82d042020-01-03 12:18:53 -0800178 void ExpectNonIndexedLiteral(quiche::QuicheStringPiece name,
179 quiche::QuicheStringPiece value) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500180 expected_.AppendPrefix(kLiteralNoIndexOpcode);
181 expected_.AppendUint32(0);
182 ExpectString(&expected_, name);
183 ExpectString(&expected_, value);
184 }
bnc7f82d042020-01-03 12:18:53 -0800185 void ExpectString(HpackOutputStream* stream, quiche::QuicheStringPiece str) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500186 const HpackHuffmanTable& huffman_table = peer_.huffman_table();
187 size_t encoded_size = peer_.compression_enabled()
188 ? huffman_table.EncodedSize(str)
189 : str.size();
190 if (encoded_size < str.size()) {
191 expected_.AppendPrefix(kStringLiteralHuffmanEncoded);
192 expected_.AppendUint32(encoded_size);
193 huffman_table.EncodeString(str, stream);
194 } else {
195 expected_.AppendPrefix(kStringLiteralIdentityEncoded);
196 expected_.AppendUint32(str.size());
197 expected_.AppendBytes(str);
198 }
199 }
200 void ExpectHeaderTableSizeUpdate(uint32_t size) {
201 expected_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
202 expected_.AppendUint32(size);
203 }
QUICHE team6ddfcae2019-12-11 21:31:23 -0800204 Representations MakeRepresentations(const SpdyHeaderBlock& header_set) {
205 Representations r;
206 for (const auto& header : header_set) {
207 r.push_back(header);
208 }
209 return r;
210 }
QUICHE team82dee2f2019-01-18 12:35:12 -0500211 void CompareWithExpectedEncoding(const SpdyHeaderBlock& header_set) {
bnc44712912019-08-15 18:58:14 -0700212 std::string expected_out, actual_out;
QUICHE team82dee2f2019-01-18 12:35:12 -0500213 expected_.TakeString(&expected_out);
QUICHE team6ddfcae2019-12-11 21:31:23 -0800214 switch (strategy_) {
215 case kDefault:
216 EXPECT_TRUE(test::HpackEncoderPeer::EncodeHeaderSet(
217 &encoder_, header_set, &actual_out));
218 break;
219 case kIncremental:
220 EXPECT_TRUE(test::HpackEncoderPeer::EncodeIncremental(
221 &encoder_, header_set, &actual_out));
222 break;
223 case kRepresentations:
224 EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations(
225 &encoder_, MakeRepresentations(header_set), &actual_out));
226 break;
227 }
228 EXPECT_EQ(expected_out, actual_out);
229 }
230 void CompareWithExpectedEncoding(const Representations& representations) {
231 std::string expected_out, actual_out;
232 expected_.TakeString(&expected_out);
233 EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations(
234 &encoder_, representations, &actual_out));
QUICHE team82dee2f2019-01-18 12:35:12 -0500235 EXPECT_EQ(expected_out, actual_out);
236 }
237 size_t IndexOf(const HpackEntry* entry) {
238 return peer_.table()->IndexOf(entry);
239 }
240
241 HpackEncoder encoder_;
242 test::HpackEncoderPeer peer_;
243
244 const HpackEntry* static_;
245 const HpackEntry* key_1_;
246 const HpackEntry* key_2_;
247 const HpackEntry* cookie_a_;
248 const HpackEntry* cookie_c_;
249
QUICHE teamb6a87632019-12-10 10:18:13 -0800250 SpdySimpleArena headers_storage_;
bnc7f82d042020-01-03 12:18:53 -0800251 std::vector<std::pair<quiche::QuicheStringPiece, quiche::QuicheStringPiece>>
252 headers_observed_;
QUICHE team82dee2f2019-01-18 12:35:12 -0500253
254 HpackOutputStream expected_;
QUICHE team6ddfcae2019-12-11 21:31:23 -0800255 EncodeStrategy strategy_ = kDefault;
256};
257
258TEST_F(HpackEncoderTestBase, EncodeRepresentations) {
259 encoder_.SetHeaderListener(
bnc7f82d042020-01-03 12:18:53 -0800260 [this](quiche::QuicheStringPiece name, quiche::QuicheStringPiece value) {
QUICHE team6ddfcae2019-12-11 21:31:23 -0800261 this->SaveHeaders(name, value);
262 });
bnc7f82d042020-01-03 12:18:53 -0800263 const std::vector<
264 std::pair<quiche::QuicheStringPiece, quiche::QuicheStringPiece>>
265 header_list = {{"cookie", "val1; val2;val3"},
266 {":path", "/home"},
267 {"accept", "text/html, text/plain,application/xml"},
268 {"cookie", "val4"},
269 {"withnul", quiche::QuicheStringPiece("one\0two", 7)}};
QUICHE team6ddfcae2019-12-11 21:31:23 -0800270 ExpectNonIndexedLiteral(":path", "/home");
271 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val1");
272 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val2");
273 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val3");
274 ExpectIndexedLiteral(peer_.table()->GetByName("accept"),
275 "text/html, text/plain,application/xml");
276 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val4");
bnc7f82d042020-01-03 12:18:53 -0800277 ExpectIndexedLiteral("withnul", quiche::QuicheStringPiece("one\0two", 7));
QUICHE team6ddfcae2019-12-11 21:31:23 -0800278
279 CompareWithExpectedEncoding(header_list);
280 EXPECT_THAT(
281 headers_observed_,
282 ElementsAre(Pair(":path", "/home"), Pair("cookie", "val1"),
283 Pair("cookie", "val2"), Pair("cookie", "val3"),
284 Pair("accept", "text/html, text/plain,application/xml"),
285 Pair("cookie", "val4"),
bnc7f82d042020-01-03 12:18:53 -0800286 Pair("withnul", quiche::QuicheStringPiece("one\0two", 7))));
QUICHE team6ddfcae2019-12-11 21:31:23 -0800287}
288
289class HpackEncoderTest : public HpackEncoderTestBase,
290 public ::testing::WithParamInterface<EncodeStrategy> {
291 protected:
292 void SetUp() override {
293 strategy_ = GetParam();
294 HpackEncoderTestBase::SetUp();
295 }
QUICHE team82dee2f2019-01-18 12:35:12 -0500296};
297
QUICHE teamded03512019-03-07 14:45:11 -0800298INSTANTIATE_TEST_SUITE_P(HpackEncoderTests,
299 HpackEncoderTest,
QUICHE team6ddfcae2019-12-11 21:31:23 -0800300 ::testing::Values(kDefault,
301 kIncremental,
302 kRepresentations));
QUICHE team82dee2f2019-01-18 12:35:12 -0500303
304TEST_P(HpackEncoderTest, SingleDynamicIndex) {
305 encoder_.SetHeaderListener(
bnc7f82d042020-01-03 12:18:53 -0800306 [this](quiche::QuicheStringPiece name, quiche::QuicheStringPiece value) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500307 this->SaveHeaders(name, value);
308 });
309
310 ExpectIndex(IndexOf(key_2_));
311
312 SpdyHeaderBlock headers;
313 headers[key_2_->name()] = key_2_->value();
314 CompareWithExpectedEncoding(headers);
315 EXPECT_THAT(headers_observed_,
316 ElementsAre(Pair(key_2_->name(), key_2_->value())));
317}
318
319TEST_P(HpackEncoderTest, SingleStaticIndex) {
320 ExpectIndex(IndexOf(static_));
321
322 SpdyHeaderBlock headers;
323 headers[static_->name()] = static_->value();
324 CompareWithExpectedEncoding(headers);
325}
326
327TEST_P(HpackEncoderTest, SingleStaticIndexTooLarge) {
328 peer_.table()->SetMaxSize(1); // Also evicts all fixtures.
329 ExpectIndex(IndexOf(static_));
330
331 SpdyHeaderBlock headers;
332 headers[static_->name()] = static_->value();
333 CompareWithExpectedEncoding(headers);
334
dschinazice445c72019-12-17 17:38:38 -0800335 EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
QUICHE team82dee2f2019-01-18 12:35:12 -0500336}
337
338TEST_P(HpackEncoderTest, SingleLiteralWithIndexName) {
339 ExpectIndexedLiteral(key_2_, "value3");
340
341 SpdyHeaderBlock headers;
342 headers[key_2_->name()] = "value3";
343 CompareWithExpectedEncoding(headers);
344
345 // A new entry was inserted and added to the reference set.
346 HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
347 EXPECT_EQ(new_entry->name(), key_2_->name());
348 EXPECT_EQ(new_entry->value(), "value3");
349}
350
351TEST_P(HpackEncoderTest, SingleLiteralWithLiteralName) {
352 ExpectIndexedLiteral("key3", "value3");
353
354 SpdyHeaderBlock headers;
355 headers["key3"] = "value3";
356 CompareWithExpectedEncoding(headers);
357
358 HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
359 EXPECT_EQ(new_entry->name(), "key3");
360 EXPECT_EQ(new_entry->value(), "value3");
361}
362
363TEST_P(HpackEncoderTest, SingleLiteralTooLarge) {
364 peer_.table()->SetMaxSize(1); // Also evicts all fixtures.
365
366 ExpectIndexedLiteral("key3", "value3");
367
368 // A header overflowing the header table is still emitted.
369 // The header table is empty.
370 SpdyHeaderBlock headers;
371 headers["key3"] = "value3";
372 CompareWithExpectedEncoding(headers);
373
374 EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
375}
376
377TEST_P(HpackEncoderTest, EmitThanEvict) {
378 // |key_1_| is toggled and placed into the reference set,
379 // and then immediately evicted by "key3".
380 ExpectIndex(IndexOf(key_1_));
381 ExpectIndexedLiteral("key3", "value3");
382
383 SpdyHeaderBlock headers;
384 headers[key_1_->name()] = key_1_->value();
385 headers["key3"] = "value3";
386 CompareWithExpectedEncoding(headers);
387}
388
389TEST_P(HpackEncoderTest, CookieHeaderIsCrumbled) {
390 ExpectIndex(IndexOf(cookie_a_));
391 ExpectIndex(IndexOf(cookie_c_));
392 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");
393
394 SpdyHeaderBlock headers;
395 headers["cookie"] = "a=bb; c=dd; e=ff";
396 CompareWithExpectedEncoding(headers);
397}
398
QUICHE team72254912019-12-11 08:40:07 -0800399TEST_P(HpackEncoderTest, MultiValuedHeadersNotCrumbled) {
400 ExpectIndexedLiteral("foo", "bar, baz");
401 SpdyHeaderBlock headers;
402 headers["foo"] = "bar, baz";
403 CompareWithExpectedEncoding(headers);
404}
405
QUICHE team82dee2f2019-01-18 12:35:12 -0500406TEST_P(HpackEncoderTest, StringsDynamicallySelectHuffmanCoding) {
407 // Compactable string. Uses Huffman coding.
408 peer_.EmitString("feedbeef");
409 expected_.AppendPrefix(kStringLiteralHuffmanEncoded);
410 expected_.AppendUint32(6);
411 expected_.AppendBytes("\x94\xA5\x92\x32\x96_");
412
413 // Non-compactable. Uses identity coding.
414 peer_.EmitString("@@@@@@");
415 expected_.AppendPrefix(kStringLiteralIdentityEncoded);
416 expected_.AppendUint32(6);
417 expected_.AppendBytes("@@@@@@");
418
bnc44712912019-08-15 18:58:14 -0700419 std::string expected_out, actual_out;
QUICHE team82dee2f2019-01-18 12:35:12 -0500420 expected_.TakeString(&expected_out);
421 peer_.TakeString(&actual_out);
422 EXPECT_EQ(expected_out, actual_out);
423}
424
425TEST_P(HpackEncoderTest, EncodingWithoutCompression) {
426 encoder_.SetHeaderListener(
bnc7f82d042020-01-03 12:18:53 -0800427 [this](quiche::QuicheStringPiece name, quiche::QuicheStringPiece value) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500428 this->SaveHeaders(name, value);
429 });
430 encoder_.DisableCompression();
431
432 ExpectNonIndexedLiteral(":path", "/index.html");
433 ExpectNonIndexedLiteral("cookie", "foo=bar");
434 ExpectNonIndexedLiteral("cookie", "baz=bing");
QUICHE team992004d2019-12-12 09:40:29 -0800435 if (strategy_ == kRepresentations) {
QUICHE team64d26282019-12-10 13:40:09 -0800436 ExpectNonIndexedLiteral("hello", std::string("goodbye\0aloha", 13));
437 } else {
438 ExpectNonIndexedLiteral("hello", "goodbye");
439 ExpectNonIndexedLiteral("hello", "aloha");
440 }
QUICHE team72254912019-12-11 08:40:07 -0800441 ExpectNonIndexedLiteral("multivalue", "value1, value2");
QUICHE team82dee2f2019-01-18 12:35:12 -0500442
443 SpdyHeaderBlock headers;
444 headers[":path"] = "/index.html";
445 headers["cookie"] = "foo=bar; baz=bing";
446 headers["hello"] = "goodbye";
QUICHE team64d26282019-12-10 13:40:09 -0800447 headers.AppendValueOrAddHeader("hello", "aloha");
QUICHE team72254912019-12-11 08:40:07 -0800448 headers["multivalue"] = "value1, value2";
QUICHE team82dee2f2019-01-18 12:35:12 -0500449
450 CompareWithExpectedEncoding(headers);
451
QUICHE team992004d2019-12-12 09:40:29 -0800452 if (strategy_ == kRepresentations) {
QUICHE team64d26282019-12-10 13:40:09 -0800453 EXPECT_THAT(
454 headers_observed_,
bnc7f82d042020-01-03 12:18:53 -0800455 ElementsAre(
456 Pair(":path", "/index.html"), Pair("cookie", "foo=bar"),
457 Pair("cookie", "baz=bing"),
458 Pair("hello", quiche::QuicheStringPiece("goodbye\0aloha", 13)),
459 Pair("multivalue", "value1, value2")));
QUICHE team64d26282019-12-10 13:40:09 -0800460 } else {
461 EXPECT_THAT(
462 headers_observed_,
463 ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"),
464 Pair("cookie", "baz=bing"), Pair("hello", "goodbye"),
QUICHE team72254912019-12-11 08:40:07 -0800465 Pair("hello", "aloha"),
466 Pair("multivalue", "value1, value2")));
QUICHE team64d26282019-12-10 13:40:09 -0800467 }
QUICHE team82dee2f2019-01-18 12:35:12 -0500468}
469
470TEST_P(HpackEncoderTest, MultipleEncodingPasses) {
471 encoder_.SetHeaderListener(
bnc7f82d042020-01-03 12:18:53 -0800472 [this](quiche::QuicheStringPiece name, quiche::QuicheStringPiece value) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500473 this->SaveHeaders(name, value);
474 });
475
476 // Pass 1.
477 {
478 SpdyHeaderBlock headers;
479 headers["key1"] = "value1";
480 headers["cookie"] = "a=bb";
481
482 ExpectIndex(IndexOf(key_1_));
483 ExpectIndex(IndexOf(cookie_a_));
484 CompareWithExpectedEncoding(headers);
485 }
486 // Header table is:
487 // 65: key1: value1
488 // 64: key2: value2
489 // 63: cookie: a=bb
490 // 62: cookie: c=dd
491 // Pass 2.
492 {
493 SpdyHeaderBlock headers;
494 headers["key2"] = "value2";
495 headers["cookie"] = "c=dd; e=ff";
496
497 // "key2: value2"
498 ExpectIndex(64);
499 // "cookie: c=dd"
500 ExpectIndex(62);
501 // This cookie evicts |key1| from the dynamic table.
502 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");
503
504 CompareWithExpectedEncoding(headers);
505 }
506 // Header table is:
507 // 65: key2: value2
508 // 64: cookie: a=bb
509 // 63: cookie: c=dd
510 // 62: cookie: e=ff
511 // Pass 3.
512 {
513 SpdyHeaderBlock headers;
514 headers["key2"] = "value2";
515 headers["cookie"] = "a=bb; b=cc; c=dd";
516
517 // "key2: value2"
518 ExpectIndex(65);
519 // "cookie: a=bb"
520 ExpectIndex(64);
521 // This cookie evicts |key2| from the dynamic table.
522 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "b=cc");
523 // "cookie: c=dd"
524 ExpectIndex(64);
525
526 CompareWithExpectedEncoding(headers);
527 }
528
529 // clang-format off
530 EXPECT_THAT(headers_observed_,
531 ElementsAre(Pair("key1", "value1"),
532 Pair("cookie", "a=bb"),
533 Pair("key2", "value2"),
534 Pair("cookie", "c=dd"),
535 Pair("cookie", "e=ff"),
536 Pair("key2", "value2"),
537 Pair("cookie", "a=bb"),
538 Pair("cookie", "b=cc"),
539 Pair("cookie", "c=dd")));
540 // clang-format on
541}
542
543TEST_P(HpackEncoderTest, PseudoHeadersFirst) {
544 SpdyHeaderBlock headers;
545 // A pseudo-header that should not be indexed.
546 headers[":path"] = "/spam/eggs.html";
547 // A pseudo-header to be indexed.
548 headers[":authority"] = "www.example.com";
549 // A regular header which precedes ":" alphabetically, should still be encoded
550 // after pseudo-headers.
551 headers["-foo"] = "bar";
552 headers["foo"] = "bar";
553 headers["cookie"] = "c=dd";
554
555 // Headers are indexed in the order in which they were added.
556 // This entry pushes "cookie: a=bb" back to 63.
557 ExpectNonIndexedLiteral(":path", "/spam/eggs.html");
558 ExpectIndexedLiteral(peer_.table()->GetByName(":authority"),
559 "www.example.com");
560 ExpectIndexedLiteral("-foo", "bar");
561 ExpectIndexedLiteral("foo", "bar");
562 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "c=dd");
563 CompareWithExpectedEncoding(headers);
564}
565
566TEST_P(HpackEncoderTest, CookieToCrumbs) {
567 test::HpackEncoderPeer peer(nullptr);
bnc7f82d042020-01-03 12:18:53 -0800568 std::vector<quiche::QuicheStringPiece> out;
QUICHE team82dee2f2019-01-18 12:35:12 -0500569
570 // Leading and trailing whitespace is consumed. A space after ';' is consumed.
571 // All other spaces remain. ';' at beginning and end of string produce empty
572 // crumbs.
573 // See section 8.1.3.4 "Compressing the Cookie Header Field" in the HTTP/2
574 // specification at http://tools.ietf.org/html/draft-ietf-httpbis-http2-11
575 peer.CookieToCrumbs(" foo=1;bar=2 ; bar=3; bing=4; ", &out);
576 EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", " bing=4", ""));
577
578 peer.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out);
579 EXPECT_THAT(out, ElementsAre("", "", "foo = bar ", "", "", "baz =bing"));
580
581 peer.CookieToCrumbs("baz=bing; foo=bar; baz=bing", &out);
582 EXPECT_THAT(out, ElementsAre("baz=bing", "foo=bar", "baz=bing"));
583
584 peer.CookieToCrumbs("baz=bing", &out);
585 EXPECT_THAT(out, ElementsAre("baz=bing"));
586
587 peer.CookieToCrumbs("", &out);
588 EXPECT_THAT(out, ElementsAre(""));
589
590 peer.CookieToCrumbs("foo;bar; baz;baz;bing;", &out);
591 EXPECT_THAT(out, ElementsAre("foo", "bar", "baz", "baz", "bing", ""));
592
593 peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3;\t ", &out);
594 EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", ""));
595
596 peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3 \t ", &out);
597 EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3"));
598}
599
600TEST_P(HpackEncoderTest, DecomposeRepresentation) {
601 test::HpackEncoderPeer peer(nullptr);
bnc7f82d042020-01-03 12:18:53 -0800602 std::vector<quiche::QuicheStringPiece> out;
QUICHE team82dee2f2019-01-18 12:35:12 -0500603
604 peer.DecomposeRepresentation("", &out);
605 EXPECT_THAT(out, ElementsAre(""));
606
607 peer.DecomposeRepresentation("foobar", &out);
608 EXPECT_THAT(out, ElementsAre("foobar"));
609
bnc7f82d042020-01-03 12:18:53 -0800610 peer.DecomposeRepresentation(quiche::QuicheStringPiece("foo\0bar", 7), &out);
QUICHE team82dee2f2019-01-18 12:35:12 -0500611 EXPECT_THAT(out, ElementsAre("foo", "bar"));
612
bnc7f82d042020-01-03 12:18:53 -0800613 peer.DecomposeRepresentation(quiche::QuicheStringPiece("\0foo\0bar", 8),
614 &out);
QUICHE team82dee2f2019-01-18 12:35:12 -0500615 EXPECT_THAT(out, ElementsAre("", "foo", "bar"));
616
bnc7f82d042020-01-03 12:18:53 -0800617 peer.DecomposeRepresentation(quiche::QuicheStringPiece("foo\0bar\0", 8),
618 &out);
QUICHE team82dee2f2019-01-18 12:35:12 -0500619 EXPECT_THAT(out, ElementsAre("foo", "bar", ""));
620
bnc7f82d042020-01-03 12:18:53 -0800621 peer.DecomposeRepresentation(quiche::QuicheStringPiece("\0foo\0bar\0", 9),
622 &out);
QUICHE team82dee2f2019-01-18 12:35:12 -0500623 EXPECT_THAT(out, ElementsAre("", "foo", "bar", ""));
624}
625
626// Test that encoded headers do not have \0-delimited multiple values, as this
627// became disallowed in HTTP/2 draft-14.
628TEST_P(HpackEncoderTest, CrumbleNullByteDelimitedValue) {
QUICHE team6ddfcae2019-12-11 21:31:23 -0800629 if (strategy_ == kRepresentations) {
630 // When HpackEncoder is asked to encode a list of Representations, the
631 // caller must crumble null-delimited values.
632 return;
633 }
QUICHE team82dee2f2019-01-18 12:35:12 -0500634 SpdyHeaderBlock headers;
635 // A header field to be crumbled: "spam: foo\0bar".
bnc44712912019-08-15 18:58:14 -0700636 headers["spam"] = std::string("foo\0bar", 7);
QUICHE team82dee2f2019-01-18 12:35:12 -0500637
638 ExpectIndexedLiteral("spam", "foo");
639 expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
640 expected_.AppendUint32(62);
641 expected_.AppendPrefix(kStringLiteralIdentityEncoded);
642 expected_.AppendUint32(3);
643 expected_.AppendBytes("bar");
644 CompareWithExpectedEncoding(headers);
645}
646
647TEST_P(HpackEncoderTest, HeaderTableSizeUpdate) {
648 encoder_.ApplyHeaderTableSizeSetting(1024);
649 ExpectHeaderTableSizeUpdate(1024);
650 ExpectIndexedLiteral("key3", "value3");
651
652 SpdyHeaderBlock headers;
653 headers["key3"] = "value3";
654 CompareWithExpectedEncoding(headers);
655
656 HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
657 EXPECT_EQ(new_entry->name(), "key3");
658 EXPECT_EQ(new_entry->value(), "value3");
659}
660
661TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithMin) {
662 const size_t starting_size = peer_.table()->settings_size_bound();
663 encoder_.ApplyHeaderTableSizeSetting(starting_size - 2);
664 encoder_.ApplyHeaderTableSizeSetting(starting_size - 1);
665 // We must encode the low watermark, so the peer knows to evict entries
666 // if necessary.
667 ExpectHeaderTableSizeUpdate(starting_size - 2);
668 ExpectHeaderTableSizeUpdate(starting_size - 1);
669 ExpectIndexedLiteral("key3", "value3");
670
671 SpdyHeaderBlock headers;
672 headers["key3"] = "value3";
673 CompareWithExpectedEncoding(headers);
674
675 HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
676 EXPECT_EQ(new_entry->name(), "key3");
677 EXPECT_EQ(new_entry->value(), "value3");
678}
679
680TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithExistingSize) {
681 encoder_.ApplyHeaderTableSizeSetting(peer_.table()->settings_size_bound());
682 // No encoded size update.
683 ExpectIndexedLiteral("key3", "value3");
684
685 SpdyHeaderBlock headers;
686 headers["key3"] = "value3";
687 CompareWithExpectedEncoding(headers);
688
689 HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
690 EXPECT_EQ(new_entry->name(), "key3");
691 EXPECT_EQ(new_entry->value(), "value3");
692}
693
694TEST_P(HpackEncoderTest, HeaderTableSizeUpdatesWithGreaterSize) {
695 const size_t starting_size = peer_.table()->settings_size_bound();
696 encoder_.ApplyHeaderTableSizeSetting(starting_size + 1);
697 encoder_.ApplyHeaderTableSizeSetting(starting_size + 2);
698 // Only a single size update to the final size.
699 ExpectHeaderTableSizeUpdate(starting_size + 2);
700 ExpectIndexedLiteral("key3", "value3");
701
702 SpdyHeaderBlock headers;
703 headers["key3"] = "value3";
704 CompareWithExpectedEncoding(headers);
705
706 HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
707 EXPECT_EQ(new_entry->name(), "key3");
708 EXPECT_EQ(new_entry->value(), "value3");
709}
710
711} // namespace
712
713} // namespace spdy