// 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 "quiche/spdy/core/hpack/hpack_encoder.h"

#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "absl/strings/string_view.h"
#include "quiche/http2/hpack/huffman/hpack_huffman_encoder.h"
#include "quiche/http2/test_tools/http2_random.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/platform/api/quiche_test.h"
#include "quiche/common/quiche_simple_arena.h"
#include "quiche/spdy/core/hpack/hpack_constants.h"
#include "quiche/spdy/core/hpack/hpack_entry.h"
#include "quiche/spdy/core/hpack/hpack_header_table.h"
#include "quiche/spdy/core/hpack/hpack_output_stream.h"
#include "quiche/spdy/core/hpack/hpack_static_table.h"
#include "quiche/spdy/core/http2_header_block.h"

namespace spdy {

namespace test {

class HpackHeaderTablePeer {
 public:
  explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {}

  const HpackEntry* GetFirstStaticEntry() const {
    return &table_->static_entries_.front();
  }

  HpackHeaderTable::DynamicEntryTable* 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) {}

  bool compression_enabled() const { return encoder_->enable_compression_; }
  HpackHeaderTable* table() { return &encoder_->header_table_; }
  HpackHeaderTablePeer table_peer() { return HpackHeaderTablePeer(table()); }
  void EmitString(absl::string_view str) { encoder_->EmitString(str); }
  void TakeString(std::string* out) {
    *out = encoder_->output_stream_.TakeString();
  }
  static void CookieToCrumbs(absl::string_view cookie,
                             std::vector<absl::string_view>* 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(absl::string_view value,
                                      std::vector<absl::string_view>* 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);
    }
  }

  // TODO(dahollings): Remove or clean up these methods when deprecating
  // non-incremental encoding path.
  static std::string EncodeHeaderBlock(HpackEncoder* encoder,
                                       const Http2HeaderBlock& header_set) {
    return encoder->EncodeHeaderBlock(header_set);
  }

  static bool EncodeIncremental(HpackEncoder* encoder,
                                const Http2HeaderBlock& header_set,
                                std::string* output) {
    std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator =
        encoder->EncodeHeaderSet(header_set);
    http2::test::Http2Random random;
    std::string output_buffer = encoderator->Next(random.UniformInRange(0, 16));
    while (encoderator->HasNext()) {
      std::string second_buffer =
          encoderator->Next(random.UniformInRange(0, 16));
      output_buffer.append(second_buffer);
    }
    *output = std::move(output_buffer);
    return true;
  }

  static bool EncodeRepresentations(HpackEncoder* encoder,
                                    const Representations& representations,
                                    std::string* output) {
    std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator =
        encoder->EncodeRepresentations(representations);
    http2::test::Http2Random random;
    std::string output_buffer = encoderator->Next(random.UniformInRange(0, 16));
    while (encoderator->HasNext()) {
      std::string second_buffer =
          encoderator->Next(random.UniformInRange(0, 16));
      output_buffer.append(second_buffer);
    }
    *output = std::move(output_buffer);
    return true;
  }

 private:
  HpackEncoder* encoder_;
};

}  // namespace test

namespace {

using testing::ElementsAre;
using testing::Pair;

const size_t kStaticEntryIndex = 1;

enum EncodeStrategy {
  kDefault,
  kIncremental,
  kRepresentations,
};

class HpackEncoderTest
    : public quiche::test::QuicheTestWithParam<EncodeStrategy> {
 protected:
  typedef test::HpackEncoderPeer::Representations Representations;

  HpackEncoderTest()
      : peer_(&encoder_),
        static_(peer_.table_peer().GetFirstStaticEntry()),
        dynamic_table_insertions_(0),
        headers_storage_(1024 /* block size */),
        strategy_(GetParam()) {}

  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_1_index_ = dynamic_table_insertions_++;
    key_2_ = peer_.table()->TryAddEntry("key2", "value2");
    key_2_index_ = dynamic_table_insertions_++;
    cookie_a_ = peer_.table()->TryAddEntry("cookie", "a=bb");
    cookie_a_index_ = dynamic_table_insertions_++;
    cookie_c_ = peer_.table()->TryAddEntry("cookie", "c=dd");
    cookie_c_index_ = dynamic_table_insertions_++;

    // No further insertions may occur without evictions.
    peer_.table()->SetMaxSize(peer_.table()->size());
    QUICHE_CHECK_EQ(kInitialDynamicTableSize, peer_.table()->size());
  }

