OLD | NEW |
| (Empty) |
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 "net/spdy/hpack_encoder.h" | |
6 | |
7 #include <map> | |
8 #include <string> | |
9 | |
10 #include "testing/gmock/include/gmock/gmock.h" | |
11 #include "testing/gtest/include/gtest/gtest.h" | |
12 | |
13 namespace net { | |
14 | |
15 using base::StringPiece; | |
16 using std::string; | |
17 using testing::ElementsAre; | |
18 | |
19 namespace test { | |
20 | |
21 class HpackHeaderTablePeer { | |
22 public: | |
23 explicit HpackHeaderTablePeer(HpackHeaderTable* table) | |
24 : table_(table) {} | |
25 | |
26 HpackHeaderTable::EntryTable* dynamic_entries() { | |
27 return &table_->dynamic_entries_; | |
28 } | |
29 | |
30 private: | |
31 HpackHeaderTable* table_; | |
32 }; | |
33 | |
34 class HpackEncoderPeer { | |
35 public: | |
36 typedef HpackEncoder::Representation Representation; | |
37 typedef HpackEncoder::Representations Representations; | |
38 | |
39 explicit HpackEncoderPeer(HpackEncoder* encoder) | |
40 : encoder_(encoder) {} | |
41 | |
42 HpackHeaderTable* table() { | |
43 return &encoder_->header_table_; | |
44 } | |
45 HpackHeaderTablePeer table_peer() { | |
46 return HpackHeaderTablePeer(table()); | |
47 } | |
48 bool allow_huffman_compression() { | |
49 return encoder_->allow_huffman_compression_; | |
50 } | |
51 void set_allow_huffman_compression(bool allow) { | |
52 encoder_->allow_huffman_compression_ = allow; | |
53 } | |
54 void EmitString(StringPiece str) { | |
55 encoder_->EmitString(str); | |
56 } | |
57 void TakeString(string* out) { | |
58 encoder_->output_stream_.TakeString(out); | |
59 } | |
60 void UpdateCharacterCounts(StringPiece str) { | |
61 encoder_->UpdateCharacterCounts(str); | |
62 } | |
63 static void CookieToCrumbs(StringPiece cookie, | |
64 std::vector<StringPiece>* out) { | |
65 Representations tmp; | |
66 HpackEncoder::CookieToCrumbs(make_pair("", cookie), &tmp); | |
67 | |
68 out->clear(); | |
69 for (size_t i = 0; i != tmp.size(); ++i) { | |
70 out->push_back(tmp[i].second); | |
71 } | |
72 } | |
73 static void DecomposeRepresentation(StringPiece value, | |
74 std::vector<StringPiece>* out) { | |
75 Representations tmp; | |
76 HpackEncoder::DecomposeRepresentation(make_pair("foobar", value), &tmp); | |
77 | |
78 out->clear(); | |
79 for (size_t i = 0; i != tmp.size(); ++i) { | |
80 out->push_back(tmp[i].second); | |
81 } | |
82 } | |
83 | |
84 private: | |
85 HpackEncoder* encoder_; | |
86 }; | |
87 | |
88 } // namespace test | |
89 | |
90 namespace { | |
91 | |
92 using std::map; | |
93 using testing::ElementsAre; | |
94 | |
95 class HpackEncoderTest : public ::testing::Test { | |
96 protected: | |
97 typedef test::HpackEncoderPeer::Representations Representations; | |
98 | |
99 HpackEncoderTest() | |
100 : encoder_(ObtainHpackHuffmanTable()), | |
101 peer_(&encoder_), | |
102 static_(peer_.table()->GetByIndex(1)) {} | |
103 | |
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"); | |
111 | |
112 // No further insertions may occur without evictions. | |
113 peer_.table()->SetMaxSize(peer_.table()->size()); | |
114 | |
115 // Disable Huffman coding by default. Most tests don't care about it. | |
116 peer_.set_allow_huffman_compression(false); | |
117 } | |
118 | |
119 void ExpectIndex(size_t index) { | |
120 expected_.AppendPrefix(kIndexedOpcode); | |
121 expected_.AppendUint32(index); | |
122 } | |
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); | |
129 } | |
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); | |
139 } | |
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); | |
149 } | |
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); | |
155 } | |
156 size_t IndexOf(HpackEntry* entry) { | |
157 return peer_.table()->IndexOf(entry); | |
158 } | |
159 size_t IndexOf(const HpackEntry* entry) { | |
160 return peer_.table()->IndexOf(entry); | |
161 } | |
162 | |
163 HpackEncoder encoder_; | |
164 test::HpackEncoderPeer peer_; | |
165 | |
166 const HpackEntry* static_; | |
167 const HpackEntry* key_1_; | |
168 const HpackEntry* key_2_; | |
169 const HpackEntry* cookie_a_; | |
170 const HpackEntry* cookie_c_; | |
171 | |
172 HpackOutputStream expected_; | |
173 }; | |
174 | |
175 TEST_F(HpackEncoderTest, SingleDynamicIndex) { | |
176 ExpectIndex(IndexOf(key_2_)); | |
177 | |
178 map<string, string> headers; | |
179 headers[key_2_->name()] = key_2_->value(); | |
180 CompareWithExpectedEncoding(headers); | |
181 } | |
182 | |
183 TEST_F(HpackEncoderTest, SingleStaticIndex) { | |
184 ExpectIndex(IndexOf(static_)); | |
185 | |
186 map<string, string> headers; | |
187 headers[static_->name()] = static_->value(); | |
188 CompareWithExpectedEncoding(headers); | |
189 } | |
190 | |
191 TEST_F(HpackEncoderTest, SingleStaticIndexTooLarge) { | |
192 peer_.table()->SetMaxSize(1); // Also evicts all fixtures. | |
193 ExpectIndex(IndexOf(static_)); | |
194 | |
195 map<string, string> headers; | |
196 headers[static_->name()] = static_->value(); | |
197 CompareWithExpectedEncoding(headers); | |
198 | |
199 EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size()); | |
200 } | |
201 | |
202 TEST_F(HpackEncoderTest, SingleLiteralWithIndexName) { | |
203 ExpectIndexedLiteral(key_2_, "value3"); | |
204 | |
205 map<string, string> headers; | |
206 headers[key_2_->name()] = "value3"; | |
207 CompareWithExpectedEncoding(headers); | |
208 | |
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"); | |
213 } | |
214 | |
215 TEST_F(HpackEncoderTest, SingleLiteralWithLiteralName) { | |
216 ExpectIndexedLiteral("key3", "value3"); | |
217 | |
218 map<string, string> headers; | |
219 headers["key3"] = "value3"; | |
220 CompareWithExpectedEncoding(headers); | |
221 | |
222 HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); | |
223 EXPECT_EQ(new_entry->name(), "key3"); | |
224 EXPECT_EQ(new_entry->value(), "value3"); | |
225 } | |
226 | |
227 TEST_F(HpackEncoderTest, SingleLiteralTooLarge) { | |
228 peer_.table()->SetMaxSize(1); // Also evicts all fixtures. | |
229 | |
230 ExpectIndexedLiteral("key3", "value3"); | |
231 | |
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); | |
237 | |
238 EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size()); | |
239 } | |
240 | |
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"); | |
246 | |
247 map<string, string> headers; | |
248 headers[key_1_->name()] = key_1_->value(); | |
249 headers["key3"] = "value3"; | |
250 CompareWithExpectedEncoding(headers); | |
251 } | |
252 | |
253 TEST_F(HpackEncoderTest, CookieHeaderIsCrumbled) { | |
254 ExpectIndex(IndexOf(cookie_a_)); | |
255 ExpectIndex(IndexOf(cookie_c_)); | |
256 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); | |
257 | |
258 map<string, string> headers; | |
259 headers["cookie"] = "e=ff; a=bb; c=dd"; | |
260 CompareWithExpectedEncoding(headers); | |
261 } | |
262 | |
263 TEST_F(HpackEncoderTest, StringsDynamicallySelectHuffmanCoding) { | |
264 peer_.set_allow_huffman_compression(true); | |
265 | |
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_"); | |
271 | |
272 // Non-compactable. Uses identity coding. | |
273 peer_.EmitString("@@@@@@"); | |
274 expected_.AppendPrefix(kStringLiteralIdentityEncoded); | |
275 expected_.AppendUint32(6); | |
276 expected_.AppendBytes("@@@@@@"); | |
277 | |
278 string expected_out, actual_out; | |
279 expected_.TakeString(&expected_out); | |
280 peer_.TakeString(&actual_out); | |
281 EXPECT_EQ(expected_out, actual_out); | |
282 } | |
283 | |
284 TEST_F(HpackEncoderTest, EncodingWithoutCompression) { | |
285 // Implementation should internally disable. | |
286 peer_.set_allow_huffman_compression(true); | |
287 | |
288 ExpectNonIndexedLiteral(":path", "/index.html"); | |
289 ExpectNonIndexedLiteral("cookie", "foo=bar; baz=bing"); | |
290 ExpectNonIndexedLiteral("hello", "goodbye"); | |
291 | |
292 map<string, string> headers; | |
293 headers[":path"] = "/index.html"; | |
294 headers["cookie"] = "foo=bar; baz=bing"; | |
295 headers["hello"] = "goodbye"; | |
296 | |
297 string expected_out, actual_out; | |
298 expected_.TakeString(&expected_out); | |
299 encoder_.EncodeHeaderSetWithoutCompression(headers, &actual_out); | |
300 EXPECT_EQ(expected_out, actual_out); | |
301 } | |
302 | |
303 TEST_F(HpackEncoderTest, MultipleEncodingPasses) { | |
304 // Pass 1. | |
305 { | |
306 map<string, string> headers; | |
307 headers["key1"] = "value1"; | |
308 headers["cookie"] = "a=bb"; | |
309 | |
310 ExpectIndex(IndexOf(cookie_a_)); | |
311 ExpectIndex(IndexOf(key_1_)); | |
312 CompareWithExpectedEncoding(headers); | |
313 } | |
314 // Header table is: | |
315 // 65: key1: value1 | |
316 // 64: key2: value2 | |
317 // 63: cookie: a=bb | |
318 // 62: cookie: c=dd | |
319 // Pass 2. | |
320 { | |
321 map<string, string> headers; | |
322 headers["key2"] = "value2"; | |
323 headers["cookie"] = "c=dd; e=ff"; | |
324 | |
325 ExpectIndex(IndexOf(cookie_c_)); | |
326 // This cookie evicts |key1| from the dynamic table. | |
327 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); | |
328 // "key2: value2" | |
329 ExpectIndex(65); | |
330 | |
331 CompareWithExpectedEncoding(headers); | |
332 } | |
333 // Header table is: | |
334 // 65: key2: value2 | |
335 // 64: cookie: a=bb | |
336 // 63: cookie: c=dd | |
337 // 62: cookie: e=ff | |
338 // Pass 3. | |
339 { | |
340 map<string, string> headers; | |
341 headers["key2"] = "value2"; | |
342 headers["cookie"] = "a=bb; b=cc; c=dd"; | |
343 | |
344 // "cookie: a=bb" | |
345 ExpectIndex(64); | |
346 // This cookie evicts |key2| from the dynamic table. | |
347 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "b=cc"); | |
348 // "cookie: c=dd" | |
349 ExpectIndex(64); | |
350 // "key2: value2" | |
351 ExpectIndexedLiteral("key2", "value2"); | |
352 | |
353 CompareWithExpectedEncoding(headers); | |
354 } | |
355 } | |
356 | |
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"; | |
368 | |
369 // Pseudo-headers are encoded in alphabetical order. | |
370 // This entry pushes "cookie: a=bb" back to 63. | |
371 ExpectIndexedLiteral(peer_.table()->GetByName(":authority"), | |
372 "www.example.com"); | |
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"); | |
377 ExpectIndex(64); | |
378 ExpectIndexedLiteral("foo", "bar"); | |
379 CompareWithExpectedEncoding(headers); | |
380 } | |
381 | |
382 TEST_F(HpackEncoderTest, CookieToCrumbs) { | |
383 test::HpackEncoderPeer peer(NULL); | |
384 std::vector<StringPiece> out; | |
385 | |
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")); | |
392 | |
393 peer.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out); | |
394 EXPECT_THAT(out, ElementsAre("", "baz =bing", "foo = bar ")); | |
395 | |
396 peer.CookieToCrumbs("baz=bing; foo=bar; baz=bing", &out); | |
397 EXPECT_THAT(out, ElementsAre("baz=bing", "foo=bar")); | |
398 | |
399 peer.CookieToCrumbs("baz=bing", &out); | |
400 EXPECT_THAT(out, ElementsAre("baz=bing")); | |
401 | |
402 peer.CookieToCrumbs("", &out); | |
403 EXPECT_THAT(out, ElementsAre("")); | |
404 | |
405 peer.CookieToCrumbs("foo;bar; baz;baz;bing;", &out); | |
406 EXPECT_THAT(out, ElementsAre("", "bar", "baz", "bing", "foo")); | |
407 } | |
408 | |
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); | |
413 | |
414 char kTestString[] = "foo\0\1\xff""boo"; | |
415 peer_.UpdateCharacterCounts( | |
416 StringPiece(kTestString, arraysize(kTestString) - 1)); | |
417 | |
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; | |
425 | |
426 EXPECT_EQ(expect, counts); | |
427 EXPECT_EQ(9u, total_counts); | |
428 } | |
429 | |
430 TEST_F(HpackEncoderTest, DecomposeRepresentation) { | |
431 test::HpackEncoderPeer peer(NULL); | |
432 std::vector<StringPiece> out; | |
433 | |
434 peer.DecomposeRepresentation("", &out); | |
435 EXPECT_THAT(out, ElementsAre("")); | |
436 | |
437 peer.DecomposeRepresentation("foobar", &out); | |
438 EXPECT_THAT(out, ElementsAre("foobar")); | |
439 | |
440 peer.DecomposeRepresentation(StringPiece("foo\0bar", 7), &out); | |
441 EXPECT_THAT(out, ElementsAre("foo", "bar")); | |
442 | |
443 peer.DecomposeRepresentation(StringPiece("\0foo\0bar", 8), &out); | |
444 EXPECT_THAT(out, ElementsAre("", "foo", "bar")); | |
445 | |
446 peer.DecomposeRepresentation(StringPiece("foo\0bar\0", 8), &out); | |
447 EXPECT_THAT(out, ElementsAre("foo", "bar", "")); | |
448 | |
449 peer.DecomposeRepresentation(StringPiece("\0foo\0bar\0", 9), &out); | |
450 EXPECT_THAT(out, ElementsAre("", "foo", "bar", "")); | |
451 } | |
452 | |
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); | |
459 | |
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); | |
467 } | |
468 | |
469 } // namespace | |
470 | |
471 } // namespace net | |
OLD | NEW |