| Index: net/spdy/hpack_encoder_test.cc
|
| diff --git a/net/spdy/hpack_encoder_test.cc b/net/spdy/hpack_encoder_test.cc
|
| index 34e980dc7fd2988ad83b8982f7f63cc475589a87..6c8d40988168721867be522f4caa327f118d51bd 100644
|
| --- a/net/spdy/hpack_encoder_test.cc
|
| +++ b/net/spdy/hpack_encoder_test.cc
|
| @@ -18,18 +18,56 @@ using testing::ElementsAre;
|
|
|
| namespace test {
|
|
|
| +class HpackHeaderTablePeer {
|
| + public:
|
| + explicit HpackHeaderTablePeer(HpackHeaderTable* table)
|
| + : table_(table) {}
|
| +
|
| + HpackHeaderTable::EntryTable* dynamic_entries() {
|
| + return &table_->dynamic_entries_;
|
| + }
|
| +
|
| + private:
|
| + HpackHeaderTable* table_;
|
| +};
|
| +
|
| class HpackEncoderPeer {
|
| public:
|
| + typedef HpackEncoder::Representation Representation;
|
| + typedef HpackEncoder::Representations Representations;
|
| +
|
| explicit HpackEncoderPeer(HpackEncoder* encoder)
|
| - : encoder_(encoder) {}
|
| + : encoder_(encoder) {}
|
|
|
| - void set_max_string_literal_size(uint32 size) {
|
| - encoder_->max_string_literal_size_ = size;
|
| + HpackHeaderTable* table() {
|
| + return &encoder_->header_table_;
|
| + }
|
| + HpackHeaderTablePeer table_peer() {
|
| + return HpackHeaderTablePeer(table());
|
| + }
|
| + bool allow_huffman_compression() {
|
| + return encoder_->allow_huffman_compression_;
|
| + }
|
| + void set_allow_huffman_compression(bool allow) {
|
| + encoder_->allow_huffman_compression_ = allow;
|
| + }
|
| + void EmitString(StringPiece str) {
|
| + encoder_->EmitString(str);
|
| + }
|
| + void TakeString(string* out) {
|
| + encoder_->output_stream_.TakeString(out);
|
| }
|
| static void CookieToCrumbs(StringPiece cookie,
|
| std::vector<StringPiece>* out) {
|
| - HpackEncoder::CookieToCrumbs(cookie, out);
|
| + Representations tmp;
|
| + HpackEncoder::CookieToCrumbs(make_pair("", cookie), &tmp);
|
| +
|
| + out->clear();
|
| + for (size_t i = 0; i != tmp.size(); ++i) {
|
| + out->push_back(tmp[i].second);
|
| + }
|
| }
|
| +
|
| private:
|
| HpackEncoder* encoder_;
|
| };
|
| @@ -38,13 +76,332 @@ class HpackEncoderPeer {
|
|
|
| namespace {
|
|
|
| -TEST(HpackEncoderTest, CookieToCrumbs) {
|
| +using std::map;
|
| +using testing::ElementsAre;
|
| +
|
| +class HpackEncoderTest : public ::testing::Test {
|
| + protected:
|
| + typedef test::HpackEncoderPeer::Representations Representations;
|
| +
|
| + HpackEncoderTest()
|
| + : encoder_(ObtainHpackHuffmanTable()),
|
| + peer_(&encoder_) {}
|
| +
|
| + virtual void SetUp() {
|
| + static_ = peer_.table()->GetByIndex(1);
|
| + // Populate dynamic entries into the table fixture. For simplicity each
|
| + // entry has name.size() + value.size() == 10.
|
| + key_1_ = peer_.table()->TryAddEntry("key1", "value1");
|
| + key_2_ = peer_.table()->TryAddEntry("key2", "value2");
|
| + cookie_a_ = peer_.table()->TryAddEntry("cookie", "a=bb");
|
| + cookie_c_ = peer_.table()->TryAddEntry("cookie", "c=dd");
|
| +
|
| + // No further insertions may occur without evictions.
|
| + peer_.table()->SetMaxSize(peer_.table()->size());
|
| +
|
| + // Disable Huffman coding by default. Most tests don't care about it.
|
| + peer_.set_allow_huffman_compression(false);
|
| + }
|
| +
|
| + void ExpectIndex(size_t index) {
|
| + expected_.AppendPrefix(kIndexedOpcode);
|
| + expected_.AppendUint32(index);
|
| + }
|
| + void ExpectIndexedLiteral(HpackEntry* key_entry, StringPiece value) {
|
| + expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
|
| + expected_.AppendUint32(key_entry->Index());
|
| + expected_.AppendPrefix(kStringLiteralIdentityEncoded);
|
| + expected_.AppendUint32(value.size());
|
| + expected_.AppendBytes(value);
|
| + }
|
| + void ExpectIndexedLiteral(StringPiece name, StringPiece value) {
|
| + expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
|
| + expected_.AppendUint32(0);
|
| + expected_.AppendPrefix(kStringLiteralIdentityEncoded);
|
| + expected_.AppendUint32(name.size());
|
| + expected_.AppendBytes(name);
|
| + expected_.AppendPrefix(kStringLiteralIdentityEncoded);
|
| + expected_.AppendUint32(value.size());
|
| + expected_.AppendBytes(value);
|
| + }
|
| + void ExpectNonIndexedLiteral(StringPiece name, StringPiece value) {
|
| + expected_.AppendPrefix(kLiteralNoIndexOpcode);
|
| + expected_.AppendUint32(0);
|
| + expected_.AppendPrefix(kStringLiteralIdentityEncoded);
|
| + expected_.AppendUint32(name.size());
|
| + expected_.AppendBytes(name);
|
| + expected_.AppendPrefix(kStringLiteralIdentityEncoded);
|
| + expected_.AppendUint32(value.size());
|
| + expected_.AppendBytes(value);
|
| + }
|
| + void CompareWithExpectedEncoding(const map<string, string>& header_set) {
|
| + string expected_out, actual_out;
|
| + expected_.TakeString(&expected_out);
|
| + EXPECT_TRUE(encoder_.EncodeHeaderSet(header_set, &actual_out));
|
| + EXPECT_EQ(expected_out, actual_out);
|
| + }
|
| +
|
| + HpackEncoder encoder_;
|
| + test::HpackEncoderPeer peer_;
|
| +
|
| + HpackEntry* static_;
|
| + HpackEntry* key_1_;
|
| + HpackEntry* key_2_;
|
| + HpackEntry* cookie_a_;
|
| + HpackEntry* cookie_c_;
|
| +
|
| + HpackOutputStream expected_;
|
| +};
|
| +
|
| +TEST_F(HpackEncoderTest, SingleDynamicIndex) {
|
| + ExpectIndex(key_2_->Index());
|
| +
|
| + map<string, string> headers;
|
| + headers[key_2_->name()] = key_2_->value();
|
| + CompareWithExpectedEncoding(headers);
|
| +
|
| + // |key_2_| was added to the reference set.
|
| + EXPECT_THAT(peer_.table()->reference_set(), ElementsAre(key_2_));
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, SingleStaticIndex) {
|
| + ExpectIndex(static_->Index());
|
| +
|
| + map<string, string> headers;
|
| + headers[static_->name()] = static_->value();
|
| + CompareWithExpectedEncoding(headers);
|
| +
|
| + // A new entry copying |static_| was inserted and added to the reference set.
|
| + HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
|
| + EXPECT_NE(static_, new_entry);
|
| + EXPECT_EQ(static_->name(), new_entry->name());
|
| + EXPECT_EQ(static_->value(), new_entry->value());
|
| + EXPECT_THAT(peer_.table()->reference_set(), ElementsAre(new_entry));
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, SingleStaticIndexTooLarge) {
|
| + peer_.table()->SetMaxSize(1); // Also evicts all fixtures.
|
| + ExpectIndex(static_->Index());
|
| +
|
| + map<string, string> headers;
|
| + headers[static_->name()] = static_->value();
|
| + CompareWithExpectedEncoding(headers);
|
| +
|
| + EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
|
| + EXPECT_EQ(0u, peer_.table()->reference_set().size());
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, SingleLiteralWithIndexName) {
|
| + ExpectIndexedLiteral(key_2_, "value3");
|
| +
|
| + map<string, string> headers;
|
| + headers[key_2_->name()] = "value3";
|
| + CompareWithExpectedEncoding(headers);
|
| +
|
| + // A new entry was inserted and added to the reference set.
|
| + HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
|
| + EXPECT_EQ(new_entry->name(), key_2_->name());
|
| + EXPECT_EQ(new_entry->value(), "value3");
|
| + EXPECT_THAT(peer_.table()->reference_set(), ElementsAre(new_entry));
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, SingleLiteralWithLiteralName) {
|
| + ExpectIndexedLiteral("key3", "value3");
|
| +
|
| + map<string, string> headers;
|
| + headers["key3"] = "value3";
|
| + CompareWithExpectedEncoding(headers);
|
| +
|
| + // A new entry was inserted and added to the reference set.
|
| + HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
|
| + EXPECT_EQ(new_entry->name(), "key3");
|
| + EXPECT_EQ(new_entry->value(), "value3");
|
| + EXPECT_THAT(peer_.table()->reference_set(), ElementsAre(new_entry));
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, SingleLiteralTooLarge) {
|
| + peer_.table()->SetMaxSize(1); // Also evicts all fixtures.
|
| +
|
| + ExpectIndexedLiteral("key3", "value3");
|
| +
|
| + // A header overflowing the header table is still emitted.
|
| + // The header table is empty.
|
| + map<string, string> headers;
|
| + headers["key3"] = "value3";
|
| + CompareWithExpectedEncoding(headers);
|
| +
|
| + EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
|
| + EXPECT_EQ(0u, peer_.table()->reference_set().size());
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, SingleInReferenceSet) {
|
| + peer_.table()->Toggle(key_2_);
|
| +
|
| + // Nothing is emitted.
|
| + map<string, string> headers;
|
| + headers[key_2_->name()] = key_2_->value();
|
| + CompareWithExpectedEncoding(headers);
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, ExplicitToggleOff) {
|
| + peer_.table()->Toggle(key_1_);
|
| + peer_.table()->Toggle(key_2_);
|
| +
|
| + // |key_1_| is explicitly toggled off.
|
| + ExpectIndex(key_1_->Index());
|
| +
|
| + map<string, string> headers;
|
| + headers[key_2_->name()] = key_2_->value();
|
| + CompareWithExpectedEncoding(headers);
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, ImplicitToggleOff) {
|
| + peer_.table()->Toggle(key_1_);
|
| + peer_.table()->Toggle(key_2_);
|
| +
|
| + // |key_1_| is evicted. No explicit toggle required.
|
| + ExpectIndexedLiteral("key3", "value3");
|
| +
|
| + map<string, string> headers;
|
| + headers[key_2_->name()] = key_2_->value();
|
| + headers["key3"] = "value3";
|
| + CompareWithExpectedEncoding(headers);
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, ExplicitDoubleToggle) {
|
| + peer_.table()->Toggle(key_1_);
|
| +
|
| + // |key_1_| is double-toggled prior to being evicted.
|
| + ExpectIndex(key_1_->Index());
|
| + ExpectIndex(key_1_->Index());
|
| + ExpectIndexedLiteral("key3", "value3");
|
| +
|
| + map<string, string> headers;
|
| + headers[key_1_->name()] = key_1_->value();
|
| + headers["key3"] = "value3";
|
| + CompareWithExpectedEncoding(headers);
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, EmitThanEvict) {
|
| + // |key_1_| is toggled and placed into the reference set,
|
| + // and then immediately evicted by "key3".
|
| + ExpectIndex(key_1_->Index());
|
| + ExpectIndexedLiteral("key3", "value3");
|
| +
|
| + map<string, string> headers;
|
| + headers[key_1_->name()] = key_1_->value();
|
| + headers["key3"] = "value3";
|
| + CompareWithExpectedEncoding(headers);
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, CookieHeaderIsCrumbled) {
|
| + peer_.table()->Toggle(cookie_a_);
|
| +
|
| + // |cookie_a_| is already in the reference set. |cookie_c_| is
|
| + // toggled, and "e=ff" is emitted with an indexed name.
|
| + ExpectIndex(cookie_c_->Index());
|
| + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");
|
| +
|
| + map<string, string> headers;
|
| + headers["cookie"] = "e=ff; a=bb; c=dd";
|
| + CompareWithExpectedEncoding(headers);
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, StringsDynamicallySelectHuffmanCoding) {
|
| + peer_.set_allow_huffman_compression(true);
|
| +
|
| + // Compactable string. Uses Huffman coding.
|
| + peer_.EmitString("feedbeef");
|
| + expected_.AppendPrefix(kStringLiteralHuffmanEncoded);
|
| + expected_.AppendUint32(5);
|
| + expected_.AppendBytes("\xC4G\v\xC4q");
|
| +
|
| + // Non-compactable. Uses identity coding.
|
| + peer_.EmitString("@@@@@@");
|
| + expected_.AppendPrefix(kStringLiteralIdentityEncoded);
|
| + expected_.AppendUint32(6);
|
| + expected_.AppendBytes("@@@@@@");
|
| +
|
| + string expected_out, actual_out;
|
| + expected_.TakeString(&expected_out);
|
| + peer_.TakeString(&actual_out);
|
| + EXPECT_EQ(expected_out, actual_out);
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, EncodingWithoutCompression) {
|
| + // Implementation should internally disable.
|
| + peer_.set_allow_huffman_compression(true);
|
| +
|
| + ExpectNonIndexedLiteral(":path", "/index.html");
|
| + ExpectNonIndexedLiteral("cookie", "foo=bar; baz=bing");
|
| + ExpectNonIndexedLiteral("hello", "goodbye");
|
| +
|
| + map<string, string> headers;
|
| + headers[":path"] = "/index.html";
|
| + headers["cookie"] = "foo=bar; baz=bing";
|
| + headers["hello"] = "goodbye";
|
| +
|
| + string expected_out, actual_out;
|
| + expected_.TakeString(&expected_out);
|
| + encoder_.EncodeHeaderSetWithoutCompression(headers, &actual_out);
|
| + EXPECT_EQ(expected_out, actual_out);
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, MultipleEncodingPasses) {
|
| + // Pass 1: key_1_ and cookie_a_ are toggled on.
|
| + {
|
| + map<string, string> headers;
|
| + headers["key1"] = "value1";
|
| + headers["cookie"] = "a=bb";
|
| +
|
| + ExpectIndex(cookie_a_->Index());
|
| + ExpectIndex(key_1_->Index());
|
| + CompareWithExpectedEncoding(headers);
|
| + }
|
| + // Pass 2: |key_1_| is double-toggled and evicted.
|
| + // |key_2_| & |cookie_c_| are toggled on.
|
| + // |cookie_a_| is toggled off.
|
| + // A new cookie entry is added.
|
| + {
|
| + map<string, string> headers;
|
| + headers["key1"] = "value1";
|
| + headers["key2"] = "value2";
|
| + headers["cookie"] = "c=dd; e=ff";
|
| +
|
| + ExpectIndex(cookie_c_->Index()); // Toggle on.
|
| + ExpectIndex(key_1_->Index()); // Double-toggle before eviction.
|
| + ExpectIndex(key_1_->Index());
|
| + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");
|
| + ExpectIndex(key_2_->Index() + 1); // Toggle on. Add 1 to reflect insertion.
|
| + ExpectIndex(cookie_a_->Index() + 1); // Toggle off.
|
| + CompareWithExpectedEncoding(headers);
|
| + }
|
| + // Pass 3: |key_2_| is evicted and implicitly toggled off.
|
| + // |cookie_c_| is explicitly toggled off.
|
| + // "key1" is re-inserted.
|
| + {
|
| + map<string, string> headers;
|
| + headers["key1"] = "value1";
|
| + headers["key3"] = "value3";
|
| + headers["cookie"] = "e=ff";
|
| +
|
| + ExpectIndexedLiteral("key1", "value1");
|
| + ExpectIndexedLiteral("key3", "value3");
|
| + ExpectIndex(cookie_c_->Index() + 2); // Toggle off. Add 1 for insertion.
|
| +
|
| + CompareWithExpectedEncoding(headers);
|
| + }
|
| +}
|
| +
|
| +TEST_F(HpackEncoderTest, CookieToCrumbs) {
|
| test::HpackEncoderPeer peer(NULL);
|
| std::vector<StringPiece> out;
|
|
|
| // A space after ';' is consumed. All other spaces remain.
|
| - // ';' at begin and end of string produce empty crumbs.
|
| - peer.CookieToCrumbs(" foo=1;bar=2 ; baz=3; bing=4;", &out);
|
| + // ';' at beginning and end of string produce empty crumbs.
|
| + // See section 8.1.3.4 "Compressing the Cookie Header Field" in the HTTP/2
|
| + // specification at http://tools.ietf.org/html/draft-ietf-httpbis-http2-11
|
| + peer.CookieToCrumbs(" foo=1;bar=2 ; baz=3; bing=4; ", &out);
|
| EXPECT_THAT(out, ElementsAre(" foo=1", "bar=2 ", "baz=3", " bing=4", ""));
|
|
|
| peer.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out);
|
| @@ -58,42 +415,9 @@ TEST(HpackEncoderTest, CookieToCrumbs) {
|
|
|
| peer.CookieToCrumbs("", &out);
|
| EXPECT_THAT(out, ElementsAre(""));
|
| -}
|
| -
|
| -// Test that EncoderHeaderSet() simply encodes everything as literals
|
| -// without indexing.
|
| -TEST(HpackEncoderTest, Basic) {
|
| - HpackEncoder encoder;
|
| -
|
| - std::map<string, string> header_set1;
|
| - header_set1["name1"] = "value1";
|
| - header_set1["name2"] = "value2";
|
| -
|
| - string encoded_header_set1;
|
| - EXPECT_TRUE(encoder.EncodeHeaderSet(header_set1, &encoded_header_set1));
|
| - EXPECT_EQ("\x40\x05name1\x06value1"
|
| - "\x40\x05name2\x06value2", encoded_header_set1);
|
| -
|
| - std::map<string, string> header_set2;
|
| - header_set2["name2"] = "different-value";
|
| - header_set2["name3"] = "value3";
|
| -
|
| - string encoded_header_set2;
|
| - EXPECT_TRUE(encoder.EncodeHeaderSet(header_set2, &encoded_header_set2));
|
| - EXPECT_EQ("\x40\x05name2\x0f" "different-value"
|
| - "\x40\x05name3\x06value3", encoded_header_set2);
|
| -}
|
| -
|
| -TEST(HpackEncoderTest, CookieCrumbling) {
|
| - HpackEncoder encoder;
|
| -
|
| - std::map<string, string> header_set;
|
| - header_set["Cookie"] = "key1=value1; key2=value2";
|
|
|
| - string encoded_header_set;
|
| - EXPECT_TRUE(encoder.EncodeHeaderSet(header_set, &encoded_header_set));
|
| - EXPECT_EQ("\x40\x06""Cookie\x0bkey1=value1"
|
| - "\x40\x06""Cookie\x0bkey2=value2", encoded_header_set);
|
| + peer.CookieToCrumbs("foo;bar; baz;bing;", &out);
|
| + EXPECT_THAT(out, ElementsAre("foo", "bar", "baz", "bing", ""));
|
| }
|
|
|
| } // namespace
|
|
|