  void SaveHeaders(absl::string_view name, absl::string_view value) {
    absl::string_view n(headers_storage_.Memdup(name.data(), name.size()),
                        name.size());
    absl::string_view v(headers_storage_.Memdup(value.data(), value.size()),
                        value.size());
    headers_observed_.push_back(std::make_pair(n, v));
  }

  void ExpectIndex(size_t index) {
    expected_.AppendPrefix(kIndexedOpcode);
    expected_.AppendUint32(index);
  }
  void ExpectIndexedLiteral(size_t key_index, absl::string_view value) {
    expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
    expected_.AppendUint32(key_index);
    ExpectString(&expected_, value);
  }
  void ExpectIndexedLiteral(absl::string_view name, absl::string_view value) {
    expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
    expected_.AppendUint32(0);
    ExpectString(&expected_, name);
    ExpectString(&expected_, value);
  }
  void ExpectNonIndexedLiteral(absl::string_view name,
                               absl::string_view value) {
    expected_.AppendPrefix(kLiteralNoIndexOpcode);
    expected_.AppendUint32(0);
    ExpectString(&expected_, name);
    ExpectString(&expected_, value);
  }
  void ExpectNonIndexedLiteralWithNameIndex(size_t key_index,
                                            absl::string_view value) {
    expected_.AppendPrefix(kLiteralNoIndexOpcode);
    expected_.AppendUint32(key_index);
    ExpectString(&expected_, value);
  }
  void ExpectString(HpackOutputStream* stream, absl::string_view str) {
    size_t encoded_size =
        peer_.compression_enabled() ? http2::HuffmanSize(str) : str.size();
    if (encoded_size < str.size()) {
      expected_.AppendPrefix(kStringLiteralHuffmanEncoded);
      expected_.AppendUint32(encoded_size);
      http2::HuffmanEncodeFast(str, encoded_size, stream->MutableString());
    } else {
      expected_.AppendPrefix(kStringLiteralIdentityEncoded);
      expected_.AppendUint32(str.size());
      expected_.AppendBytes(str);
    }
  }
  void ExpectHeaderTableSizeUpdate(uint32_t size) {
    expected_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
    expected_.AppendUint32(size);
  }
  Representations MakeRepresentations(const Http2HeaderBlock& header_set) {
    Representations r;
    for (const auto& header : header_set) {
      r.push_back(header);
    }
    return r;
  }
  void CompareWithExpectedEncoding(const Http2HeaderBlock& header_set) {
    std::string actual_out;
    std::string expected_out = expected_.TakeString();
    switch (strategy_) {
      case kDefault:
        actual_out =
            test::HpackEncoderPeer::EncodeHeaderBlock(&encoder_, header_set);
        break;
      case kIncremental:
        EXPECT_TRUE(test::HpackEncoderPeer::EncodeIncremental(
            &encoder_, header_set, &actual_out));
        break;
      case kRepresentations:
        EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations(
            &encoder_, MakeRepresentations(header_set), &actual_out));
        break;
    }
    EXPECT_EQ(expected_out, actual_out);
  }
  void CompareWithExpectedEncoding(const Representations& representations) {
    std::string actual_out;
    std::string expected_out = expected_.TakeString();
    EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations(
        &encoder_, representations, &actual_out));
    EXPECT_EQ(expected_out, actual_out);
  }
  // Converts the index of a dynamic table entry to the HPACK index.
  // In these test, dynamic table entries are indexed sequentially, starting
  // with 0.  The HPACK indexing scheme is defined at
  // https://httpwg.org/specs/rfc7541.html#index.address.space.
  size_t DynamicIndexToWireIndex(size_t index) {
    return dynamic_table_insertions_ - index + kStaticTableSize;
  }

  HpackEncoder encoder_;
  test::HpackEncoderPeer peer_;

  // Calculated based on the names and values inserted in SetUp(), above.
  const size_t kInitialDynamicTableSize = 4 * (10 + 32);

  const HpackEntry* static_;
  const HpackEntry* key_1_;
  const HpackEntry* key_2_;
  const HpackEntry* cookie_a_;
  const HpackEntry* cookie_c_;
  size_t key_1_index_;
  size_t key_2_index_;
  size_t cookie_a_index_;
  size_t cookie_c_index_;
  size_t dynamic_table_insertions_;

  quiche::QuicheSimpleArena headers_storage_;
  std::vector<std::pair<absl::string_view, absl::string_view>>
      headers_observed_;

  HpackOutputStream expected_;
  const EncodeStrategy strategy_;
};

