| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/spdy/hpack/hpack_encoder.h" | 5 #include "net/spdy/hpack/hpack_encoder.h" |
| 6 | 6 |
| 7 #include <map> | 7 #include <map> |
| 8 #include <string> | 8 #include <string> |
| 9 | 9 |
| 10 #include "base/rand_util.h" | 10 #include "base/rand_util.h" |
| 11 #include "net/base/arena.h" | 11 #include "net/base/arena.h" |
| 12 #include "net/spdy/hpack/hpack_huffman_table.h" |
| 12 #include "testing/gmock/include/gmock/gmock.h" | 13 #include "testing/gmock/include/gmock/gmock.h" |
| 13 #include "testing/gtest/include/gtest/gtest.h" | 14 #include "testing/gtest/include/gtest/gtest.h" |
| 14 | 15 |
| 15 namespace net { | 16 namespace net { |
| 16 | 17 |
| 17 using base::StringPiece; | 18 using base::StringPiece; |
| 18 using std::string; | 19 using std::string; |
| 19 using std::vector; | 20 using std::vector; |
| 20 using std::pair; | 21 using std::pair; |
| 21 using testing::ElementsAre; | 22 using testing::ElementsAre; |
| (...skipping 12 matching lines...) Expand all Loading... |
| 34 HpackHeaderTable* table_; | 35 HpackHeaderTable* table_; |
| 35 }; | 36 }; |
| 36 | 37 |
| 37 class HpackEncoderPeer { | 38 class HpackEncoderPeer { |
| 38 public: | 39 public: |
| 39 typedef HpackEncoder::Representation Representation; | 40 typedef HpackEncoder::Representation Representation; |
| 40 typedef HpackEncoder::Representations Representations; | 41 typedef HpackEncoder::Representations Representations; |
| 41 | 42 |
| 42 explicit HpackEncoderPeer(HpackEncoder* encoder) : encoder_(encoder) {} | 43 explicit HpackEncoderPeer(HpackEncoder* encoder) : encoder_(encoder) {} |
| 43 | 44 |
| 45 bool compression_enabled() const { return encoder_->enable_compression_; } |
| 44 HpackHeaderTable* table() { return &encoder_->header_table_; } | 46 HpackHeaderTable* table() { return &encoder_->header_table_; } |
| 45 HpackHeaderTablePeer table_peer() { return HpackHeaderTablePeer(table()); } | 47 HpackHeaderTablePeer table_peer() { return HpackHeaderTablePeer(table()); } |
| 46 void set_allow_huffman_compression(bool allow) { | 48 const HpackHuffmanTable& huffman_table() const { |
| 47 encoder_->allow_huffman_compression_ = allow; | 49 return encoder_->huffman_table_; |
| 48 } | 50 } |
| 49 void EmitString(StringPiece str) { encoder_->EmitString(str); } | 51 void EmitString(StringPiece str) { encoder_->EmitString(str); } |
| 50 void TakeString(string* out) { encoder_->output_stream_.TakeString(out); } | 52 void TakeString(string* out) { encoder_->output_stream_.TakeString(out); } |
| 51 static void CookieToCrumbs(StringPiece cookie, | 53 static void CookieToCrumbs(StringPiece cookie, |
| 52 std::vector<StringPiece>* out) { | 54 std::vector<StringPiece>* out) { |
| 53 Representations tmp; | 55 Representations tmp; |
| 54 HpackEncoder::CookieToCrumbs(std::make_pair("", cookie), &tmp); | 56 HpackEncoder::CookieToCrumbs(std::make_pair("", cookie), &tmp); |
| 55 | 57 |
| 56 out->clear(); | 58 out->clear(); |
| 57 for (size_t i = 0; i != tmp.size(); ++i) { | 59 for (size_t i = 0; i != tmp.size(); ++i) { |
| (...skipping 10 matching lines...) Expand all Loading... |
| 68 for (size_t i = 0; i != tmp.size(); ++i) { | 70 for (size_t i = 0; i != tmp.size(); ++i) { |
| 69 out->push_back(tmp[i].second); | 71 out->push_back(tmp[i].second); |
| 70 } | 72 } |
| 71 } | 73 } |
| 72 | 74 |
| 73 // TODO(dahollings): Remove or clean up these methods when deprecating | 75 // TODO(dahollings): Remove or clean up these methods when deprecating |
| 74 // non-incremental encoding path. | 76 // non-incremental encoding path. |
| 75 static bool EncodeHeaderSet(HpackEncoder* encoder, | 77 static bool EncodeHeaderSet(HpackEncoder* encoder, |
| 76 const SpdyHeaderBlock& header_set, | 78 const SpdyHeaderBlock& header_set, |
| 77 string* output, | 79 string* output, |
| 78 bool use_compression, | |
| 79 bool use_incremental) { | 80 bool use_incremental) { |
| 80 if (use_incremental) { | 81 if (use_incremental) { |
| 81 return EncodeIncremental(encoder, header_set, output, use_compression); | 82 return EncodeIncremental(encoder, header_set, output); |
| 82 } else { | 83 } else { |
| 83 return use_compression ? encoder->EncodeHeaderSet(header_set, output) | 84 return encoder->EncodeHeaderSet(header_set, output); |
| 84 : encoder->EncodeHeaderSetWithoutCompression( | |
| 85 header_set, output); | |
| 86 } | 85 } |
| 87 } | 86 } |
| 88 | 87 |
| 89 static bool EncodeIncremental(HpackEncoder* encoder, | 88 static bool EncodeIncremental(HpackEncoder* encoder, |
| 90 const SpdyHeaderBlock& header_set, | 89 const SpdyHeaderBlock& header_set, |
| 91 string* output, | 90 string* output) { |
| 92 bool use_compression) { | |
| 93 std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator = | 91 std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator = |
| 94 encoder->EncodeHeaderSet(header_set, use_compression); | 92 encoder->EncodeHeaderSet(header_set); |
| 95 string output_buffer; | 93 string output_buffer; |
| 96 encoderator->Next(base::RandInt(0, 15), &output_buffer); | 94 encoderator->Next(base::RandInt(0, 15), &output_buffer); |
| 97 while (encoderator->HasNext()) { | 95 while (encoderator->HasNext()) { |
| 98 string second_buffer; | 96 string second_buffer; |
| 99 encoderator->Next(base::RandInt(0, 15), &second_buffer); | 97 encoderator->Next(base::RandInt(0, 15), &second_buffer); |
| 100 output_buffer.append(second_buffer); | 98 output_buffer.append(second_buffer); |
| 101 } | 99 } |
| 102 *output = std::move(output_buffer); | 100 *output = std::move(output_buffer); |
| 103 return true; | 101 return true; |
| 104 } | 102 } |
| (...skipping 25 matching lines...) Expand all Loading... |
| 130 | 128 |
| 131 // Populate dynamic entries into the table fixture. For simplicity each | 129 // Populate dynamic entries into the table fixture. For simplicity each |
| 132 // entry has name.size() + value.size() == 10. | 130 // entry has name.size() + value.size() == 10. |
| 133 key_1_ = peer_.table()->TryAddEntry("key1", "value1"); | 131 key_1_ = peer_.table()->TryAddEntry("key1", "value1"); |
| 134 key_2_ = peer_.table()->TryAddEntry("key2", "value2"); | 132 key_2_ = peer_.table()->TryAddEntry("key2", "value2"); |
| 135 cookie_a_ = peer_.table()->TryAddEntry("cookie", "a=bb"); | 133 cookie_a_ = peer_.table()->TryAddEntry("cookie", "a=bb"); |
| 136 cookie_c_ = peer_.table()->TryAddEntry("cookie", "c=dd"); | 134 cookie_c_ = peer_.table()->TryAddEntry("cookie", "c=dd"); |
| 137 | 135 |
| 138 // No further insertions may occur without evictions. | 136 // No further insertions may occur without evictions. |
| 139 peer_.table()->SetMaxSize(peer_.table()->size()); | 137 peer_.table()->SetMaxSize(peer_.table()->size()); |
| 140 | |
| 141 // Disable Huffman coding by default. Most tests don't care about it. | |
| 142 peer_.set_allow_huffman_compression(false); | |
| 143 } | 138 } |
| 144 | 139 |
| 145 void SaveHeaders(StringPiece name, StringPiece value) { | 140 void SaveHeaders(StringPiece name, StringPiece value) { |
| 146 StringPiece n(headers_storage_.Memdup(name.data(), name.size()), | 141 StringPiece n(headers_storage_.Memdup(name.data(), name.size()), |
| 147 name.size()); | 142 name.size()); |
| 148 StringPiece v(headers_storage_.Memdup(value.data(), value.size()), | 143 StringPiece v(headers_storage_.Memdup(value.data(), value.size()), |
| 149 value.size()); | 144 value.size()); |
| 150 headers_observed_.push_back(make_pair(n, v)); | 145 headers_observed_.push_back(make_pair(n, v)); |
| 151 } | 146 } |
| 152 | 147 |
| 153 void ExpectIndex(size_t index) { | 148 void ExpectIndex(size_t index) { |
| 154 expected_.AppendPrefix(kIndexedOpcode); | 149 expected_.AppendPrefix(kIndexedOpcode); |
| 155 expected_.AppendUint32(index); | 150 expected_.AppendUint32(index); |
| 156 } | 151 } |
| 157 void ExpectIndexedLiteral(const HpackEntry* key_entry, StringPiece value) { | 152 void ExpectIndexedLiteral(const HpackEntry* key_entry, StringPiece value) { |
| 158 expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); | 153 expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); |
| 159 expected_.AppendUint32(IndexOf(key_entry)); | 154 expected_.AppendUint32(IndexOf(key_entry)); |
| 160 expected_.AppendPrefix(kStringLiteralIdentityEncoded); | 155 ExpectString(&expected_, value); |
| 161 expected_.AppendUint32(value.size()); | |
| 162 expected_.AppendBytes(value); | |
| 163 } | 156 } |
| 164 void ExpectIndexedLiteral(StringPiece name, StringPiece value) { | 157 void ExpectIndexedLiteral(StringPiece name, StringPiece value) { |
| 165 expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); | 158 expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); |
| 166 expected_.AppendUint32(0); | 159 expected_.AppendUint32(0); |
| 167 expected_.AppendPrefix(kStringLiteralIdentityEncoded); | 160 ExpectString(&expected_, name); |
| 168 expected_.AppendUint32(name.size()); | 161 ExpectString(&expected_, value); |
| 169 expected_.AppendBytes(name); | |
| 170 expected_.AppendPrefix(kStringLiteralIdentityEncoded); | |
| 171 expected_.AppendUint32(value.size()); | |
| 172 expected_.AppendBytes(value); | |
| 173 } | 162 } |
| 174 void ExpectNonIndexedLiteral(StringPiece name, StringPiece value) { | 163 void ExpectNonIndexedLiteral(StringPiece name, StringPiece value) { |
| 175 expected_.AppendPrefix(kLiteralNoIndexOpcode); | 164 expected_.AppendPrefix(kLiteralNoIndexOpcode); |
| 176 expected_.AppendUint32(0); | 165 expected_.AppendUint32(0); |
| 177 expected_.AppendPrefix(kStringLiteralIdentityEncoded); | 166 ExpectString(&expected_, name); |
| 178 expected_.AppendUint32(name.size()); | 167 ExpectString(&expected_, value); |
| 179 expected_.AppendBytes(name); | 168 } |
| 180 expected_.AppendPrefix(kStringLiteralIdentityEncoded); | 169 void ExpectString(HpackOutputStream* stream, StringPiece str) { |
| 181 expected_.AppendUint32(value.size()); | 170 const HpackHuffmanTable& huffman_table = peer_.huffman_table(); |
| 182 expected_.AppendBytes(value); | 171 size_t encoded_size = peer_.compression_enabled() |
| 172 ? huffman_table.EncodedSize(str) |
| 173 : str.size(); |
| 174 if (encoded_size < str.size()) { |
| 175 expected_.AppendPrefix(kStringLiteralHuffmanEncoded); |
| 176 expected_.AppendUint32(encoded_size); |
| 177 huffman_table.EncodeString(str, stream); |
| 178 } else { |
| 179 expected_.AppendPrefix(kStringLiteralIdentityEncoded); |
| 180 expected_.AppendUint32(str.size()); |
| 181 expected_.AppendBytes(str); |
| 182 } |
| 183 } | 183 } |
| 184 void ExpectHeaderTableSizeUpdate(uint32_t size) { | 184 void ExpectHeaderTableSizeUpdate(uint32_t size) { |
| 185 expected_.AppendPrefix(kHeaderTableSizeUpdateOpcode); | 185 expected_.AppendPrefix(kHeaderTableSizeUpdateOpcode); |
| 186 expected_.AppendUint32(size); | 186 expected_.AppendUint32(size); |
| 187 } | 187 } |
| 188 void CompareWithExpectedEncoding(const SpdyHeaderBlock& header_set) { | 188 void CompareWithExpectedEncoding(const SpdyHeaderBlock& header_set) { |
| 189 string expected_out, actual_out; | 189 string expected_out, actual_out; |
| 190 expected_.TakeString(&expected_out); | 190 expected_.TakeString(&expected_out); |
| 191 EXPECT_TRUE(test::HpackEncoderPeer::EncodeHeaderSet( | 191 EXPECT_TRUE(test::HpackEncoderPeer::EncodeHeaderSet( |
| 192 &encoder_, header_set, &actual_out, true, use_incremental_)); | 192 &encoder_, header_set, &actual_out, use_incremental_)); |
| 193 EXPECT_EQ(expected_out, actual_out); | |
| 194 } | |
| 195 void CompareWithExpectedEncodingWithoutCompression( | |
| 196 const SpdyHeaderBlock& header_set) { | |
| 197 string expected_out, actual_out; | |
| 198 expected_.TakeString(&expected_out); | |
| 199 EXPECT_TRUE(test::HpackEncoderPeer::EncodeHeaderSet( | |
| 200 &encoder_, header_set, &actual_out, false, use_incremental_)); | |
| 201 EXPECT_EQ(expected_out, actual_out); | 193 EXPECT_EQ(expected_out, actual_out); |
| 202 } | 194 } |
| 203 size_t IndexOf(const HpackEntry* entry) { | 195 size_t IndexOf(const HpackEntry* entry) { |
| 204 return peer_.table()->IndexOf(entry); | 196 return peer_.table()->IndexOf(entry); |
| 205 } | 197 } |
| 206 | 198 |
| 207 HpackEncoder encoder_; | 199 HpackEncoder encoder_; |
| 208 test::HpackEncoderPeer peer_; | 200 test::HpackEncoderPeer peer_; |
| 209 | 201 |
| 210 const HpackEntry* static_; | 202 const HpackEntry* static_; |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 310 ExpectIndex(IndexOf(cookie_a_)); | 302 ExpectIndex(IndexOf(cookie_a_)); |
| 311 ExpectIndex(IndexOf(cookie_c_)); | 303 ExpectIndex(IndexOf(cookie_c_)); |
| 312 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); | 304 ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); |
| 313 | 305 |
| 314 SpdyHeaderBlock headers; | 306 SpdyHeaderBlock headers; |
| 315 headers["cookie"] = "a=bb; c=dd; e=ff"; | 307 headers["cookie"] = "a=bb; c=dd; e=ff"; |
| 316 CompareWithExpectedEncoding(headers); | 308 CompareWithExpectedEncoding(headers); |
| 317 } | 309 } |
| 318 | 310 |
| 319 TEST_P(HpackEncoderTest, StringsDynamicallySelectHuffmanCoding) { | 311 TEST_P(HpackEncoderTest, StringsDynamicallySelectHuffmanCoding) { |
| 320 peer_.set_allow_huffman_compression(true); | |
| 321 | |
| 322 // Compactable string. Uses Huffman coding. | 312 // Compactable string. Uses Huffman coding. |
| 323 peer_.EmitString("feedbeef"); | 313 peer_.EmitString("feedbeef"); |
| 324 expected_.AppendPrefix(kStringLiteralHuffmanEncoded); | 314 expected_.AppendPrefix(kStringLiteralHuffmanEncoded); |
| 325 expected_.AppendUint32(6); | 315 expected_.AppendUint32(6); |
| 326 expected_.AppendBytes("\x94\xA5\x92\x32\x96_"); | 316 expected_.AppendBytes("\x94\xA5\x92\x32\x96_"); |
| 327 | 317 |
| 328 // Non-compactable. Uses identity coding. | 318 // Non-compactable. Uses identity coding. |
| 329 peer_.EmitString("@@@@@@"); | 319 peer_.EmitString("@@@@@@"); |
| 330 expected_.AppendPrefix(kStringLiteralIdentityEncoded); | 320 expected_.AppendPrefix(kStringLiteralIdentityEncoded); |
| 331 expected_.AppendUint32(6); | 321 expected_.AppendUint32(6); |
| 332 expected_.AppendBytes("@@@@@@"); | 322 expected_.AppendBytes("@@@@@@"); |
| 333 | 323 |
| 334 string expected_out, actual_out; | 324 string expected_out, actual_out; |
| 335 expected_.TakeString(&expected_out); | 325 expected_.TakeString(&expected_out); |
| 336 peer_.TakeString(&actual_out); | 326 peer_.TakeString(&actual_out); |
| 337 EXPECT_EQ(expected_out, actual_out); | 327 EXPECT_EQ(expected_out, actual_out); |
| 338 } | 328 } |
| 339 | 329 |
| 340 TEST_P(HpackEncoderTest, EncodingWithoutCompression) { | 330 TEST_P(HpackEncoderTest, EncodingWithoutCompression) { |
| 341 encoder_.SetHeaderListener([this](StringPiece name, StringPiece value) { | 331 encoder_.SetHeaderListener([this](StringPiece name, StringPiece value) { |
| 342 this->SaveHeaders(name, value); | 332 this->SaveHeaders(name, value); |
| 343 }); | 333 }); |
| 344 | 334 encoder_.DisableCompression(); |
| 345 // Implementation should internally disable. | |
| 346 peer_.set_allow_huffman_compression(true); | |
| 347 | 335 |
| 348 ExpectNonIndexedLiteral(":path", "/index.html"); | 336 ExpectNonIndexedLiteral(":path", "/index.html"); |
| 349 ExpectNonIndexedLiteral("cookie", "foo=bar; baz=bing"); | 337 ExpectNonIndexedLiteral("cookie", "foo=bar"); |
| 338 ExpectNonIndexedLiteral("cookie", "baz=bing"); |
| 350 ExpectNonIndexedLiteral("hello", "goodbye"); | 339 ExpectNonIndexedLiteral("hello", "goodbye"); |
| 351 | 340 |
| 352 SpdyHeaderBlock headers; | 341 SpdyHeaderBlock headers; |
| 353 headers[":path"] = "/index.html"; | 342 headers[":path"] = "/index.html"; |
| 354 headers["cookie"] = "foo=bar; baz=bing"; | 343 headers["cookie"] = "foo=bar; baz=bing"; |
| 355 headers["hello"] = "goodbye"; | 344 headers["hello"] = "goodbye"; |
| 356 | 345 |
| 357 CompareWithExpectedEncodingWithoutCompression(headers); | 346 CompareWithExpectedEncoding(headers); |
| 358 | 347 |
| 359 EXPECT_THAT(headers_observed_, | 348 EXPECT_THAT( |
| 360 ElementsAre(Pair(":path", "/index.html"), | 349 headers_observed_, |
| 361 Pair("cookie", "foo=bar; baz=bing"), | 350 ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"), |
| 362 Pair("hello", "goodbye"))); | 351 Pair("cookie", "baz=bing"), Pair("hello", "goodbye"))); |
| 363 } | 352 } |
| 364 | 353 |
| 365 TEST_P(HpackEncoderTest, MultipleEncodingPasses) { | 354 TEST_P(HpackEncoderTest, MultipleEncodingPasses) { |
| 366 encoder_.SetHeaderListener([this](StringPiece name, StringPiece value) { | 355 encoder_.SetHeaderListener([this](StringPiece name, StringPiece value) { |
| 367 this->SaveHeaders(name, value); | 356 this->SaveHeaders(name, value); |
| 368 }); | 357 }); |
| 369 | 358 |
| 370 // Pass 1. | 359 // Pass 1. |
| 371 { | 360 { |
| 372 SpdyHeaderBlock headers; | 361 SpdyHeaderBlock headers; |
| (...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 590 CompareWithExpectedEncoding(headers); | 579 CompareWithExpectedEncoding(headers); |
| 591 | 580 |
| 592 HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); | 581 HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); |
| 593 EXPECT_EQ(new_entry->name(), "key3"); | 582 EXPECT_EQ(new_entry->name(), "key3"); |
| 594 EXPECT_EQ(new_entry->value(), "value3"); | 583 EXPECT_EQ(new_entry->value(), "value3"); |
| 595 } | 584 } |
| 596 | 585 |
| 597 } // namespace | 586 } // namespace |
| 598 | 587 |
| 599 } // namespace net | 588 } // namespace net |
| OLD | NEW |