Adds HpackEncoder::EncodeRepresentations(), where the caller can specify an explicit list of (key, value) pairs. This method does not split values on \0.

gfe-relnote: Adds a new method to HpackEncoder which is not yet used in production code. Not protected.
PiperOrigin-RevId: 285121328
Change-Id: Ieffbc569af94e9d9f4c350241d390149feb77019
diff --git a/spdy/core/hpack/hpack_encoder_test.cc b/spdy/core/hpack/hpack_encoder_test.cc
index 9a73a88..fb09301 100644
--- a/spdy/core/hpack/hpack_encoder_test.cc
+++ b/spdy/core/hpack/hpack_encoder_test.cc
@@ -71,13 +71,8 @@
   // non-incremental encoding path.
   static bool EncodeHeaderSet(HpackEncoder* encoder,
                               const SpdyHeaderBlock& header_set,
-                              std::string* output,
-                              bool use_incremental) {
-    if (use_incremental) {
-      return EncodeIncremental(encoder, header_set, output);
-    } else {
-      return encoder->EncodeHeaderSet(header_set, output);
-    }
+                              std::string* output) {
+    return encoder->EncodeHeaderSet(header_set, output);
   }
 
   static bool EncodeIncremental(HpackEncoder* encoder,
@@ -97,6 +92,23 @@
     return true;
   }
 
+  static bool EncodeRepresentations(HpackEncoder* encoder,
+                                    const Representations& representations,
+                                    std::string* output) {
+    std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator =
+        encoder->EncodeRepresentations(representations);
+    std::string output_buffer;
+    http2::test::Http2Random random;
+    encoderator->Next(random.UniformInRange(0, 16), &output_buffer);
+    while (encoderator->HasNext()) {
+      std::string second_buffer;
+      encoderator->Next(random.UniformInRange(0, 16), &second_buffer);
+      output_buffer.append(second_buffer);
+    }
+    *output = std::move(output_buffer);
+    return true;
+  }
+
  private:
   HpackEncoder* encoder_;
 };
@@ -108,19 +120,23 @@
 using testing::ElementsAre;
 using testing::Pair;
 
-class HpackEncoderTest : public ::testing::TestWithParam<bool> {
+enum EncodeStrategy {
+  kDefault,
+  kIncremental,
+  kRepresentations,
+};
+
+class HpackEncoderTestBase : public ::testing::Test {
  protected:
   typedef test::HpackEncoderPeer::Representations Representations;
 
-  HpackEncoderTest()
+  HpackEncoderTestBase()
       : encoder_(ObtainHpackHuffmanTable()),
         peer_(&encoder_),
         static_(peer_.table()->GetByIndex(1)),
         headers_storage_(1024 /* block size */) {}
 
   void SetUp() override {
-    use_incremental_ = GetParam();
-
     // Populate dynamic entries into the table fixture. For simplicity each
     // entry has name.size() + value.size() == 10.
     key_1_ = peer_.table()->TryAddEntry("key1", "value1");
@@ -181,11 +197,37 @@
     expected_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
     expected_.AppendUint32(size);
   }
+  Representations MakeRepresentations(const SpdyHeaderBlock& header_set) {
+    Representations r;
+    for (const auto& header : header_set) {
+      r.push_back(header);
+    }
+    return r;
+  }
   void CompareWithExpectedEncoding(const SpdyHeaderBlock& header_set) {
     std::string expected_out, actual_out;
     expected_.TakeString(&expected_out);
-    EXPECT_TRUE(test::HpackEncoderPeer::EncodeHeaderSet(
-        &encoder_, header_set, &actual_out, use_incremental_));
+    switch (strategy_) {
+      case kDefault:
+        EXPECT_TRUE(test::HpackEncoderPeer::EncodeHeaderSet(
+            &encoder_, header_set, &actual_out));
+        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 expected_out, actual_out;
+    expected_.TakeString(&expected_out);
+    EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations(
+        &encoder_, representations, &actual_out));
     EXPECT_EQ(expected_out, actual_out);
   }
   size_t IndexOf(const HpackEntry* entry) {
@@ -205,12 +247,53 @@
   std::vector<std::pair<SpdyStringPiece, SpdyStringPiece>> headers_observed_;
 
   HpackOutputStream expected_;
-  bool use_incremental_;
+  EncodeStrategy strategy_ = kDefault;
+};
+
+TEST_F(HpackEncoderTestBase, EncodeRepresentations) {
+  encoder_.SetHeaderListener(
+      [this](SpdyStringPiece name, SpdyStringPiece value) {
+        this->SaveHeaders(name, value);
+      });
+  const std::vector<std::pair<SpdyStringPiece, SpdyStringPiece>> header_list = {
+      {"cookie", "val1; val2;val3"},
+      {":path", "/home"},
+      {"accept", "text/html, text/plain,application/xml"},
+      {"cookie", "val4"},
+      {"withnul", SpdyStringPiece("one\0two", 7)}};
+  ExpectNonIndexedLiteral(":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", SpdyStringPiece("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", std::string("one\0two", 7))));
+}
+
+class HpackEncoderTest : public HpackEncoderTestBase,
+                         public ::testing::WithParamInterface<EncodeStrategy> {
+ protected:
+  void SetUp() override {
+    strategy_ = GetParam();
+    HpackEncoderTestBase::SetUp();
+  }
 };
 
 INSTANTIATE_TEST_SUITE_P(HpackEncoderTests,
                          HpackEncoderTest,
-                         ::testing::Bool());
+                         ::testing::Values(kDefault,
+                                           kIncremental,
+                                           kRepresentations));
 
 TEST_P(HpackEncoderTest, SingleDynamicIndex) {
   encoder_.SetHeaderListener(
@@ -343,7 +426,7 @@
   ExpectNonIndexedLiteral(":path", "/index.html");
   ExpectNonIndexedLiteral("cookie", "foo=bar");
   ExpectNonIndexedLiteral("cookie", "baz=bing");
-  if (use_incremental_) {
+  if (strategy_ != kDefault) {
     // BUG: encodes as a \0-delimited value. Should be separate entries.
     ExpectNonIndexedLiteral("hello", std::string("goodbye\0aloha", 13));
   } else {
@@ -361,7 +444,7 @@
 
   CompareWithExpectedEncoding(headers);
 
-  if (use_incremental_) {
+  if (strategy_ != kDefault) {
     // BUG: value for "hello" encodes as \0-delimited. Should be separate
     // entries.
     EXPECT_THAT(
@@ -536,6 +619,11 @@
 // 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;
+  }
   SpdyHeaderBlock headers;
   // A header field to be crumbled: "spam: foo\0bar".
   headers["spam"] = std::string("foo\0bar", 7);