using HpackEncoderTestWithDefaultStrategy = HpackEncoderTest;

INSTANTIATE_TEST_SUITE_P(HpackEncoderTests, HpackEncoderTestWithDefaultStrategy,
                         ::testing::Values(kDefault));

TEST_P(HpackEncoderTestWithDefaultStrategy, EncodeRepresentations) {
  EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize());
  encoder_.SetHeaderListener(
      [this](absl::string_view name, absl::string_view value) {
        this->SaveHeaders(name, value);
      });
  const std::vector<std::pair<absl::string_view, absl::string_view>>
      header_list = {{"cookie", "val1; val2;val3"},
                     {":path", "/home"},
                     {"accept", "text/html, text/plain,application/xml"},
                     {"cookie", "val4"},
                     {"withnul", absl::string_view("one\0two", 7)}};
  ExpectNonIndexedLiteralWithNameIndex(peer_.table()->GetByName(":path"),
                                       "/home");
  ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val1");
  ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val2");
  ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val3");
  ExpectIndexedLiteral(peer_.table()->GetByName("accept"),
                       "text/html, text/plain,application/xml");
  ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val4");
  ExpectIndexedLiteral("withnul", absl::string_view("one\0two", 7));

  CompareWithExpectedEncoding(header_list);
  EXPECT_THAT(
      headers_observed_,
      ElementsAre(Pair(":path", "/home"), Pair("cookie", "val1"),
                  Pair("cookie", "val2"), Pair("cookie", "val3"),
                  Pair("accept", "text/html, text/plain,application/xml"),
                  Pair("cookie", "val4"),
                  Pair("withnul", absl::string_view("one\0two", 7))));
  // Insertions and evictions have happened over the course of the test.
  EXPECT_GE(kInitialDynamicTableSize, encoder_.GetDynamicTableSize());
}

TEST_P(HpackEncoderTestWithDefaultStrategy, DynamicTableGrows) {
  EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize());
  peer_.table()->SetMaxSize(4096);
  encoder_.SetHeaderListener(
      [this](absl::string_view name, absl::string_view value) {
        this->SaveHeaders(name, value);
      });
  const std::vector<std::pair<absl::string_view, absl::string_view>>
      header_list = {{"cookie", "val1; val2;val3"},
                     {":path", "/home"},
                     {"accept", "text/html, text/plain,application/xml"},
                     {"cookie", "val4"},
                     {"withnul", absl::string_view("one\0two", 7)}};
  std::string out;
  EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations(&encoder_,
                                                            header_list, &out));

  EXPECT_FALSE(out.empty());
  // Insertions have happened over the course of the test.
  EXPECT_GT(encoder_.GetDynamicTableSize(), kInitialDynamicTableSize);
}

INSTANTIATE_TEST_SUITE_P(HpackEncoderTests, HpackEncoderTest,
                         ::testing::Values(kDefault, kIncremental,
                                           kRepresentations));

TEST_P(HpackEncoderTest, SingleDynamicIndex) {
  encoder_.SetHeaderListener(
      [this](absl::string_view name, absl::string_view value) {
        this->SaveHeaders(name, value);
      });

  ExpectIndex(DynamicIndexToWireIndex(key_2_index_));

  Http2HeaderBlock headers;
  headers[key_2_->name()] = key_2_->value();
  CompareWithExpectedEncoding(headers);
  EXPECT_THAT(headers_observed_,
              ElementsAre(Pair(key_2_->name(), key_2_->value())));
}

TEST_P(HpackEncoderTest, SingleStaticIndex) {
  ExpectIndex(kStaticEntryIndex);

  Http2HeaderBlock headers;
  headers[static_->name()] = static_->value();
  CompareWithExpectedEncoding(headers);
}

TEST_P(HpackEncoderTest, SingleStaticIndexTooLarge) {
  peer_.table()->SetMaxSize(1);  // Also evicts all fixtures.
  ExpectIndex(kStaticEntryIndex);

  Http2HeaderBlock headers;
  headers[static_->name()] = static_->value();
  CompareWithExpectedEncoding(headers);

  EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
}

TEST_P(HpackEncoderTest, SingleLiteralWithIndexName) {
  ExpectIndexedLiteral(DynamicIndexToWireIndex(key_2_index_), "value3");

  Http2HeaderBlock 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().get();
  EXPECT_EQ(new_entry->name(), key_2_->name());
  EXPECT_EQ(new_entry->value(), "value3");
}

