1 // 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 "quiche/spdy/core/hpack/hpack_encoder.h"
6
7 #include <cstddef>
8 #include <cstdint>
9 #include <memory>
10 #include <string>
11 #include <utility>
12 #include <vector>
13
14 #include "absl/strings/string_view.h"
15 #include "quiche/http2/hpack/huffman/hpack_huffman_encoder.h"
16 #include "quiche/http2/test_tools/http2_random.h"
17 #include "quiche/common/platform/api/quiche_logging.h"
18 #include "quiche/common/platform/api/quiche_test.h"
19 #include "quiche/common/quiche_simple_arena.h"
20 #include "quiche/spdy/core/hpack/hpack_constants.h"
21 #include "quiche/spdy/core/hpack/hpack_entry.h"
22 #include "quiche/spdy/core/hpack/hpack_header_table.h"
23 #include "quiche/spdy/core/hpack/hpack_output_stream.h"
24 #include "quiche/spdy/core/hpack/hpack_static_table.h"
25 #include "quiche/spdy/core/http2_header_block.h"
26
27 namespace spdy {
28
29 namespace test {
30
31 class HpackHeaderTablePeer {
32 public:
HpackHeaderTablePeer(HpackHeaderTable * table)33 explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {}
34
GetFirstStaticEntry() const35 const HpackEntry* GetFirstStaticEntry() const {
36 return &table_->static_entries_.front();
37 }
38
dynamic_entries()39 HpackHeaderTable::DynamicEntryTable* dynamic_entries() {
40 return &table_->dynamic_entries_;
41 }
42
43 private:
44 HpackHeaderTable* table_;
45 };
46
47 class HpackEncoderPeer {
48 public:
49 typedef HpackEncoder::Representation Representation;
50 typedef HpackEncoder::Representations Representations;
51
HpackEncoderPeer(HpackEncoder * encoder)52 explicit HpackEncoderPeer(HpackEncoder* encoder) : encoder_(encoder) {}
53
compression_enabled() const54 bool compression_enabled() const { return encoder_->enable_compression_; }
table()55 HpackHeaderTable* table() { return &encoder_->header_table_; }
table_peer()56 HpackHeaderTablePeer table_peer() { return HpackHeaderTablePeer(table()); }
EmitString(absl::string_view str)57 void EmitString(absl::string_view str) { encoder_->EmitString(str); }
TakeString(std::string * out)58 void TakeString(std::string* out) {
59 *out = encoder_->output_stream_.TakeString();
60 }
CookieToCrumbs(absl::string_view cookie,std::vector<absl::string_view> * out)61 static void CookieToCrumbs(absl::string_view cookie,
62 std::vector<absl::string_view>* out) {
63 Representations tmp;
64 HpackEncoder::CookieToCrumbs(std::make_pair("", cookie), &tmp);
65
66 out->clear();
67 for (size_t i = 0; i != tmp.size(); ++i) {
68 out->push_back(tmp[i].second);
69 }
70 }
DecomposeRepresentation(absl::string_view value,std::vector<absl::string_view> * out)71 static void DecomposeRepresentation(absl::string_view value,
72 std::vector<absl::string_view>* out) {
73 Representations tmp;
74 HpackEncoder::DecomposeRepresentation(std::make_pair("foobar", value),
75 &tmp);
76
77 out->clear();
78 for (size_t i = 0; i != tmp.size(); ++i) {
79 out->push_back(tmp[i].second);
80 }
81 }
82
83 // TODO(dahollings): Remove or clean up these methods when deprecating
84 // non-incremental encoding path.
EncodeHeaderBlock(HpackEncoder * encoder,const Http2HeaderBlock & header_set)85 static std::string EncodeHeaderBlock(HpackEncoder* encoder,
86 const Http2HeaderBlock& header_set) {
87 return encoder->EncodeHeaderBlock(header_set);
88 }
89
EncodeIncremental(HpackEncoder * encoder,const Http2HeaderBlock & header_set,std::string * output)90 static bool EncodeIncremental(HpackEncoder* encoder,
91 const Http2HeaderBlock& header_set,
92 std::string* output) {
93 std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator =
94 encoder->EncodeHeaderSet(header_set);
95 http2::test::Http2Random random;
96 std::string output_buffer = encoderator->Next(random.UniformInRange(0, 16));
97 while (encoderator->HasNext()) {
98 std::string second_buffer =
99 encoderator->Next(random.UniformInRange(0, 16));
100 output_buffer.append(second_buffer);
101 }
102 *output = std::move(output_buffer);
103 return true;
104 }
105
EncodeRepresentations(HpackEncoder * encoder,const Representations & representations,std::string * output)106 static bool EncodeRepresentations(HpackEncoder* encoder,
107 const Representations& representations,
108 std::string* output) {
109 std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator =
110 encoder->EncodeRepresentations(representations);
111 http2::test::Http2Random random;
112 std::string output_buffer = encoderator->Next(random.UniformInRange(0, 16));
113 while (encoderator->HasNext()) {
114 std::string second_buffer =
115 encoderator->Next(random.UniformInRange(0, 16));
116 output_buffer.append(second_buffer);
117 }
118 *output = std::move(output_buffer);
119 return true;
120 }
121
122 private:
123 HpackEncoder* encoder_;
124 };
125
126 } // namespace test
127
128 namespace {
129
130 using testing::ElementsAre;
131 using testing::Pair;
132
133 const size_t kStaticEntryIndex = 1;
134
135 enum EncodeStrategy {
136 kDefault,
137 kIncremental,
138 kRepresentations,
139 };
140
141 class HpackEncoderTest
142 : public quiche::test::QuicheTestWithParam<EncodeStrategy> {
143 protected:
144 typedef test::HpackEncoderPeer::Representations Representations;
145
HpackEncoderTest()146 HpackEncoderTest()
147 : peer_(&encoder_),
148 static_(peer_.table_peer().GetFirstStaticEntry()),
149 dynamic_table_insertions_(0),
150 headers_storage_(1024 /* block size */),
151 strategy_(GetParam()) {}
152
SetUp()153 void SetUp() override {
154 // Populate dynamic entries into the table fixture. For simplicity each
155 // entry has name.size() + value.size() == 10.
156 key_1_ = peer_.table()->TryAddEntry("key1", "value1");
157 key_1_index_ = dynamic_table_insertions_++;
158 key_2_ = peer_.table()->TryAddEntry("key2", "value2");
159 key_2_index_ = dynamic_table_insertions_++;
160 cookie_a_ = peer_.table()->TryAddEntry("cookie", "a=bb");
161 cookie_a_index_ = dynamic_table_insertions_++;
162 cookie_c_ = peer_.table()->TryAddEntry("cookie", "c=dd");
163 cookie_c_index_ = dynamic_table_insertions_++;
164
165 // No further insertions may occur without evictions.
166 peer_.table()->SetMaxSize(peer_.table()->size());
167 QUICHE_CHECK_EQ(kInitialDynamicTableSize, peer_.table()->size());
168 }
169
SaveHeaders(absl::string_view name,absl::string_view value)170 void SaveHeaders(absl::string_view name, absl::string_view value) {
171 absl::string_view n(headers_storage_.Memdup(name.data(), name.size()),
172 name.size());
173 absl::string_view v(headers_storage_.Memdup(value.data(), value.size()),
174 value.size());
175 headers_observed_.push_back(std::make_pair(n, v));
176 }
177
ExpectIndex(size_t index)178 void ExpectIndex(size_t index) {
179 expected_.AppendPrefix(kIndexedOpcode);
180 expected_.AppendUint32(index);
181 }
ExpectIndexedLiteral(size_t key_index,absl::string_view value)182 void ExpectIndexedLiteral(size_t key_index, absl::string_view value) {
183 expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
184 expected_.AppendUint32(key_index);
185 ExpectString(&expected_, value);
186 }
ExpectIndexedLiteral(absl::string_view name,absl::string_view value)187 void ExpectIndexedLiteral(absl::string_view name, absl::string_view value) {
188 expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
189 expected_.AppendUint32(0);
190 ExpectString(&expected_, name);
191 ExpectString(&expected_, value);
192 }
ExpectNonIndexedLiteral(absl::string_view name,absl::string_view value)193 void ExpectNonIndexedLiteral(absl::string_view name,
194 absl::string_view value) {
195 expected_.AppendPrefix(kLiteralNoIndexOpcode);
196 expected_.AppendUint32(0);
197 ExpectString(&expected_, name);
198 ExpectString(&expected_, value);
199 }
ExpectNonIndexedLiteralWithNameIndex(size_t key_index,absl::string_view value)200 void ExpectNonIndexedLiteralWithNameIndex(size_t key_index,
201 absl::string_view value) {
202 expected_.AppendPrefix(kLiteralNoIndexOpcode);
203 expected_.AppendUint32(key_index);
204 ExpectString(&expected_, value);
205 }
ExpectString(HpackOutputStream * stream,absl::string_view str)206 void ExpectString(HpackOutputStream* stream, absl::string_view str) {
207 size_t encoded_size =
208 peer_.compression_enabled() ? http2::HuffmanSize(str) : str.size();
209 if (encoded_size < str.size()) {
210 expected_.AppendPrefix(kStringLiteralHuffmanEncoded);
211 expected_.AppendUint32(encoded_size);
212 http2::HuffmanEncodeFast(str, encoded_size, stream->MutableString());
213 } else {
214 expected_.AppendPrefix(kStringLiteralIdentityEncoded);
215 expected_.AppendUint32(str.size());
216 expected_.AppendBytes(str);
217 }
218 }
ExpectHeaderTableSizeUpdate(uint32_t size)219 void ExpectHeaderTableSizeUpdate(uint32_t size) {
220 expected_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
221 expected_.AppendUint32(size);
222 }
MakeRepresentations(const Http2HeaderBlock & header_set)223 Representations MakeRepresentations(const Http2HeaderBlock& header_set) {
224 Representations r;
225 for (const auto& header : header_set) {
226 r.push_back(header);
227 }
228 return r;
229 }
CompareWithExpectedEncoding(const Http2HeaderBlock & header_set)230 void CompareWithExpectedEncoding(const Http2HeaderBlock& header_set) {
231 std::string actual_out;
232 std::string expected_out = expected_.TakeString();
233 switch (strategy_) {
234 case kDefault:
235 actual_out =
236 test::HpackEncoderPeer::EncodeHeaderBlock(&encoder_, header_set);
237 break;
238 case kIncremental:
239 EXPECT_TRUE(test::HpackEncoderPeer::EncodeIncremental(
240 &encoder_, header_set, &actual_out));
241 break;
242 case kRepresentations:
243 EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations(
244 &encoder_, MakeRepresentations(header_set), &actual_out));
245 break;
246 }
247 EXPECT_EQ(expected_out, actual_out);
248 }
CompareWithExpectedEncoding(const Representations & representations)249 void CompareWithExpectedEncoding(const Representations& representations) {
250 std::string actual_out;
251 std::string expected_out = expected_.TakeString();
252 EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations(
253 &encoder_, representations, &actual_out));
254 EXPECT_EQ(expected_out, actual_out);
255 }
256 // Converts the index of a dynamic table entry to the HPACK index.
257 // In these test, dynamic table entries are indexed sequentially, starting
258 // with 0. The HPACK indexing scheme is defined at
259 // https://httpwg.org/specs/rfc7541.html#index.address.space.
DynamicIndexToWireIndex(size_t index)260 size_t DynamicIndexToWireIndex(size_t index) {
261 return dynamic_table_insertions_ - index + kStaticTableSize;
262 }
263
264 HpackEncoder encoder_;
265 test::HpackEncoderPeer peer_;
266
267 // Calculated based on the names and values inserted in SetUp(), above.
268 const size_t kInitialDynamicTableSize = 4 * (10 + 32);
269
270 const HpackEntry* static_;
271 const HpackEntry* key_1_;
272 const HpackEntry* key_2_;
273 const HpackEntry* cookie_a_;
274 const HpackEntry* cookie_c_;
275 size_t key_1_index_;
276 size_t key_2_index_;
277 size_t cookie_a_index_;
278 size_t cookie_c_index_;
279 size_t dynamic_table_insertions_;
280
281 quiche::QuicheSimpleArena headers_storage_;
282 std::vector<std::pair<absl::string_view, absl::string_view>>
283 headers_observed_;
284
285 HpackOutputStream expected_;
286 const EncodeStrategy strategy_;
287 };
288
289 using HpackEncoderTestWithDefaultStrategy = HpackEncoderTest;
290
291 INSTANTIATE_TEST_SUITE_P(HpackEncoderTests, HpackEncoderTestWithDefaultStrategy,
292 ::testing::Values(kDefault));
293
TEST_P(HpackEncoderTestWithDefaultStrategy,EncodeRepresentations)294 TEST_P(HpackEncoderTestWithDefaultStrategy, EncodeRepresentations) {
295 EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize());
296 encoder_.SetHeaderListener(
297 [this](absl::string_view name, absl::string_view value) {
298 this->SaveHeaders(name, value);
299 });
300 const std::vector<std::pair<absl::string_view, absl::string_view>>
301 header_list = {{"cookie", "val1; val2;val3"},
302 {":path", "/home"},
303 {"accept", "text/html, text/plain,application/xml"},
304 {"cookie", "val4"},
305 {"withnul", absl::string_view("one\0two", 7)}};
306 ExpectNonIndexedLiteralWithNameIndex(peer_.table()->GetByName(":path"),
307 "/home");
308 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val1");
309 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val2");
310 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val3");
311 ExpectIndexedLiteral(peer_.table()->GetByName("accept"),
312 "text/html, text/plain,application/xml");
313 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val4");
314 ExpectIndexedLiteral("withnul", absl::string_view("one\0two", 7));
315
316 CompareWithExpectedEncoding(header_list);
317 EXPECT_THAT(
318 headers_observed_,
319 ElementsAre(Pair(":path", "/home"), Pair("cookie", "val1"),
320 Pair("cookie", "val2"), Pair("cookie", "val3"),
321 Pair("accept", "text/html, text/plain,application/xml"),
322 Pair("cookie", "val4"),
323 Pair("withnul", absl::string_view("one\0two", 7))));
324 // Insertions and evictions have happened over the course of the test.
325 EXPECT_GE(kInitialDynamicTableSize, encoder_.GetDynamicTableSize());
326 }
327
TEST_P(HpackEncoderTestWithDefaultStrategy,WithoutCookieCrumbling)328 TEST_P(HpackEncoderTestWithDefaultStrategy, WithoutCookieCrumbling) {
329 EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize());
330 encoder_.SetHeaderListener(
331 [this](absl::string_view name, absl::string_view value) {
332 this->SaveHeaders(name, value);
333 });
334 encoder_.DisableCookieCrumbling();
335
336 const std::vector<std::pair<absl::string_view, absl::string_view>>
337 header_list = {{"cookie", "val1; val2;val3"},
338 {":path", "/home"},
339 {"accept", "text/html, text/plain,application/xml"},
340 {"cookie", "val4"},
341 {"withnul", absl::string_view("one\0two", 7)}};
342 ExpectNonIndexedLiteralWithNameIndex(peer_.table()->GetByName(":path"),
343 "/home");
344 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val1; val2;val3");
345 ExpectIndexedLiteral(peer_.table()->GetByName("accept"),
346 "text/html, text/plain,application/xml");
347 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val4");
348 ExpectIndexedLiteral("withnul", absl::string_view("one\0two", 7));
349
350 CompareWithExpectedEncoding(header_list);
351 EXPECT_THAT(
352 headers_observed_,
353 ElementsAre(Pair(":path", "/home"), Pair("cookie", "val1; val2;val3"),
354 Pair("accept", "text/html, text/plain,application/xml"),
355 Pair("cookie", "val4"),
356 Pair("withnul", absl::string_view("one\0two", 7))));
357 // Insertions and evictions have happened over the course of the test.
358 EXPECT_GE(kInitialDynamicTableSize, encoder_.GetDynamicTableSize());
359 }
360
TEST_P(HpackEncoderTestWithDefaultStrategy,DynamicTableGrows)361 TEST_P(HpackEncoderTestWithDefaultStrategy, DynamicTableGrows) {
362 EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize());
363 peer_.table()->SetMaxSize(4096);
364 encoder_.SetHeaderListener(
365 [this](absl::string_view name, absl::string_view value) {
366 this->SaveHeaders(name, value);
367 });
368 const std::vector<std::pair<absl::string_view, absl::string_view>>
369 header_list = {{"cookie", "val1; val2;val3"},
370 {":path", "/home"},
371 {"accept", "text/html, text/plain,application/xml"},
372 {"cookie", "val4"},
373 {"withnul", absl::string_view("one\0two", 7)}};
374 std::string out;
375 EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations(&encoder_,
376 header_list, &out));
377
378 EXPECT_FALSE(out.empty());
379 // Insertions have happened over the course of the test.
380 EXPECT_GT(encoder_.GetDynamicTableSize(), kInitialDynamicTableSize);
381 }
382
383 INSTANTIATE_TEST_SUITE_P(HpackEncoderTests, HpackEncoderTest,
384 ::testing::Values(kDefault, kIncremental,
385 kRepresentations));
386
TEST_P(HpackEncoderTest,SingleDynamicIndex)387 TEST_P(HpackEncoderTest, SingleDynamicIndex) {
388 encoder_.SetHeaderListener(
389 [this](absl::string_view name, absl::string_view value) {
390 this->SaveHeaders(name, value);
391 });
392
393 ExpectIndex(DynamicIndexToWireIndex(key_2_index_));
394
395 Http2HeaderBlock headers;
396 headers[key_2_->name()] = key_2_->value();
397 CompareWithExpectedEncoding(headers);
398 EXPECT_THAT(headers_observed_,
399 ElementsAre(Pair(key_2_->name(), key_2_->value())));
400 }
401
TEST_P(HpackEncoderTest,SingleStaticIndex)402 TEST_P(HpackEncoderTest, SingleStaticIndex) {
403 ExpectIndex(kStaticEntryIndex);
404
405 Http2HeaderBlock headers;
406 headers[static_->name()] = static_->value();
407 CompareWithExpectedEncoding(headers);
408 }
409
TEST_P(HpackEncoderTest,SingleStaticIndexTooLarge)410 TEST_P(HpackEncoderTest, SingleStaticIndexTooLarge) {
411 peer_.table()->SetMaxSize(1); // Also evicts all fixtures.
412 ExpectIndex(kStaticEntryIndex);
413
414 Http2HeaderBlock headers;
415 headers[static_->name()] = static_->value();
416 CompareWithExpectedEncoding(headers);
417
418 EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
419 }
420
TEST_P(HpackEncoderTest,SingleLiteralWithIndexName)421 TEST_P(HpackEncoderTest, SingleLiteralWithIndexName) {
422 ExpectIndexedLiteral(DynamicIndexToWireIndex(key_2_index_), "value3");
423
424 Http2HeaderBlock headers;
425 headers[key_2_->name()] = "value3";
426 CompareWithExpectedEncoding(headers);
427
428 // A new entry was inserted and added to the reference set.
429 HpackEntry* new_entry = peer_.table_peer().dynamic_entries()->front().get();
430 EXPECT_EQ(new_entry->name(), key_2_->name());
431 EXPECT_EQ(new_entry->value(), "value3");
432 }
433
TEST_P(HpackEncoderTest,SingleLiteralWithLiteralName)434 TEST_P(HpackEncoderTest, SingleLiteralWithLiteralName) {
435 ExpectIndexedLiteral("key3", "value3");
436
437 Http2HeaderBlock headers;
438 headers["key3"] = "value3";
439 CompareWithExpectedEncoding(headers);
440
441 HpackEntry* new_entry = peer_.table_peer().dynamic_entries()->front().get();
442 EXPECT_EQ(new_entry->name(), "key3");
443 EXPECT_EQ(new_entry->value(), "value3");
444 }
445
TEST_P(HpackEncoderTest,SingleLiteralTooLarge)446 TEST_P(HpackEncoderTest, SingleLiteralTooLarge) {
447 peer_.table()->SetMaxSize(1); // Also evicts all fixtures.
448
449 ExpectIndexedLiteral("key3", "value3");
450
451 // A header overflowing the header table is still emitted.
452 // The header table is empty.
453 Http2HeaderBlock headers;
454 headers["key3"] = "value3";
455 CompareWithExpectedEncoding(headers);
456
457 EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
458 }
459
TEST_P(HpackEncoderTest,EmitThanEvict)460 TEST_P(HpackEncoderTest, EmitThanEvict) {
461 // |key_1_| is toggled and placed into the reference set,
462 // and then immediately evicted by "key3".
463 ExpectIndex(DynamicIndexToWireIndex(key_1_index_));
464 ExpectIndexedLiteral("key3", "value3");
465
466 Http2HeaderBlock headers;
467 headers[key_1_->name()] = key_1_->value();
468 headers["key3"] = "value3";
469 CompareWithExpectedEncoding(headers);
470 }
471
TEST_P(HpackEncoderTest,CookieHeaderIsCrumbled)472 TEST_P(HpackEncoderTest, CookieHeaderIsCrumbled) {
473 ExpectIndex(DynamicIndexToWireIndex(cookie_a_index_));
474 ExpectIndex(DynamicIndexToWireIndex(cookie_c_index_));
475 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");
476
477 Http2HeaderBlock headers;
478 headers["cookie"] = "a=bb; c=dd; e=ff";
479 CompareWithExpectedEncoding(headers);
480 }
481
TEST_P(HpackEncoderTest,CookieHeaderIsNotCrumbled)482 TEST_P(HpackEncoderTest, CookieHeaderIsNotCrumbled) {
483 encoder_.DisableCookieCrumbling();
484 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "a=bb; c=dd; e=ff");
485
486 Http2HeaderBlock headers;
487 headers["cookie"] = "a=bb; c=dd; e=ff";
488 CompareWithExpectedEncoding(headers);
489 }
490
TEST_P(HpackEncoderTest,MultiValuedHeadersNotCrumbled)491 TEST_P(HpackEncoderTest, MultiValuedHeadersNotCrumbled) {
492 ExpectIndexedLiteral("foo", "bar, baz");
493 Http2HeaderBlock headers;
494 headers["foo"] = "bar, baz";
495 CompareWithExpectedEncoding(headers);
496 }
497
TEST_P(HpackEncoderTest,StringsDynamicallySelectHuffmanCoding)498 TEST_P(HpackEncoderTest, StringsDynamicallySelectHuffmanCoding) {
499 // Compactable string. Uses Huffman coding.
500 peer_.EmitString("feedbeef");
501 expected_.AppendPrefix(kStringLiteralHuffmanEncoded);
502 expected_.AppendUint32(6);
503 expected_.AppendBytes("\x94\xA5\x92\x32\x96_");
504
505 // Non-compactable. Uses identity coding.
506 peer_.EmitString("@@@@@@");
507 expected_.AppendPrefix(kStringLiteralIdentityEncoded);
508 expected_.AppendUint32(6);
509 expected_.AppendBytes("@@@@@@");
510
511 std::string actual_out;
512 std::string expected_out = expected_.TakeString();
513 peer_.TakeString(&actual_out);
514 EXPECT_EQ(expected_out, actual_out);
515 }
516
TEST_P(HpackEncoderTest,EncodingWithoutCompression)517 TEST_P(HpackEncoderTest, EncodingWithoutCompression) {
518 encoder_.SetHeaderListener(
519 [this](absl::string_view name, absl::string_view value) {
520 this->SaveHeaders(name, value);
521 });
522 encoder_.DisableCompression();
523
524 ExpectNonIndexedLiteral(":path", "/index.html");
525 ExpectNonIndexedLiteral("cookie", "foo=bar");
526 ExpectNonIndexedLiteral("cookie", "baz=bing");
527 if (strategy_ == kRepresentations) {
528 ExpectNonIndexedLiteral("hello", std::string("goodbye\0aloha", 13));
529 } else {
530 ExpectNonIndexedLiteral("hello", "goodbye");
531 ExpectNonIndexedLiteral("hello", "aloha");
532 }
533 ExpectNonIndexedLiteral("multivalue", "value1, value2");
534
535 Http2HeaderBlock headers;
536 headers[":path"] = "/index.html";
537 headers["cookie"] = "foo=bar; baz=bing";
538 headers["hello"] = "goodbye";
539 headers.AppendValueOrAddHeader("hello", "aloha");
540 headers["multivalue"] = "value1, value2";
541
542 CompareWithExpectedEncoding(headers);
543
544 if (strategy_ == kRepresentations) {
545 EXPECT_THAT(
546 headers_observed_,
547 ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"),
548 Pair("cookie", "baz=bing"),
549 Pair("hello", absl::string_view("goodbye\0aloha", 13)),
550 Pair("multivalue", "value1, value2")));
551 } else {
552 EXPECT_THAT(
553 headers_observed_,
554 ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"),
555 Pair("cookie", "baz=bing"), Pair("hello", "goodbye"),
556 Pair("hello", "aloha"),
557 Pair("multivalue", "value1, value2")));
558 }
559 EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize());
560 }
561
TEST_P(HpackEncoderTest,MultipleEncodingPasses)562 TEST_P(HpackEncoderTest, MultipleEncodingPasses) {
563 encoder_.SetHeaderListener(
564 [this](absl::string_view name, absl::string_view value) {
565 this->SaveHeaders(name, value);
566 });
567
568 // Pass 1.
569 {
570 Http2HeaderBlock headers;
571 headers["key1"] = "value1";
572 headers["cookie"] = "a=bb";
573
574 ExpectIndex(DynamicIndexToWireIndex(key_1_index_));
575 ExpectIndex(DynamicIndexToWireIndex(cookie_a_index_));
576 CompareWithExpectedEncoding(headers);
577 }
578 // Header table is:
579 // 65: key1: value1
580 // 64: key2: value2
581 // 63: cookie: a=bb
582 // 62: cookie: c=dd
583 // Pass 2.
584 {
585 Http2HeaderBlock headers;
586 headers["key2"] = "value2";
587 headers["cookie"] = "c=dd; e=ff";
588
589 // "key2: value2"
590 ExpectIndex(DynamicIndexToWireIndex(key_2_index_));
591 // "cookie: c=dd"
592 ExpectIndex(DynamicIndexToWireIndex(cookie_c_index_));
593 // This cookie evicts |key1| from the dynamic table.
594 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");
595 dynamic_table_insertions_++;
596
597 CompareWithExpectedEncoding(headers);
598 }
599 // Header table is:
600 // 65: key2: value2
601 // 64: cookie: a=bb
602 // 63: cookie: c=dd
603 // 62: cookie: e=ff
604 // Pass 3.
605 {
606 Http2HeaderBlock headers;
607 headers["key2"] = "value2";
608 headers["cookie"] = "a=bb; b=cc; c=dd";
609
610 // "key2: value2"
611 EXPECT_EQ(65u, DynamicIndexToWireIndex(key_2_index_));
612 ExpectIndex(DynamicIndexToWireIndex(key_2_index_));
613 // "cookie: a=bb"
614 EXPECT_EQ(64u, DynamicIndexToWireIndex(cookie_a_index_));
615 ExpectIndex(DynamicIndexToWireIndex(cookie_a_index_));
616 // This cookie evicts |key2| from the dynamic table.
617 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "b=cc");
618 dynamic_table_insertions_++;
619 // "cookie: c=dd"
620 ExpectIndex(DynamicIndexToWireIndex(cookie_c_index_));
621
622 CompareWithExpectedEncoding(headers);
623 }
624
625 // clang-format off
626 EXPECT_THAT(headers_observed_,
627 ElementsAre(Pair("key1", "value1"),
628 Pair("cookie", "a=bb"),
629 Pair("key2", "value2"),
630 Pair("cookie", "c=dd"),
631 Pair("cookie", "e=ff"),
632 Pair("key2", "value2"),
633 Pair("cookie", "a=bb"),
634 Pair("cookie", "b=cc"),
635 Pair("cookie", "c=dd")));
636 // clang-format on
637 }
638
TEST_P(HpackEncoderTest,PseudoHeadersFirst)639 TEST_P(HpackEncoderTest, PseudoHeadersFirst) {
640 Http2HeaderBlock headers;
641 // A pseudo-header that should not be indexed.
642 headers[":path"] = "/spam/eggs.html";
643 // A pseudo-header to be indexed.
644 headers[":authority"] = "www.example.com";
645 // A regular header which precedes ":" alphabetically, should still be encoded
646 // after pseudo-headers.
647 headers["-foo"] = "bar";
648 headers["foo"] = "bar";
649 headers["cookie"] = "c=dd";
650
651 // Headers are indexed in the order in which they were added.
652 // This entry pushes "cookie: a=bb" back to 63.
653 ExpectNonIndexedLiteralWithNameIndex(peer_.table()->GetByName(":path"),
654 "/spam/eggs.html");
655 ExpectIndexedLiteral(peer_.table()->GetByName(":authority"),
656 "www.example.com");
657 ExpectIndexedLiteral("-foo", "bar");
658 ExpectIndexedLiteral("foo", "bar");
659 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "c=dd");
660 CompareWithExpectedEncoding(headers);
661 }
662
TEST_P(HpackEncoderTest,CookieToCrumbs)663 TEST_P(HpackEncoderTest, CookieToCrumbs) {
664 test::HpackEncoderPeer peer(nullptr);
665 std::vector<absl::string_view> out;
666
667 // Leading and trailing whitespace is consumed. A space after ';' is consumed.
668 // All other spaces remain. ';' at beginning and end of string produce empty
669 // crumbs.
670 // See section 8.1.3.4 "Compressing the Cookie Header Field" in the HTTP/2
671 // specification at http://tools.ietf.org/html/draft-ietf-httpbis-http2-11
672 peer.CookieToCrumbs(" foo=1;bar=2 ; bar=3; bing=4; ", &out);
673 EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", " bing=4", ""));
674
675 peer.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out);
676 EXPECT_THAT(out, ElementsAre("", "", "foo = bar ", "", "", "baz =bing"));
677
678 peer.CookieToCrumbs("baz=bing; foo=bar; baz=bing", &out);
679 EXPECT_THAT(out, ElementsAre("baz=bing", "foo=bar", "baz=bing"));
680
681 peer.CookieToCrumbs("baz=bing", &out);
682 EXPECT_THAT(out, ElementsAre("baz=bing"));
683
684 peer.CookieToCrumbs("", &out);
685 EXPECT_THAT(out, ElementsAre(""));
686
687 peer.CookieToCrumbs("foo;bar; baz;baz;bing;", &out);
688 EXPECT_THAT(out, ElementsAre("foo", "bar", "baz", "baz", "bing", ""));
689
690 peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3;\t ", &out);
691 EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", ""));
692
693 peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3 \t ", &out);
694 EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3"));
695 }
696
TEST_P(HpackEncoderTest,DecomposeRepresentation)697 TEST_P(HpackEncoderTest, DecomposeRepresentation) {
698 test::HpackEncoderPeer peer(nullptr);
699 std::vector<absl::string_view> out;
700
701 peer.DecomposeRepresentation("", &out);
702 EXPECT_THAT(out, ElementsAre(""));
703
704 peer.DecomposeRepresentation("foobar", &out);
705 EXPECT_THAT(out, ElementsAre("foobar"));
706
707 peer.DecomposeRepresentation(absl::string_view("foo\0bar", 7), &out);
708 EXPECT_THAT(out, ElementsAre("foo", "bar"));
709
710 peer.DecomposeRepresentation(absl::string_view("\0foo\0bar", 8), &out);
711 EXPECT_THAT(out, ElementsAre("", "foo", "bar"));
712
713 peer.DecomposeRepresentation(absl::string_view("foo\0bar\0", 8), &out);
714 EXPECT_THAT(out, ElementsAre("foo", "bar", ""));
715
716 peer.DecomposeRepresentation(absl::string_view("\0foo\0bar\0", 9), &out);
717 EXPECT_THAT(out, ElementsAre("", "foo", "bar", ""));
718 }
719
720 // Test that encoded headers do not have \0-delimited multiple values, as this
721 // became disallowed in HTTP/2 draft-14.
TEST_P(HpackEncoderTest,CrumbleNullByteDelimitedValue)722 TEST_P(HpackEncoderTest, CrumbleNullByteDelimitedValue) {
723 if (strategy_ == kRepresentations) {
724 // When HpackEncoder is asked to encode a list of Representations, the
725 // caller must crumble null-delimited values.
726 return;
727 }
728 Http2HeaderBlock headers;
729 // A header field to be crumbled: "spam: foo\0bar".
730 headers["spam"] = std::string("foo\0bar", 7);
731
732 ExpectIndexedLiteral("spam", "foo");
733 expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
734 expected_.AppendUint32(62);
735 expected_.AppendPrefix(kStringLiteralIdentityEncoded);
736 expected_.AppendUint32(3);
737 expected_.AppendBytes("bar");
738 CompareWithExpectedEncoding(headers);
739 }
740
TEST_P(HpackEncoderTest,HeaderTableSizeUpdate)741 TEST_P(HpackEncoderTest, HeaderTableSizeUpdate) {
742 encoder_.ApplyHeaderTableSizeSetting(1024);
743 ExpectHeaderTableSizeUpdate(1024);
744 ExpectIndexedLiteral("key3", "value3");
745
746 Http2HeaderBlock headers;
747 headers["key3"] = "value3";
748 CompareWithExpectedEncoding(headers);
749
750 HpackEntry* new_entry = peer_.table_peer().dynamic_entries()->front().get();
751 EXPECT_EQ(new_entry->name(), "key3");
752 EXPECT_EQ(new_entry->value(), "value3");
753 }
754
TEST_P(HpackEncoderTest,HeaderTableSizeUpdateWithMin)755 TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithMin) {
756 const size_t starting_size = peer_.table()->settings_size_bound();
757 encoder_.ApplyHeaderTableSizeSetting(starting_size - 2);
758 encoder_.ApplyHeaderTableSizeSetting(starting_size - 1);
759 // We must encode the low watermark, so the peer knows to evict entries
760 // if necessary.
761 ExpectHeaderTableSizeUpdate(starting_size - 2);
762 ExpectHeaderTableSizeUpdate(starting_size - 1);
763 ExpectIndexedLiteral("key3", "value3");
764
765 Http2HeaderBlock headers;
766 headers["key3"] = "value3";
767 CompareWithExpectedEncoding(headers);
768
769 HpackEntry* new_entry = peer_.table_peer().dynamic_entries()->front().get();
770 EXPECT_EQ(new_entry->name(), "key3");
771 EXPECT_EQ(new_entry->value(), "value3");
772 }
773
TEST_P(HpackEncoderTest,HeaderTableSizeUpdateWithExistingSize)774 TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithExistingSize) {
775 encoder_.ApplyHeaderTableSizeSetting(peer_.table()->settings_size_bound());
776 // No encoded size update.
777 ExpectIndexedLiteral("key3", "value3");
778
779 Http2HeaderBlock headers;
780 headers["key3"] = "value3";
781 CompareWithExpectedEncoding(headers);
782
783 HpackEntry* new_entry = peer_.table_peer().dynamic_entries()->front().get();
784 EXPECT_EQ(new_entry->name(), "key3");
785 EXPECT_EQ(new_entry->value(), "value3");
786 }
787
TEST_P(HpackEncoderTest,HeaderTableSizeUpdatesWithGreaterSize)788 TEST_P(HpackEncoderTest, HeaderTableSizeUpdatesWithGreaterSize) {
789 const size_t starting_size = peer_.table()->settings_size_bound();
790 encoder_.ApplyHeaderTableSizeSetting(starting_size + 1);
791 encoder_.ApplyHeaderTableSizeSetting(starting_size + 2);
792 // Only a single size update to the final size.
793 ExpectHeaderTableSizeUpdate(starting_size + 2);
794 ExpectIndexedLiteral("key3", "value3");
795
796 Http2HeaderBlock headers;
797 headers["key3"] = "value3";
798 CompareWithExpectedEncoding(headers);
799
800 HpackEntry* new_entry = peer_.table_peer().dynamic_entries()->front().get();
801 EXPECT_EQ(new_entry->name(), "key3");
802 EXPECT_EQ(new_entry->value(), "value3");
803 }
804
805 } // namespace
806
807 } // namespace spdy
808