xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder_test.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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