TEST_P(HpackEncoderTest, SingleLiteralWithLiteralName) {
  ExpectIndexedLiteral("key3", "value3");

  Http2HeaderBlock headers;
  headers["key3"] = "value3";
  CompareWithExpectedEncoding(headers);

  HpackEntry* new_entry = peer_.table_peer().dynamic_entries()->front().get();
  EXPECT_EQ(new_entry->name(), "key3");
  EXPECT_EQ(new_entry->value(), "value3");
}

TEST_P(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.
  Http2HeaderBlock headers;
  headers["key3"] = "value3";
  CompareWithExpectedEncoding(headers);

  EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
}

TEST_P(HpackEncoderTest, EmitThanEvict) {
  // |key_1_| is toggled and placed into the reference set,
  // and then immediately evicted by "key3".
  ExpectIndex(DynamicIndexToWireIndex(key_1_index_));
  ExpectIndexedLiteral("key3", "value3");

  Http2HeaderBlock headers;
  headers[key_1_->name()] = key_1_->value();
  headers["key3"] = "value3";
  CompareWithExpectedEncoding(headers);
}

TEST_P(HpackEncoderTest, CookieHeaderIsCrumbled) {
  ExpectIndex(DynamicIndexToWireIndex(cookie_a_index_));
  ExpectIndex(DynamicIndexToWireIndex(cookie_c_index_));
  ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");

  Http2HeaderBlock headers;
  headers["cookie"] = "a=bb; c=dd; e=ff";
  CompareWithExpectedEncoding(headers);
}

TEST_P(HpackEncoderTest, MultiValuedHeadersNotCrumbled) {
  ExpectIndexedLiteral("foo", "bar, baz");
  Http2HeaderBlock headers;
  headers["foo"] = "bar, baz";
  CompareWithExpectedEncoding(headers);
}

TEST_P(HpackEncoderTest, StringsDynamicallySelectHuffmanCoding) {
  // 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("@@@@@@");

  std::string actual_out;
  std::string expected_out = expected_.TakeString();
  peer_.TakeString(&actual_out);
  EXPECT_EQ(expected_out, actual_out);
}

TEST_P(HpackEncoderTest, EncodingWithoutCompression) {
  encoder_.SetHeaderListener(
      [this](absl::string_view name, absl::string_view value) {
        this->SaveHeaders(name, value);
      });
  encoder_.DisableCompression();

  ExpectNonIndexedLiteral(":path", "/index.html");
  ExpectNonIndexedLiteral("cookie", "foo=bar");
  ExpectNonIndexedLiteral("cookie", "baz=bing");
  if (strategy_ == kRepresentations) {
    ExpectNonIndexedLiteral("hello", std::string("goodbye\0aloha", 13));
  } else {
    ExpectNonIndexedLiteral("hello", "goodbye");
    ExpectNonIndexedLiteral("hello", "aloha");
  }
  ExpectNonIndexedLiteral("multivalue", "value1, value2");

  Http2HeaderBlock headers;
  headers[":path"] = "/index.html";
  headers["cookie"] = "foo=bar; baz=bing";
  headers["hello"] = "goodbye";
  headers.AppendValueOrAddHeader("hello", "aloha");
  headers["multivalue"] = "value1, value2";

  CompareWithExpectedEncoding(headers);

  if (strategy_ == kRepresentations) {
    EXPECT_THAT(
        headers_observed_,
        ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"),
                    Pair("cookie", "baz=bing"),
                    Pair("hello", absl::string_view("goodbye\0aloha", 13)),
                    Pair("multivalue", "value1, value2")));
  } else {
    EXPECT_THAT(
        headers_observed_,
        ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"),
                    Pair("cookie", "baz=bing"), Pair("hello", "goodbye"),
                    Pair("hello", "aloha"),
                    Pair("multivalue", "value1, value2")));
  }
  EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize());
}

