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.
5 #include "net/spdy/hpack_encoder.h"
10 #include "testing/gmock/include/gmock/gmock.h"
11 #include "testing/gtest/include/gtest/gtest.h"
15 using base::StringPiece
;
17 using testing::ElementsAre
;
21 class HpackHeaderTablePeer
{
23 explicit HpackHeaderTablePeer(HpackHeaderTable
* table
)
26 HpackHeaderTable::EntryTable
* dynamic_entries() {
27 return &table_
->dynamic_entries_
;
31 HpackHeaderTable
* table_
;
34 class HpackEncoderPeer
{
36 typedef HpackEncoder::Representation Representation
;
37 typedef HpackEncoder::Representations Representations
;
39 explicit HpackEncoderPeer(HpackEncoder
* encoder
)
40 : encoder_(encoder
) {}
42 HpackHeaderTable
* table() {
43 return &encoder_
->header_table_
;
45 HpackHeaderTablePeer
table_peer() {
46 return HpackHeaderTablePeer(table());
48 bool allow_huffman_compression() {
49 return encoder_
->allow_huffman_compression_
;
51 void set_allow_huffman_compression(bool allow
) {
52 encoder_
->allow_huffman_compression_
= allow
;
54 void EmitString(StringPiece str
) {
55 encoder_
->EmitString(str
);
57 void TakeString(string
* out
) {
58 encoder_
->output_stream_
.TakeString(out
);
60 void UpdateCharacterCounts(StringPiece str
) {
61 encoder_
->UpdateCharacterCounts(str
);
63 static void CookieToCrumbs(StringPiece cookie
,
64 std::vector
<StringPiece
>* out
) {
66 HpackEncoder::CookieToCrumbs(make_pair("", cookie
), &tmp
);
69 for (size_t i
= 0; i
!= tmp
.size(); ++i
) {
70 out
->push_back(tmp
[i
].second
);
73 static void DecomposeRepresentation(StringPiece value
,
74 std::vector
<StringPiece
>* out
) {
76 HpackEncoder::DecomposeRepresentation(make_pair("foobar", value
), &tmp
);
79 for (size_t i
= 0; i
!= tmp
.size(); ++i
) {
80 out
->push_back(tmp
[i
].second
);
85 HpackEncoder
* encoder_
;
93 using testing::ElementsAre
;
95 class HpackEncoderTest
: public ::testing::Test
{
97 typedef test::HpackEncoderPeer::Representations Representations
;
100 : encoder_(ObtainHpackHuffmanTable()),
102 static_(peer_
.table()->GetByIndex(1)) {}
104 void SetUp() override
{
105 // Populate dynamic entries into the table fixture. For simplicity each
106 // entry has name.size() + value.size() == 10.
107 key_1_
= peer_
.table()->TryAddEntry("key1", "value1");
108 key_2_
= peer_
.table()->TryAddEntry("key2", "value2");
109 cookie_a_
= peer_
.table()->TryAddEntry("cookie", "a=bb");
110 cookie_c_
= peer_
.table()->TryAddEntry("cookie", "c=dd");
112 // No further insertions may occur without evictions.
113 peer_
.table()->SetMaxSize(peer_
.table()->size());
115 // Disable Huffman coding by default. Most tests don't care about it.
116 peer_
.set_allow_huffman_compression(false);
119 void ExpectIndex(size_t index
) {
120 expected_
.AppendPrefix(kIndexedOpcode
);
121 expected_
.AppendUint32(index
);
123 void ExpectIndexedLiteral(const HpackEntry
* key_entry
, StringPiece value
) {
124 expected_
.AppendPrefix(kLiteralIncrementalIndexOpcode
);
125 expected_
.AppendUint32(IndexOf(key_entry
));
126 expected_
.AppendPrefix(kStringLiteralIdentityEncoded
);
127 expected_
.AppendUint32(value
.size());
128 expected_
.AppendBytes(value
);
130 void ExpectIndexedLiteral(StringPiece name
, StringPiece value
) {
131 expected_
.AppendPrefix(kLiteralIncrementalIndexOpcode
);
132 expected_
.AppendUint32(0);
133 expected_
.AppendPrefix(kStringLiteralIdentityEncoded
);
134 expected_
.AppendUint32(name
.size());
135 expected_
.AppendBytes(name
);
136 expected_
.AppendPrefix(kStringLiteralIdentityEncoded
);
137 expected_
.AppendUint32(value
.size());
138 expected_
.AppendBytes(value
);
140 void ExpectNonIndexedLiteral(StringPiece name
, StringPiece value
) {
141 expected_
.AppendPrefix(kLiteralNoIndexOpcode
);
142 expected_
.AppendUint32(0);
143 expected_
.AppendPrefix(kStringLiteralIdentityEncoded
);
144 expected_
.AppendUint32(name
.size());
145 expected_
.AppendBytes(name
);
146 expected_
.AppendPrefix(kStringLiteralIdentityEncoded
);
147 expected_
.AppendUint32(value
.size());
148 expected_
.AppendBytes(value
);
150 void CompareWithExpectedEncoding(const map
<string
, string
>& header_set
) {
151 string expected_out
, actual_out
;
152 expected_
.TakeString(&expected_out
);
153 EXPECT_TRUE(encoder_
.EncodeHeaderSet(header_set
, &actual_out
));
154 EXPECT_EQ(expected_out
, actual_out
);
156 size_t IndexOf(HpackEntry
* entry
) {
157 return peer_
.table()->IndexOf(entry
);
159 size_t IndexOf(const HpackEntry
* entry
) {
160 return peer_
.table()->IndexOf(entry
);
163 HpackEncoder encoder_
;
164 test::HpackEncoderPeer peer_
;
166 const HpackEntry
* static_
;
167 const HpackEntry
* key_1_
;
168 const HpackEntry
* key_2_
;
169 const HpackEntry
* cookie_a_
;
170 const HpackEntry
* cookie_c_
;
172 HpackOutputStream expected_
;
175 TEST_F(HpackEncoderTest
, SingleDynamicIndex
) {
176 ExpectIndex(IndexOf(key_2_
));
178 map
<string
, string
> headers
;
179 headers
[key_2_
->name()] = key_2_
->value();
180 CompareWithExpectedEncoding(headers
);
183 TEST_F(HpackEncoderTest
, SingleStaticIndex
) {
184 ExpectIndex(IndexOf(static_
));
186 map
<string
, string
> headers
;
187 headers
[static_
->name()] = static_
->value();
188 CompareWithExpectedEncoding(headers
);
191 TEST_F(HpackEncoderTest
, SingleStaticIndexTooLarge
) {
192 peer_
.table()->SetMaxSize(1); // Also evicts all fixtures.
193 ExpectIndex(IndexOf(static_
));
195 map
<string
, string
> headers
;
196 headers
[static_
->name()] = static_
->value();
197 CompareWithExpectedEncoding(headers
);
199 EXPECT_EQ(0u, peer_
.table_peer().dynamic_entries()->size());
202 TEST_F(HpackEncoderTest
, SingleLiteralWithIndexName
) {
203 ExpectIndexedLiteral(key_2_
, "value3");
205 map
<string
, string
> headers
;
206 headers
[key_2_
->name()] = "value3";
207 CompareWithExpectedEncoding(headers
);
209 // A new entry was inserted and added to the reference set.
210 HpackEntry
* new_entry
= &peer_
.table_peer().dynamic_entries()->front();
211 EXPECT_EQ(new_entry
->name(), key_2_
->name());
212 EXPECT_EQ(new_entry
->value(), "value3");
215 TEST_F(HpackEncoderTest
, SingleLiteralWithLiteralName
) {
216 ExpectIndexedLiteral("key3", "value3");
218 map
<string
, string
> headers
;
219 headers
["key3"] = "value3";
220 CompareWithExpectedEncoding(headers
);
222 HpackEntry
* new_entry
= &peer_
.table_peer().dynamic_entries()->front();
223 EXPECT_EQ(new_entry
->name(), "key3");
224 EXPECT_EQ(new_entry
->value(), "value3");
227 TEST_F(HpackEncoderTest
, SingleLiteralTooLarge
) {
228 peer_
.table()->SetMaxSize(1); // Also evicts all fixtures.
230 ExpectIndexedLiteral("key3", "value3");
232 // A header overflowing the header table is still emitted.
233 // The header table is empty.
234 map
<string
, string
> headers
;
235 headers
["key3"] = "value3";
236 CompareWithExpectedEncoding(headers
);
238 EXPECT_EQ(0u, peer_
.table_peer().dynamic_entries()->size());
241 TEST_F(HpackEncoderTest
, EmitThanEvict
) {
242 // |key_1_| is toggled and placed into the reference set,
243 // and then immediately evicted by "key3".
244 ExpectIndex(IndexOf(key_1_
));
245 ExpectIndexedLiteral("key3", "value3");
247 map
<string
, string
> headers
;
248 headers
[key_1_
->name()] = key_1_
->value();
249 headers
["key3"] = "value3";
250 CompareWithExpectedEncoding(headers
);
253 TEST_F(HpackEncoderTest
, CookieHeaderIsCrumbled
) {
254 ExpectIndex(IndexOf(cookie_a_
));
255 ExpectIndex(IndexOf(cookie_c_
));
256 ExpectIndexedLiteral(peer_
.table()->GetByName("cookie"), "e=ff");
258 map
<string
, string
> headers
;
259 headers
["cookie"] = "e=ff; a=bb; c=dd";
260 CompareWithExpectedEncoding(headers
);
263 TEST_F(HpackEncoderTest
, StringsDynamicallySelectHuffmanCoding
) {
264 peer_
.set_allow_huffman_compression(true);
266 // Compactable string. Uses Huffman coding.
267 peer_
.EmitString("feedbeef");
268 expected_
.AppendPrefix(kStringLiteralHuffmanEncoded
);
269 expected_
.AppendUint32(6);
270 expected_
.AppendBytes("\x94\xA5\x92""2\x96_");
272 // Non-compactable. Uses identity coding.
273 peer_
.EmitString("@@@@@@");
274 expected_
.AppendPrefix(kStringLiteralIdentityEncoded
);
275 expected_
.AppendUint32(6);
276 expected_
.AppendBytes("@@@@@@");
278 string expected_out
, actual_out
;
279 expected_
.TakeString(&expected_out
);
280 peer_
.TakeString(&actual_out
);
281 EXPECT_EQ(expected_out
, actual_out
);
284 TEST_F(HpackEncoderTest
, EncodingWithoutCompression
) {
285 // Implementation should internally disable.
286 peer_
.set_allow_huffman_compression(true);
288 ExpectNonIndexedLiteral(":path", "/index.html");
289 ExpectNonIndexedLiteral("cookie", "foo=bar; baz=bing");
290 ExpectNonIndexedLiteral("hello", "goodbye");
292 map
<string
, string
> headers
;
293 headers
[":path"] = "/index.html";
294 headers
["cookie"] = "foo=bar; baz=bing";
295 headers
["hello"] = "goodbye";
297 string expected_out
, actual_out
;
298 expected_
.TakeString(&expected_out
);
299 encoder_
.EncodeHeaderSetWithoutCompression(headers
, &actual_out
);
300 EXPECT_EQ(expected_out
, actual_out
);
303 TEST_F(HpackEncoderTest
, MultipleEncodingPasses
) {
306 map
<string
, string
> headers
;
307 headers
["key1"] = "value1";
308 headers
["cookie"] = "a=bb";
310 ExpectIndex(IndexOf(cookie_a_
));
311 ExpectIndex(IndexOf(key_1_
));
312 CompareWithExpectedEncoding(headers
);
321 map
<string
, string
> headers
;
322 headers
["key2"] = "value2";
323 headers
["cookie"] = "c=dd; e=ff";
325 ExpectIndex(IndexOf(cookie_c_
));
326 // This cookie evicts |key1| from the dynamic table.
327 ExpectIndexedLiteral(peer_
.table()->GetByName("cookie"), "e=ff");
331 CompareWithExpectedEncoding(headers
);
340 map
<string
, string
> headers
;
341 headers
["key2"] = "value2";
342 headers
["cookie"] = "a=bb; b=cc; c=dd";
346 // This cookie evicts |key2| from the dynamic table.
347 ExpectIndexedLiteral(peer_
.table()->GetByName("cookie"), "b=cc");
351 ExpectIndexedLiteral("key2", "value2");
353 CompareWithExpectedEncoding(headers
);
357 TEST_F(HpackEncoderTest
, PseudoHeadersFirst
) {
358 map
<string
, string
> headers
;
359 // A pseudo-header to be indexed.
360 headers
[":authority"] = "www.example.com";
361 // A pseudo-header that should not be indexed.
362 headers
[":path"] = "/spam/eggs.html";
363 // A regular header which precedes ":" alphabetically, should still be encoded
364 // after pseudo-headers.
365 headers
["-foo"] = "bar";
366 headers
["foo"] = "bar";
367 headers
["cookie"] = "c=dd";
369 // Pseudo-headers are encoded in alphabetical order.
370 // This entry pushes "cookie: a=bb" back to 63.
371 ExpectIndexedLiteral(peer_
.table()->GetByName(":authority"),
373 ExpectNonIndexedLiteral(":path", "/spam/eggs.html");
374 // Regular headers are endoded in alphabetical order.
375 // This entry pushes "cookie: a=bb" back to 64.
376 ExpectIndexedLiteral("-foo", "bar");
378 ExpectIndexedLiteral("foo", "bar");
379 CompareWithExpectedEncoding(headers
);
382 TEST_F(HpackEncoderTest
, CookieToCrumbs
) {
383 test::HpackEncoderPeer
peer(NULL
);
384 std::vector
<StringPiece
> out
;
386 // A space after ';' is consumed. All other spaces remain. ';' at beginning
387 // and end of string produce empty crumbs. Duplicate crumbs are removed.
388 // See section 8.1.3.4 "Compressing the Cookie Header Field" in the HTTP/2
389 // specification at http://tools.ietf.org/html/draft-ietf-httpbis-http2-11
390 peer
.CookieToCrumbs(" foo=1;bar=2 ; bar=3; bing=4; ", &out
);
391 EXPECT_THAT(out
, ElementsAre("", " bing=4", " foo=1", "bar=2 ", "bar=3"));
393 peer
.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out
);
394 EXPECT_THAT(out
, ElementsAre("", "baz =bing", "foo = bar "));
396 peer
.CookieToCrumbs("baz=bing; foo=bar; baz=bing", &out
);
397 EXPECT_THAT(out
, ElementsAre("baz=bing", "foo=bar"));
399 peer
.CookieToCrumbs("baz=bing", &out
);
400 EXPECT_THAT(out
, ElementsAre("baz=bing"));
402 peer
.CookieToCrumbs("", &out
);
403 EXPECT_THAT(out
, ElementsAre(""));
405 peer
.CookieToCrumbs("foo;bar; baz;baz;bing;", &out
);
406 EXPECT_THAT(out
, ElementsAre("", "bar", "baz", "bing", "foo"));
409 TEST_F(HpackEncoderTest
, UpdateCharacterCounts
) {
410 std::vector
<size_t> counts(256, 0);
411 size_t total_counts
= 0;
412 encoder_
.SetCharCountsStorage(&counts
, &total_counts
);
414 char kTestString
[] = "foo\0\1\xff""boo";
415 peer_
.UpdateCharacterCounts(
416 StringPiece(kTestString
, arraysize(kTestString
) - 1));
418 std::vector
<size_t> expect(256, 0);
419 expect
[static_cast<uint8
>('f')] = 1;
420 expect
[static_cast<uint8
>('o')] = 4;
421 expect
[static_cast<uint8
>('\0')] = 1;
422 expect
[static_cast<uint8
>('\1')] = 1;
423 expect
[static_cast<uint8
>('\xff')] = 1;
424 expect
[static_cast<uint8
>('b')] = 1;
426 EXPECT_EQ(expect
, counts
);
427 EXPECT_EQ(9u, total_counts
);
430 TEST_F(HpackEncoderTest
, DecomposeRepresentation
) {
431 test::HpackEncoderPeer
peer(NULL
);
432 std::vector
<StringPiece
> out
;
434 peer
.DecomposeRepresentation("", &out
);
435 EXPECT_THAT(out
, ElementsAre(""));
437 peer
.DecomposeRepresentation("foobar", &out
);
438 EXPECT_THAT(out
, ElementsAre("foobar"));
440 peer
.DecomposeRepresentation(StringPiece("foo\0bar", 7), &out
);
441 EXPECT_THAT(out
, ElementsAre("foo", "bar"));
443 peer
.DecomposeRepresentation(StringPiece("\0foo\0bar", 8), &out
);
444 EXPECT_THAT(out
, ElementsAre("", "foo", "bar"));
446 peer
.DecomposeRepresentation(StringPiece("foo\0bar\0", 8), &out
);
447 EXPECT_THAT(out
, ElementsAre("foo", "bar", ""));
449 peer
.DecomposeRepresentation(StringPiece("\0foo\0bar\0", 9), &out
);
450 EXPECT_THAT(out
, ElementsAre("", "foo", "bar", ""));
453 // Test that encoded headers do not have \0-delimited multiple values, as this
454 // became disallowed in HTTP/2 draft-14.
455 TEST_F(HpackEncoderTest
, CrumbleNullByteDelimitedValue
) {
456 map
<string
, string
> headers
;
457 // A header field to be crumbled: "spam: foo\0bar".
458 headers
["spam"] = string("foo\0bar", 7);
460 ExpectIndexedLiteral("spam", "foo");
461 expected_
.AppendPrefix(kLiteralIncrementalIndexOpcode
);
462 expected_
.AppendUint32(62);
463 expected_
.AppendPrefix(kStringLiteralIdentityEncoded
);
464 expected_
.AppendUint32(3);
465 expected_
.AppendBytes("bar");
466 CompareWithExpectedEncoding(headers
);