diff options
Diffstat (limited to 'net/spdy/hpack/hpack_encoder_test.cc')
-rw-r--r-- | net/spdy/hpack/hpack_encoder_test.cc | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/net/spdy/hpack/hpack_encoder_test.cc b/net/spdy/hpack/hpack_encoder_test.cc new file mode 100644 index 0000000..02867cc --- /dev/null +++ b/net/spdy/hpack/hpack_encoder_test.cc @@ -0,0 +1,458 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/hpack/hpack_encoder.h" + +#include <map> +#include <string> + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +using base::StringPiece; +using std::string; +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) {} + + HpackHeaderTable* table() { return &encoder_->header_table_; } + HpackHeaderTablePeer table_peer() { return HpackHeaderTablePeer(table()); } + 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); } + void UpdateCharacterCounts(StringPiece str) { + encoder_->UpdateCharacterCounts(str); + } + static void CookieToCrumbs(StringPiece cookie, + std::vector<StringPiece>* out) { + Representations tmp; + HpackEncoder::CookieToCrumbs(std::make_pair("", cookie), &tmp); + + out->clear(); + for (size_t i = 0; i != tmp.size(); ++i) { + out->push_back(tmp[i].second); + } + } + static void DecomposeRepresentation(StringPiece value, + std::vector<StringPiece>* out) { + Representations tmp; + HpackEncoder::DecomposeRepresentation(std::make_pair("foobar", value), + &tmp); + + out->clear(); + for (size_t i = 0; i != tmp.size(); ++i) { + out->push_back(tmp[i].second); + } + } + + private: + HpackEncoder* encoder_; +}; + +} // namespace test + +namespace { + +using std::map; +using testing::ElementsAre; + +class HpackEncoderTest : public ::testing::Test { + protected: + typedef test::HpackEncoderPeer::Representations Representations; + + HpackEncoderTest() + : encoder_(ObtainHpackHuffmanTable()), + peer_(&encoder_), + static_(peer_.table()->GetByIndex(1)) {} + + void SetUp() override { + // 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(const HpackEntry* key_entry, StringPiece value) { + expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); + expected_.AppendUint32(IndexOf(key_entry)); + 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 SpdyHeaderBlock& 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); + } + size_t IndexOf(const HpackEntry* entry) { + return peer_.table()->IndexOf(entry); + } + + HpackEncoder encoder_; + test::HpackEncoderPeer peer_; + + const HpackEntry* static_; + const HpackEntry* key_1_; + const HpackEntry* key_2_; + const HpackEntry* cookie_a_; + const HpackEntry* cookie_c_; + + HpackOutputStream expected_; +}; + +TEST_F(HpackEncoderTest, SingleDynamicIndex) { + ExpectIndex(IndexOf(key_2_)); + + SpdyHeaderBlock headers; + headers[key_2_->name()] = key_2_->value(); + CompareWithExpectedEncoding(headers); +} + +TEST_F(HpackEncoderTest, SingleStaticIndex) { + ExpectIndex(IndexOf(static_)); + + SpdyHeaderBlock headers; + headers[static_->name()] = static_->value(); + CompareWithExpectedEncoding(headers); +} + +TEST_F(HpackEncoderTest, SingleStaticIndexTooLarge) { + peer_.table()->SetMaxSize(1); // Also evicts all fixtures. + ExpectIndex(IndexOf(static_)); + + SpdyHeaderBlock headers; + headers[static_->name()] = static_->value(); + CompareWithExpectedEncoding(headers); + + EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size()); +} + +TEST_F(HpackEncoderTest, SingleLiteralWithIndexName) { + ExpectIndexedLiteral(key_2_, "value3"); + + SpdyHeaderBlock 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"); +} + +TEST_F(HpackEncoderTest, SingleLiteralWithLiteralName) { + ExpectIndexedLiteral("key3", "value3"); + + SpdyHeaderBlock headers; + headers["key3"] = "value3"; + CompareWithExpectedEncoding(headers); + + HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); + EXPECT_EQ(new_entry->name(), "key3"); + EXPECT_EQ(new_entry->value(), "value3"); +} + +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. + SpdyHeaderBlock headers; + headers["key3"] = "value3"; + CompareWithExpectedEncoding(headers); + + EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size()); +} + +TEST_F(HpackEncoderTest, EmitThanEvict) { + // |key_1_| is toggled and placed into the reference set, + // and then immediately evicted by "key3". + ExpectIndex(IndexOf(key_1_)); + ExpectIndexedLiteral("key3", "value3"); + + SpdyHeaderBlock headers; + headers[key_1_->name()] = key_1_->value(); + headers["key3"] = "value3"; + CompareWithExpectedEncoding(headers); +} + +TEST_F(HpackEncoderTest, CookieHeaderIsCrumbled) { + ExpectIndex(IndexOf(cookie_a_)); + ExpectIndex(IndexOf(cookie_c_)); + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); + + SpdyHeaderBlock 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(6); + expected_.AppendBytes("\x94\xA5\x92\x32\x96_"); + + // 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"); + + SpdyHeaderBlock 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. + { + SpdyHeaderBlock headers; + headers["key1"] = "value1"; + headers["cookie"] = "a=bb"; + + ExpectIndex(IndexOf(cookie_a_)); + ExpectIndex(IndexOf(key_1_)); + CompareWithExpectedEncoding(headers); + } + // Header table is: + // 65: key1: value1 + // 64: key2: value2 + // 63: cookie: a=bb + // 62: cookie: c=dd + // Pass 2. + { + SpdyHeaderBlock headers; + headers["key2"] = "value2"; + headers["cookie"] = "c=dd; e=ff"; + + ExpectIndex(IndexOf(cookie_c_)); + // This cookie evicts |key1| from the dynamic table. + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); + // "key2: value2" + ExpectIndex(65); + + CompareWithExpectedEncoding(headers); + } + // Header table is: + // 65: key2: value2 + // 64: cookie: a=bb + // 63: cookie: c=dd + // 62: cookie: e=ff + // Pass 3. + { + SpdyHeaderBlock headers; + headers["key2"] = "value2"; + headers["cookie"] = "a=bb; b=cc; c=dd"; + + // "cookie: a=bb" + ExpectIndex(64); + // This cookie evicts |key2| from the dynamic table. + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "b=cc"); + // "cookie: c=dd" + ExpectIndex(64); + // "key2: value2" + ExpectIndexedLiteral("key2", "value2"); + + CompareWithExpectedEncoding(headers); + } +} + +TEST_F(HpackEncoderTest, PseudoHeadersFirst) { + SpdyHeaderBlock headers; + // A pseudo-header to be indexed. + headers[":authority"] = "www.example.com"; + // A pseudo-header that should not be indexed. + headers[":path"] = "/spam/eggs.html"; + // A regular header which precedes ":" alphabetically, should still be encoded + // after pseudo-headers. + headers["-foo"] = "bar"; + headers["foo"] = "bar"; + headers["cookie"] = "c=dd"; + + // Pseudo-headers are encoded in alphabetical order. + // This entry pushes "cookie: a=bb" back to 63. + ExpectIndexedLiteral(peer_.table()->GetByName(":authority"), + "www.example.com"); + ExpectNonIndexedLiteral(":path", "/spam/eggs.html"); + // Regular headers are endoded in alphabetical order. + // This entry pushes "cookie: a=bb" back to 64. + ExpectIndexedLiteral("-foo", "bar"); + ExpectIndex(64); + ExpectIndexedLiteral("foo", "bar"); + CompareWithExpectedEncoding(headers); +} + +TEST_F(HpackEncoderTest, CookieToCrumbs) { + test::HpackEncoderPeer peer(NULL); + std::vector<StringPiece> out; + + // A space after ';' is consumed. All other spaces remain. ';' at beginning + // and end of string produce empty crumbs. Duplicate crumbs are removed. + // 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 ; bar=3; bing=4; ", &out); + EXPECT_THAT(out, ElementsAre("", " bing=4", " foo=1", "bar=2 ", "bar=3")); + + peer.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out); + EXPECT_THAT(out, ElementsAre("", "baz =bing", "foo = bar ")); + + peer.CookieToCrumbs("baz=bing; foo=bar; baz=bing", &out); + EXPECT_THAT(out, ElementsAre("baz=bing", "foo=bar")); + + peer.CookieToCrumbs("baz=bing", &out); + EXPECT_THAT(out, ElementsAre("baz=bing")); + + peer.CookieToCrumbs("", &out); + EXPECT_THAT(out, ElementsAre("")); + + peer.CookieToCrumbs("foo;bar; baz;baz;bing;", &out); + EXPECT_THAT(out, ElementsAre("", "bar", "baz", "bing", "foo")); +} + +TEST_F(HpackEncoderTest, UpdateCharacterCounts) { + std::vector<size_t> counts(256, 0); + size_t total_counts = 0; + encoder_.SetCharCountsStorage(&counts, &total_counts); + + char kTestString[] = + "foo\0\1\xff" + "boo"; + peer_.UpdateCharacterCounts( + StringPiece(kTestString, arraysize(kTestString) - 1)); + + std::vector<size_t> expect(256, 0); + expect[static_cast<uint8>('f')] = 1; + expect[static_cast<uint8>('o')] = 4; + expect[static_cast<uint8>('\0')] = 1; + expect[static_cast<uint8>('\1')] = 1; + expect[static_cast<uint8>('\xff')] = 1; + expect[static_cast<uint8>('b')] = 1; + + EXPECT_EQ(expect, counts); + EXPECT_EQ(9u, total_counts); +} + +TEST_F(HpackEncoderTest, DecomposeRepresentation) { + test::HpackEncoderPeer peer(NULL); + std::vector<StringPiece> out; + + peer.DecomposeRepresentation("", &out); + EXPECT_THAT(out, ElementsAre("")); + + peer.DecomposeRepresentation("foobar", &out); + EXPECT_THAT(out, ElementsAre("foobar")); + + peer.DecomposeRepresentation(StringPiece("foo\0bar", 7), &out); + EXPECT_THAT(out, ElementsAre("foo", "bar")); + + peer.DecomposeRepresentation(StringPiece("\0foo\0bar", 8), &out); + EXPECT_THAT(out, ElementsAre("", "foo", "bar")); + + peer.DecomposeRepresentation(StringPiece("foo\0bar\0", 8), &out); + EXPECT_THAT(out, ElementsAre("foo", "bar", "")); + + peer.DecomposeRepresentation(StringPiece("\0foo\0bar\0", 9), &out); + EXPECT_THAT(out, ElementsAre("", "foo", "bar", "")); +} + +// Test that encoded headers do not have \0-delimited multiple values, as this +// became disallowed in HTTP/2 draft-14. +TEST_F(HpackEncoderTest, CrumbleNullByteDelimitedValue) { + SpdyHeaderBlock headers; + // A header field to be crumbled: "spam: foo\0bar". + headers["spam"] = string("foo\0bar", 7); + + ExpectIndexedLiteral("spam", "foo"); + expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); + expected_.AppendUint32(62); + expected_.AppendPrefix(kStringLiteralIdentityEncoded); + expected_.AppendUint32(3); + expected_.AppendBytes("bar"); + CompareWithExpectedEncoding(headers); +} + +} // namespace + +} // namespace net |