TEST_P(HpackEncoderTest, MultipleEncodingPasses) {
  encoder_.SetHeaderListener(
      [this](absl::string_view name, absl::string_view value) {
        this->SaveHeaders(name, value);
      });

  // Pass 1.
  {
    Http2HeaderBlock headers;
    headers["key1"] = "value1";
    headers["cookie"] = "a=bb";

    ExpectIndex(DynamicIndexToWireIndex(key_1_index_));
    ExpectIndex(DynamicIndexToWireIndex(cookie_a_index_));
    CompareWithExpectedEncoding(headers);
  }
  // Header table is:
  // 65: key1: value1
  // 64: key2: value2
  // 63: cookie: a=bb
  // 62: cookie: c=dd
  // Pass 2.
  {
    Http2HeaderBlock headers;
    headers["key2"] = "value2";
    headers["cookie"] = "c=dd; e=ff";

    // "key2: value2"
    ExpectIndex(DynamicIndexToWireIndex(key_2_index_));
    // "cookie: c=dd"
    ExpectIndex(DynamicIndexToWireIndex(cookie_c_index_));
    // This cookie evicts |key1| from the dynamic table.
    ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");
    dynamic_table_insertions_++;

    CompareWithExpectedEncoding(headers);
  }
  // Header table is:
  // 65: key2: value2
  // 64: cookie: a=bb
  // 63: cookie: c=dd
  // 62: cookie: e=ff
  // Pass 3.
  {
    Http2HeaderBlock headers;
    headers["key2"] = "value2";
    headers["cookie"] = "a=bb; b=cc; c=dd";

    // "key2: value2"
    EXPECT_EQ(65u, DynamicIndexToWireIndex(key_2_index_));
    ExpectIndex(DynamicIndexToWireIndex(key_2_index_));
    // "cookie: a=bb"
    EXPECT_EQ(64u, DynamicIndexToWireIndex(cookie_a_index_));
    ExpectIndex(DynamicIndexToWireIndex(cookie_a_index_));
    // This cookie evicts |key2| from the dynamic table.
    ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "b=cc");
    dynamic_table_insertions_++;
    // "cookie: c=dd"
    ExpectIndex(DynamicIndexToWireIndex(cookie_c_index_));

    CompareWithExpectedEncoding(headers);
  }

  // clang-format off
  EXPECT_THAT(headers_observed_,
              ElementsAre(Pair("key1", "value1"),
                          Pair("cookie", "a=bb"),
                          Pair("key2", "value2"),
                          Pair("cookie", "c=dd"),
                          Pair("cookie", "e=ff"),
                          Pair("key2", "value2"),
                          Pair("cookie", "a=bb"),
                          Pair("cookie", "b=cc"),
                          Pair("cookie", "c=dd")));
  // clang-format on
}

TEST_P(HpackEncoderTest, PseudoHeadersFirst) {
  Http2HeaderBlock headers;
  // A pseudo-header that should not be indexed.
  headers[":path"] = "/spam/eggs.html";
  // A pseudo-header to be indexed.
  headers[":authority"] = "www.example.com";
  // A regular header which precedes ":" alphabetically, should still be encoded
  // after pseudo-headers.
  headers["-foo"] = "bar";
  headers["foo"] = "bar";
  headers["cookie"] = "c=dd";

  // Headers are indexed in the order in which they were added.
  // This entry pushes "cookie: a=bb" back to 63.
  ExpectNonIndexedLiteralWithNameIndex(peer_.table()->GetByName(":path"),
                                       "/spam/eggs.html");
  ExpectIndexedLiteral(peer_.table()->GetByName(":authority"),
                       "www.example.com");
  ExpectIndexedLiteral("-foo", "bar");
  ExpectIndexedLiteral("foo", "bar");
  ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "c=dd");
  CompareWithExpectedEncoding(headers);
}

TEST_P(HpackEncoderTest, CookieToCrumbs) {
  test::HpackEncoderPeer peer(nullptr);
  std::vector<absl::string_view> out;

  // Leading and trailing whitespace is consumed. A space after ';' is consumed.
  // All other spaces remain. ';' 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 ; bar=3;  bing=4; ", &out);
  EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", " bing=4", ""));

  peer.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out);
  EXPECT_THAT(out, ElementsAre("", "", "foo = bar ", "", "", "baz =bing"));

  peer.CookieToCrumbs("baz=bing; foo=bar; baz=bing", &out);
  EXPECT_THAT(out, ElementsAre("baz=bing", "foo=bar", "baz=bing"));

  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("foo", "bar", "baz", "baz", "bing", ""));

  peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3;\t  ", &out);
  EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", ""));

  peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3 \t  ", &out);
  EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3"));
}

TEST_P(HpackEncoderTest, DecomposeRepresentation) {
  test::HpackEncoderPeer peer(nullptr);
  std::vector<absl::string_view> out;

  peer.DecomposeRepresentation("", &out);
  EXPECT_THAT(out, ElementsAre(""));

  peer.DecomposeRepresentation("foobar", &out);
  EXPECT_THAT(out, ElementsAre("foobar"));

  peer.DecomposeRepresentation(absl::string_view("foo\0bar", 7), &out);
  EXPECT_THAT(out, ElementsAre("foo", "bar"));

  peer.DecomposeRepresentation(absl::string_view("\0foo\0bar", 8), &out);
  EXPECT_THAT(out, ElementsAre("", "foo", "bar"));

  peer.DecomposeRepresentation(absl::string_view("foo\0bar\0", 8), &out);
  EXPECT_THAT(out, ElementsAre("foo", "bar", ""));

  peer.DecomposeRepresentation(absl::string_view("\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_P(HpackEncoderTest, CrumbleNullByteDelimitedValue) {
  if (strategy_ == kRepresentations) {
    // When HpackEncoder is asked to encode a list of Representations, the
    // caller must crumble null-delimited values.
    return;
  }
  Http2HeaderBlock headers;
  // A header field to be crumbled: "spam: foo\0bar".
  headers["spam"] = std::string("foo\0bar", 7);

  ExpectIndexedLiteral("spam", "foo");
  expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
  expected_.AppendUint32(62);
  expected_.AppendPrefix(kStringLiteralIdentityEncoded);
  expected_.AppendUint32(3);
  expected_.AppendBytes("bar");
  CompareWithExpectedEncoding(headers);
}

TEST_P(HpackEncoderTest, HeaderTableSizeUpdate) {
  encoder_.ApplyHeaderTableSizeSetting(1024);
  ExpectHeaderTableSizeUpdate(1024);
  ExpectIndexedLiteral("key3", "value3");

  Http2HeaderBlock headers;
  headers["key3"] = "value3";
  CompareWithExpectedEncoding(headers);

  HpackEntry* new_entry = peer_.table_peer().dynamic_entries()->front().get();
  EXPECT_EQ(new_entry->name(), "key3");
  EXPECT_EQ(new_entry->value(), "value3");
}

TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithMin) {
  const size_t starting_size = peer_.table()->settings_size_bound();
  encoder_.ApplyHeaderTableSizeSetting(starting_size - 2);
  encoder_.ApplyHeaderTableSizeSetting(starting_size - 1);
  // We must encode the low watermark, so the peer knows to evict entries
  // if necessary.
  ExpectHeaderTableSizeUpdate(starting_size - 2);
  ExpectHeaderTableSizeUpdate(starting_size - 1);
  ExpectIndexedLiteral("key3", "value3");

  Http2HeaderBlock headers;
  headers["key3"] = "value3";
  CompareWithExpectedEncoding(headers);

  HpackEntry* new_entry = peer_.table_peer().dynamic_entries()->front().get();
  EXPECT_EQ(new_entry->name(), "key3");
  EXPECT_EQ(new_entry->value(), "value3");
}

TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithExistingSize) {
  encoder_.ApplyHeaderTableSizeSetting(peer_.table()->settings_size_bound());
  // No encoded size update.
  ExpectIndexedLiteral("key3", "value3");

  Http2HeaderBlock headers;
  headers["key3"] = "value3";
  CompareWithExpectedEncoding(headers);

  HpackEntry* new_entry = peer_.table_peer().dynamic_entries()->front().get();
  EXPECT_EQ(new_entry->name(), "key3");
  EXPECT_EQ(new_entry->value(), "value3");
}

TEST_P(HpackEncoderTest, HeaderTableSizeUpdatesWithGreaterSize) {
  const size_t starting_size = peer_.table()->settings_size_bound();
  encoder_.ApplyHeaderTableSizeSetting(starting_size + 1);
  encoder_.ApplyHeaderTableSizeSetting(starting_size + 2);
  // Only a single size update to the final size.
  ExpectHeaderTableSizeUpdate(starting_size + 2);
  ExpectIndexedLiteral("key3", "value3");

  Http2HeaderBlock headers;
  headers["key3"] = "value3";
  CompareWithExpectedEncoding(headers);

  HpackEntry* new_entry = peer_.table_peer().dynamic_entries()->front().get();
  EXPECT_EQ(new_entry->name(), "key3");
  EXPECT_EQ(new_entry->value(), "value3");
}

}  // namespace

}  // namespace spdy
