Move all of HTTP/2 test tools into http2/test_tools.

PiperOrigin-RevId: 446965334
diff --git a/quiche/http2/test_tools/frame_decoder_state_test_util.cc b/quiche/http2/test_tools/frame_decoder_state_test_util.cc
new file mode 100644
index 0000000..e3eab59
--- /dev/null
+++ b/quiche/http2/test_tools/frame_decoder_state_test_util.cc
@@ -0,0 +1,34 @@
+// Copyright 2016 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/http2/test_tools/frame_decoder_state_test_util.h"
+
+#include "quiche/http2/http2_structures.h"
+#include "quiche/http2/test_tools/http2_random.h"
+#include "quiche/http2/test_tools/http2_structure_decoder_test_util.h"
+#include "quiche/http2/test_tools/http2_structures_test_util.h"
+#include "quiche/http2/test_tools/random_decoder_test.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+
+namespace http2 {
+namespace test {
+
+// static
+void FrameDecoderStatePeer::Randomize(FrameDecoderState* p, Http2Random* rng) {
+  QUICHE_VLOG(1) << "FrameDecoderStatePeer::Randomize";
+  ::http2::test::Randomize(&p->frame_header_, rng);
+  p->remaining_payload_ = rng->Rand32();
+  p->remaining_padding_ = rng->Rand32();
+  Http2StructureDecoderPeer::Randomize(&p->structure_decoder_, rng);
+}
+
+// static
+void FrameDecoderStatePeer::set_frame_header(const Http2FrameHeader& header,
+                                             FrameDecoderState* p) {
+  QUICHE_VLOG(1) << "FrameDecoderStatePeer::set_frame_header " << header;
+  p->frame_header_ = header;
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/frame_decoder_state_test_util.h b/quiche/http2/test_tools/frame_decoder_state_test_util.h
new file mode 100644
index 0000000..6047923
--- /dev/null
+++ b/quiche/http2/test_tools/frame_decoder_state_test_util.h
@@ -0,0 +1,37 @@
+// Copyright 2016 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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_FRAME_DECODER_STATE_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_FRAME_DECODER_STATE_TEST_UTIL_H_
+
+#include "quiche/http2/decoder/frame_decoder_state.h"
+#include "quiche/http2/http2_structures.h"
+#include "quiche/http2/test_tools/random_decoder_test.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace http2 {
+namespace test {
+
+class QUICHE_NO_EXPORT FrameDecoderStatePeer {
+ public:
+  // Randomizes (i.e. corrupts) the fields of the FrameDecoderState.
+  // PayloadDecoderBaseTest::StartDecoding calls this before passing the first
+  // decode buffer to the payload decoder, which increases the likelihood of
+  // detecting any use of prior states of the decoder on the decoding of
+  // future payloads.
+  static void Randomize(FrameDecoderState* p, Http2Random* rng);
+
+  // Inject a frame header into the FrameDecoderState.
+  // PayloadDecoderBaseTest::StartDecoding calls this just after calling
+  // Randomize (above), to simulate a full frame decoder having just finished
+  // decoding the common frame header and then calling the appropriate payload
+  // decoder based on the frame type in that frame header.
+  static void set_frame_header(const Http2FrameHeader& header,
+                               FrameDecoderState* p);
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_FRAME_DECODER_STATE_TEST_UTIL_H_
diff --git a/quiche/http2/test_tools/frame_parts.cc b/quiche/http2/test_tools/frame_parts.cc
index ba0396a..3897238 100644
--- a/quiche/http2/test_tools/frame_parts.cc
+++ b/quiche/http2/test_tools/frame_parts.cc
@@ -7,7 +7,7 @@
 #include <type_traits>
 
 #include "absl/strings/escaping.h"
-#include "quiche/http2/http2_structures_test_util.h"
+#include "quiche/http2/test_tools/http2_structures_test_util.h"
 #include "quiche/common/platform/api/quiche_logging.h"
 #include "quiche/common/platform/api/quiche_test.h"
 #include "quiche/common/platform/api/quiche_test_helpers.h"
diff --git a/quiche/http2/test_tools/frame_parts_collector.cc b/quiche/http2/test_tools/frame_parts_collector.cc
index f9de389..2b8f616 100644
--- a/quiche/http2/test_tools/frame_parts_collector.cc
+++ b/quiche/http2/test_tools/frame_parts_collector.cc
@@ -6,7 +6,7 @@
 
 #include <utility>
 
-#include "quiche/http2/http2_structures_test_util.h"
+#include "quiche/http2/test_tools/http2_structures_test_util.h"
 #include "quiche/common/platform/api/quiche_logging.h"
 #include "quiche/common/platform/api/quiche_test.h"
 
diff --git a/quiche/http2/test_tools/frame_parts_collector.h b/quiche/http2/test_tools/frame_parts_collector.h
index ccb6142..58adcdb 100644
--- a/quiche/http2/test_tools/frame_parts_collector.h
+++ b/quiche/http2/test_tools/frame_parts_collector.h
@@ -14,9 +14,9 @@
 #include <vector>
 
 #include "quiche/http2/decoder/http2_frame_decoder_listener.h"
-#include "quiche/http2/decoder/http2_frame_decoder_listener_test_util.h"
 #include "quiche/http2/http2_structures.h"
 #include "quiche/http2/test_tools/frame_parts.h"
+#include "quiche/http2/test_tools/http2_frame_decoder_listener_test_util.h"
 #include "quiche/common/platform/api/quiche_export.h"
 
 namespace http2 {
diff --git a/quiche/http2/test_tools/hpack_block_builder.cc b/quiche/http2/test_tools/hpack_block_builder.cc
new file mode 100644
index 0000000..bd9119a
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_block_builder.cc
@@ -0,0 +1,66 @@
+// Copyright 2016 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/http2/test_tools/hpack_block_builder.h"
+
+#include "quiche/http2/hpack/varint/hpack_varint_encoder.h"
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+
+void HpackBlockBuilder::AppendHighBitsAndVarint(uint8_t high_bits,
+                                                uint8_t prefix_length,
+                                                uint64_t varint) {
+  EXPECT_LE(3, prefix_length);
+  EXPECT_LE(prefix_length, 8);
+
+  HpackVarintEncoder::Encode(high_bits, prefix_length, varint, &buffer_);
+}
+
+void HpackBlockBuilder::AppendEntryTypeAndVarint(HpackEntryType entry_type,
+                                                 uint64_t varint) {
+  uint8_t high_bits;
+  uint8_t prefix_length;  // Bits of the varint prefix in the first byte.
+  switch (entry_type) {
+    case HpackEntryType::kIndexedHeader:
+      high_bits = 0x80;
+      prefix_length = 7;
+      break;
+    case HpackEntryType::kDynamicTableSizeUpdate:
+      high_bits = 0x20;
+      prefix_length = 5;
+      break;
+    case HpackEntryType::kIndexedLiteralHeader:
+      high_bits = 0x40;
+      prefix_length = 6;
+      break;
+    case HpackEntryType::kUnindexedLiteralHeader:
+      high_bits = 0x00;
+      prefix_length = 4;
+      break;
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      high_bits = 0x10;
+      prefix_length = 4;
+      break;
+    default:
+      QUICHE_BUG(http2_bug_110_1) << "Unreached, entry_type=" << entry_type;
+      high_bits = 0;
+      prefix_length = 0;
+      break;
+  }
+  AppendHighBitsAndVarint(high_bits, prefix_length, varint);
+}
+
+void HpackBlockBuilder::AppendString(bool is_huffman_encoded,
+                                     absl::string_view str) {
+  uint8_t high_bits = is_huffman_encoded ? 0x80 : 0;
+  uint8_t prefix_length = 7;
+  AppendHighBitsAndVarint(high_bits, prefix_length, str.size());
+  buffer_.append(str.data(), str.size());
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/hpack_block_builder.h b/quiche/http2/test_tools/hpack_block_builder.h
new file mode 100644
index 0000000..5aec637
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_block_builder.h
@@ -0,0 +1,97 @@
+// Copyright 2016 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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HPACK_BLOCK_BUILDER_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HPACK_BLOCK_BUILDER_H_
+
+// HpackBlockBuilder builds wire-format HPACK blocks (or fragments thereof)
+// from components.
+
+// Supports very large varints to enable tests to create HPACK blocks with
+// values that the decoder should reject. For now, this is only intended for
+// use in tests, and thus has EXPECT* in the code. If desired to use it in an
+// encoder, it will need optimization work, especially w.r.t memory mgmt, and
+// the EXPECT* will need to be removed or replaced with QUICHE_DCHECKs. And of
+// course the support for very large varints will not be needed in production
+// code.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/http2/hpack/http2_hpack_constants.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+
+class QUICHE_NO_EXPORT HpackBlockBuilder {
+ public:
+  explicit HpackBlockBuilder(absl::string_view initial_contents)
+      : buffer_(initial_contents.data(), initial_contents.size()) {}
+  HpackBlockBuilder() {}
+  ~HpackBlockBuilder() {}
+
+  size_t size() const { return buffer_.size(); }
+  const std::string& buffer() const { return buffer_; }
+
+  //----------------------------------------------------------------------------
+  // Methods for appending a valid HPACK entry.
+
+  void AppendIndexedHeader(uint64_t index) {
+    AppendEntryTypeAndVarint(HpackEntryType::kIndexedHeader, index);
+  }
+
+  void AppendDynamicTableSizeUpdate(uint64_t size) {
+    AppendEntryTypeAndVarint(HpackEntryType::kDynamicTableSizeUpdate, size);
+  }
+
+  void AppendNameIndexAndLiteralValue(HpackEntryType entry_type,
+                                      uint64_t name_index,
+                                      bool value_is_huffman_encoded,
+                                      absl::string_view value) {
+    // name_index==0 would indicate that the entry includes a literal name.
+    // Call AppendLiteralNameAndValue in that case.
+    EXPECT_NE(0u, name_index);
+    AppendEntryTypeAndVarint(entry_type, name_index);
+    AppendString(value_is_huffman_encoded, value);
+  }
+
+  void AppendLiteralNameAndValue(HpackEntryType entry_type,
+                                 bool name_is_huffman_encoded,
+                                 absl::string_view name,
+                                 bool value_is_huffman_encoded,
+                                 absl::string_view value) {
+    AppendEntryTypeAndVarint(entry_type, 0);
+    AppendString(name_is_huffman_encoded, name);
+    AppendString(value_is_huffman_encoded, value);
+  }
+
+  //----------------------------------------------------------------------------
+  // Primitive methods that are not guaranteed to write a valid HPACK entry.
+
+  // Appends a varint, with the specified high_bits above the prefix of the
+  // varint.
+  void AppendHighBitsAndVarint(uint8_t high_bits, uint8_t prefix_length,
+                               uint64_t varint);
+
+  // Append the start of an HPACK entry for the specified type, with the
+  // specified varint.
+  void AppendEntryTypeAndVarint(HpackEntryType entry_type, uint64_t varint);
+
+  // Append a header string (i.e. a header name or value) in HPACK format.
+  // Does NOT perform Huffman encoding.
+  void AppendString(bool is_huffman_encoded, absl::string_view str);
+
+ private:
+  std::string buffer_;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_HPACK_BLOCK_BUILDER_H_
diff --git a/quiche/http2/test_tools/hpack_block_builder_test.cc b/quiche/http2/test_tools/hpack_block_builder_test.cc
new file mode 100644
index 0000000..7642049
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_block_builder_test.cc
@@ -0,0 +1,169 @@
+// Copyright 2016 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/http2/test_tools/hpack_block_builder.h"
+
+#include "absl/strings/escaping.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+namespace {
+const bool kUncompressed = false;
+const bool kCompressed = true;
+
+// TODO(jamessynge): Once static table code is checked in, switch to using
+// constants from there.
+const uint32_t kStaticTableMethodGET = 2;
+const uint32_t kStaticTablePathSlash = 4;
+const uint32_t kStaticTableSchemeHttp = 6;
+
+// Tests of encoding per the RFC. See:
+//   http://httpwg.org/specs/rfc7541.html#header.field.representation.examples
+// The expected values have been copied from the RFC.
+TEST(HpackBlockBuilderTest, ExamplesFromSpecC2) {
+  {
+    HpackBlockBuilder b;
+    b.AppendLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader,
+                                kUncompressed, "custom-key", kUncompressed,
+                                "custom-header");
+    EXPECT_EQ(26u, b.size());
+
+    const char kExpected[] =
+        "\x40"            // == Literal indexed ==
+        "\x0a"            // Name length (10)
+        "custom-key"      // Name
+        "\x0d"            // Value length (13)
+        "custom-header";  // Value
+    EXPECT_EQ(kExpected, b.buffer());
+  }
+  {
+    HpackBlockBuilder b;
+    b.AppendNameIndexAndLiteralValue(HpackEntryType::kUnindexedLiteralHeader, 4,
+                                     kUncompressed, "/sample/path");
+    EXPECT_EQ(14u, b.size());
+
+    const char kExpected[] =
+        "\x04"           // == Literal unindexed, name index 0x04 ==
+        "\x0c"           // Value length (12)
+        "/sample/path";  // Value
+    EXPECT_EQ(kExpected, b.buffer());
+  }
+  {
+    HpackBlockBuilder b;
+    b.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+                                kUncompressed, "password", kUncompressed,
+                                "secret");
+    EXPECT_EQ(17u, b.size());
+
+    const char kExpected[] =
+        "\x10"      // == Literal never indexed ==
+        "\x08"      // Name length (8)
+        "password"  // Name
+        "\x06"      // Value length (6)
+        "secret";   // Value
+    EXPECT_EQ(kExpected, b.buffer());
+  }
+  {
+    HpackBlockBuilder b;
+    b.AppendIndexedHeader(2);
+    EXPECT_EQ(1u, b.size());
+
+    const char kExpected[] = "\x82";  // == Indexed (2) ==
+    EXPECT_EQ(kExpected, b.buffer());
+  }
+}
+
+// Tests of encoding per the RFC. See:
+//  http://httpwg.org/specs/rfc7541.html#request.examples.without.huffman.coding
+TEST(HpackBlockBuilderTest, ExamplesFromSpecC3) {
+  {
+    // Header block to encode:
+    //   :method: GET
+    //   :scheme: http
+    //   :path: /
+    //   :authority: www.example.com
+    HpackBlockBuilder b;
+    b.AppendIndexedHeader(2);  // :method: GET
+    b.AppendIndexedHeader(6);  // :scheme: http
+    b.AppendIndexedHeader(4);  // :path: /
+    b.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 1,
+                                     kUncompressed, "www.example.com");
+    EXPECT_EQ(20u, b.size());
+
+    // Hex dump of encoded data (copied from RFC):
+    // 0x0000:  8286 8441 0f77 7777 2e65 7861 6d70 6c65  ...A.www.example
+    // 0x0010:  2e63 6f6d                                .com
+
+    const std::string expected =
+        absl::HexStringToBytes("828684410f7777772e6578616d706c652e636f6d");
+    EXPECT_EQ(expected, b.buffer());
+  }
+}
+
+// Tests of encoding per the RFC. See:
+//   http://httpwg.org/specs/rfc7541.html#request.examples.with.huffman.coding
+TEST(HpackBlockBuilderTest, ExamplesFromSpecC4) {
+  {
+    // Header block to encode:
+    //   :method: GET
+    //   :scheme: http
+    //   :path: /
+    //   :authority: www.example.com  (Huffman encoded)
+    HpackBlockBuilder b;
+    b.AppendIndexedHeader(kStaticTableMethodGET);
+    b.AppendIndexedHeader(kStaticTableSchemeHttp);
+    b.AppendIndexedHeader(kStaticTablePathSlash);
+    const char kHuffmanWwwExampleCom[] = {'\xf1', '\xe3', '\xc2', '\xe5',
+                                          '\xf2', '\x3a', '\x6b', '\xa0',
+                                          '\xab', '\x90', '\xf4', '\xff'};
+    b.AppendNameIndexAndLiteralValue(
+        HpackEntryType::kIndexedLiteralHeader, 1, kCompressed,
+        absl::string_view(kHuffmanWwwExampleCom, sizeof kHuffmanWwwExampleCom));
+    EXPECT_EQ(17u, b.size());
+
+    // Hex dump of encoded data (copied from RFC):
+    // 0x0000:  8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4  ...A......:k....
+    // 0x0010:  ff                                       .
+
+    const std::string expected =
+        absl::HexStringToBytes("828684418cf1e3c2e5f23a6ba0ab90f4ff");
+    EXPECT_EQ(expected, b.buffer());
+  }
+}
+
+TEST(HpackBlockBuilderTest, DynamicTableSizeUpdate) {
+  {
+    HpackBlockBuilder b;
+    b.AppendDynamicTableSizeUpdate(0);
+    EXPECT_EQ(1u, b.size());
+
+    const char kData[] = {'\x20'};
+    absl::string_view expected(kData, sizeof kData);
+    EXPECT_EQ(expected, b.buffer());
+  }
+  {
+    HpackBlockBuilder b;
+    b.AppendDynamicTableSizeUpdate(4096);  // The default size.
+    EXPECT_EQ(3u, b.size());
+
+    const char kData[] = {'\x3f', '\xe1', '\x1f'};
+    absl::string_view expected(kData, sizeof kData);
+    EXPECT_EQ(expected, b.buffer());
+  }
+  {
+    HpackBlockBuilder b;
+    b.AppendDynamicTableSizeUpdate(1000000000000);  // A very large value.
+    EXPECT_EQ(7u, b.size());
+
+    const char kData[] = {'\x3f', '\xe1', '\x9f', '\x94',
+                          '\xa5', '\x8d', '\x1d'};
+    absl::string_view expected(kData, sizeof kData);
+    EXPECT_EQ(expected, b.buffer());
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/hpack_block_collector.cc b/quiche/http2/test_tools/hpack_block_collector.cc
new file mode 100644
index 0000000..7cd01ac
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_block_collector.cc
@@ -0,0 +1,143 @@
+// Copyright 2016 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/http2/test_tools/hpack_block_collector.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_test_helpers.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+
+HpackBlockCollector::HpackBlockCollector() = default;
+HpackBlockCollector::HpackBlockCollector(const HpackBlockCollector& other)
+    : pending_entry_(other.pending_entry_), entries_(other.entries_) {}
+HpackBlockCollector::~HpackBlockCollector() = default;
+
+void HpackBlockCollector::OnIndexedHeader(size_t index) {
+  pending_entry_.OnIndexedHeader(index);
+  PushPendingEntry();
+}
+void HpackBlockCollector::OnDynamicTableSizeUpdate(size_t size) {
+  pending_entry_.OnDynamicTableSizeUpdate(size);
+  PushPendingEntry();
+}
+void HpackBlockCollector::OnStartLiteralHeader(HpackEntryType header_type,
+                                               size_t maybe_name_index) {
+  pending_entry_.OnStartLiteralHeader(header_type, maybe_name_index);
+}
+void HpackBlockCollector::OnNameStart(bool huffman_encoded, size_t len) {
+  pending_entry_.OnNameStart(huffman_encoded, len);
+}
+void HpackBlockCollector::OnNameData(const char* data, size_t len) {
+  pending_entry_.OnNameData(data, len);
+}
+void HpackBlockCollector::OnNameEnd() { pending_entry_.OnNameEnd(); }
+void HpackBlockCollector::OnValueStart(bool huffman_encoded, size_t len) {
+  pending_entry_.OnValueStart(huffman_encoded, len);
+}
+void HpackBlockCollector::OnValueData(const char* data, size_t len) {
+  pending_entry_.OnValueData(data, len);
+}
+void HpackBlockCollector::OnValueEnd() {
+  pending_entry_.OnValueEnd();
+  PushPendingEntry();
+}
+
+void HpackBlockCollector::PushPendingEntry() {
+  EXPECT_TRUE(pending_entry_.IsComplete());
+  QUICHE_DVLOG(2) << "PushPendingEntry: " << pending_entry_;
+  entries_.push_back(pending_entry_);
+  EXPECT_TRUE(entries_.back().IsComplete());
+  pending_entry_.Clear();
+}
+void HpackBlockCollector::Clear() {
+  pending_entry_.Clear();
+  entries_.clear();
+}
+
+void HpackBlockCollector::ExpectIndexedHeader(size_t index) {
+  entries_.push_back(
+      HpackEntryCollector(HpackEntryType::kIndexedHeader, index));
+}
+void HpackBlockCollector::ExpectDynamicTableSizeUpdate(size_t size) {
+  entries_.push_back(
+      HpackEntryCollector(HpackEntryType::kDynamicTableSizeUpdate, size));
+}
+void HpackBlockCollector::ExpectNameIndexAndLiteralValue(
+    HpackEntryType type, size_t index, bool value_huffman,
+    const std::string& value) {
+  entries_.push_back(HpackEntryCollector(type, index, value_huffman, value));
+}
+void HpackBlockCollector::ExpectLiteralNameAndValue(HpackEntryType type,
+                                                    bool name_huffman,
+                                                    const std::string& name,
+                                                    bool value_huffman,
+                                                    const std::string& value) {
+  entries_.push_back(
+      HpackEntryCollector(type, name_huffman, name, value_huffman, value));
+}
+
+void HpackBlockCollector::ShuffleEntries(Http2Random* rng) {
+  std::shuffle(entries_.begin(), entries_.end(), *rng);
+}
+
+void HpackBlockCollector::AppendToHpackBlockBuilder(
+    HpackBlockBuilder* hbb) const {
+  QUICHE_CHECK(IsNotPending());
+  for (const auto& entry : entries_) {
+    entry.AppendToHpackBlockBuilder(hbb);
+  }
+}
+
+AssertionResult HpackBlockCollector::ValidateSoleIndexedHeader(
+    size_t ndx) const {
+  VERIFY_TRUE(pending_entry_.IsClear());
+  VERIFY_EQ(1u, entries_.size());
+  VERIFY_TRUE(entries_.front().ValidateIndexedHeader(ndx));
+  return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleLiteralValueHeader(
+    HpackEntryType expected_type, size_t expected_index,
+    bool expected_value_huffman, absl::string_view expected_value) const {
+  VERIFY_TRUE(pending_entry_.IsClear());
+  VERIFY_EQ(1u, entries_.size());
+  VERIFY_TRUE(entries_.front().ValidateLiteralValueHeader(
+      expected_type, expected_index, expected_value_huffman, expected_value));
+  return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleLiteralNameValueHeader(
+    HpackEntryType expected_type, bool expected_name_huffman,
+    absl::string_view expected_name, bool expected_value_huffman,
+    absl::string_view expected_value) const {
+  VERIFY_TRUE(pending_entry_.IsClear());
+  VERIFY_EQ(1u, entries_.size());
+  VERIFY_TRUE(entries_.front().ValidateLiteralNameValueHeader(
+      expected_type, expected_name_huffman, expected_name,
+      expected_value_huffman, expected_value));
+  return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleDynamicTableSizeUpdate(
+    size_t size) const {
+  VERIFY_TRUE(pending_entry_.IsClear());
+  VERIFY_EQ(1u, entries_.size());
+  VERIFY_TRUE(entries_.front().ValidateDynamicTableSizeUpdate(size));
+  return AssertionSuccess();
+}
+
+AssertionResult HpackBlockCollector::VerifyEq(
+    const HpackBlockCollector& that) const {
+  VERIFY_EQ(pending_entry_, that.pending_entry_);
+  VERIFY_EQ(entries_, that.entries_);
+  return AssertionSuccess();
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/hpack_block_collector.h b/quiche/http2/test_tools/hpack_block_collector.h
new file mode 100644
index 0000000..fe49d09
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_block_collector.h
@@ -0,0 +1,122 @@
+// Copyright 2016 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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HPACK_BLOCK_COLLECTOR_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HPACK_BLOCK_COLLECTOR_H_
+
+// HpackBlockCollector implements HpackEntryDecoderListener in order to record
+// the calls using HpackEntryCollector instances (one per HPACK entry). This
+// supports testing of HpackBlockDecoder, which decodes entire HPACK blocks.
+//
+// In addition to implementing the callback methods, HpackBlockCollector also
+// supports comparing two HpackBlockCollector instances (i.e. an expected and
+// an actual), or a sole HPACK entry against an expected value.
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "quiche/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "quiche/http2/hpack/http2_hpack_constants.h"
+#include "quiche/http2/test_tools/hpack_block_builder.h"
+#include "quiche/http2/test_tools/hpack_entry_collector.h"
+#include "quiche/http2/test_tools/http2_random.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace http2 {
+namespace test {
+
+class QUICHE_NO_EXPORT HpackBlockCollector : public HpackEntryDecoderListener {
+ public:
+  HpackBlockCollector();
+  HpackBlockCollector(const HpackBlockCollector& other);
+  ~HpackBlockCollector() override;
+
+  // Implementations of HpackEntryDecoderListener, forwarding to pending_entry_,
+  // an HpackEntryCollector for the "in-progress" HPACK entry. OnIndexedHeader
+  // and OnDynamicTableSizeUpdate are pending only for that one call, while
+  // OnStartLiteralHeader is followed by many calls, ending with OnValueEnd.
+  // Once all the calls for one HPACK entry have been received, PushPendingEntry
+  // is used to append the pending_entry_ entry to the collected entries_.
+  void OnIndexedHeader(size_t index) override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+  void OnStartLiteralHeader(HpackEntryType header_type,
+                            size_t maybe_name_index) override;
+  void OnNameStart(bool huffman_encoded, size_t len) override;
+  void OnNameData(const char* data, size_t len) override;
+  void OnNameEnd() override;
+  void OnValueStart(bool huffman_encoded, size_t len) override;
+  void OnValueData(const char* data, size_t len) override;
+  void OnValueEnd() override;
+
+  // Methods for creating a set of expectations (i.e. HPACK entries to compare
+  // against those collected by another instance of HpackBlockCollector).
+
+  // Add an HPACK entry for an indexed header.
+  void ExpectIndexedHeader(size_t index);
+
+  // Add an HPACK entry for a dynamic table size update.
+  void ExpectDynamicTableSizeUpdate(size_t size);
+
+  // Add an HPACK entry for a header entry with an index for the name, and a
+  // literal value.
+  void ExpectNameIndexAndLiteralValue(HpackEntryType type, size_t index,
+                                      bool value_huffman,
+                                      const std::string& value);
+
+  // Add an HPACK entry for a header entry with a literal name and value.
+  void ExpectLiteralNameAndValue(HpackEntryType type, bool name_huffman,
+                                 const std::string& name, bool value_huffman,
+                                 const std::string& value);
+
+  // Shuffle the entries, in support of generating an HPACK block of entries
+  // in some random order.
+  void ShuffleEntries(Http2Random* rng);
+
+  // Serialize entries_ to the HpackBlockBuilder.
+  void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const;
+
+  // Return AssertionSuccess if there is just one entry, and it is an
+  // Indexed Header with the specified index.
+  ::testing::AssertionResult ValidateSoleIndexedHeader(size_t ndx) const;
+
+  // Return AssertionSuccess if there is just one entry, and it is a
+  // Dynamic Table Size Update with the specified size.
+  ::testing::AssertionResult ValidateSoleDynamicTableSizeUpdate(
+      size_t size) const;
+
+  // Return AssertionSuccess if there is just one entry, and it is a Header
+  // entry with an index for the name and a literal value.
+  ::testing::AssertionResult ValidateSoleLiteralValueHeader(
+      HpackEntryType expected_type, size_t expected_index,
+      bool expected_value_huffman, absl::string_view expected_value) const;
+
+  // Return AssertionSuccess if there is just one entry, and it is a Header
+  // with a literal name and literal value.
+  ::testing::AssertionResult ValidateSoleLiteralNameValueHeader(
+      HpackEntryType expected_type, bool expected_name_huffman,
+      absl::string_view expected_name, bool expected_value_huffman,
+      absl::string_view expected_value) const;
+
+  bool IsNotPending() const { return pending_entry_.IsClear(); }
+  bool IsClear() const { return IsNotPending() && entries_.empty(); }
+  void Clear();
+
+  ::testing::AssertionResult VerifyEq(const HpackBlockCollector& that) const;
+
+ private:
+  // Push the value of pending_entry_ onto entries_, and clear pending_entry_.
+  // The pending_entry_ must be complete.
+  void PushPendingEntry();
+
+  HpackEntryCollector pending_entry_;
+  std::vector<HpackEntryCollector> entries_;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_HPACK_BLOCK_COLLECTOR_H_
diff --git a/quiche/http2/test_tools/hpack_entry_collector.cc b/quiche/http2/test_tools/hpack_entry_collector.cc
new file mode 100644
index 0000000..b99e35c
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_entry_collector.cc
@@ -0,0 +1,291 @@
+// Copyright 2016 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/http2/test_tools/hpack_entry_collector.h"
+
+#include "absl/strings/str_cat.h"
+#include "quiche/http2/hpack/http2_hpack_constants.h"
+#include "quiche/http2/test_tools/hpack_string_collector.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/platform/api/quiche_test_helpers.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+const HpackEntryType kInvalidHeaderType = static_cast<HpackEntryType>(99);
+const size_t kInvalidIndex = 99999999;
+
+}  // namespace
+
+HpackEntryCollector::HpackEntryCollector() { Clear(); }
+
+HpackEntryCollector::HpackEntryCollector(const HpackEntryCollector& other) =
+    default;
+
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
+                                         size_t index_or_size)
+    : header_type_(type), index_(index_or_size), started_(true), ended_(true) {}
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type, size_t index,
+                                         bool value_huffman,
+                                         const std::string& value)
+    : header_type_(type),
+      index_(index),
+      value_(value, value_huffman),
+      started_(true),
+      ended_(true) {}
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type, bool name_huffman,
+                                         const std::string& name,
+                                         bool value_huffman,
+                                         const std::string& value)
+    : header_type_(type),
+      index_(0),
+      name_(name, name_huffman),
+      value_(value, value_huffman),
+      started_(true),
+      ended_(true) {}
+
+HpackEntryCollector::~HpackEntryCollector() = default;
+
+void HpackEntryCollector::OnIndexedHeader(size_t index) {
+  ASSERT_FALSE(started_);
+  ASSERT_TRUE(IsClear()) << ToString();
+  Init(HpackEntryType::kIndexedHeader, index);
+  ended_ = true;
+}
+void HpackEntryCollector::OnStartLiteralHeader(HpackEntryType header_type,
+                                               size_t maybe_name_index) {
+  ASSERT_FALSE(started_);
+  ASSERT_TRUE(IsClear()) << ToString();
+  Init(header_type, maybe_name_index);
+}
+void HpackEntryCollector::OnNameStart(bool huffman_encoded, size_t len) {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  ASSERT_FALSE(IsClear());
+  ASSERT_TRUE(LiteralNameExpected()) << ToString();
+  name_.OnStringStart(huffman_encoded, len);
+}
+void HpackEntryCollector::OnNameData(const char* data, size_t len) {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  ASSERT_TRUE(LiteralNameExpected()) << ToString();
+  ASSERT_TRUE(name_.IsInProgress());
+  name_.OnStringData(data, len);
+}
+void HpackEntryCollector::OnNameEnd() {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  ASSERT_TRUE(LiteralNameExpected()) << ToString();
+  ASSERT_TRUE(name_.IsInProgress());
+  name_.OnStringEnd();
+}
+void HpackEntryCollector::OnValueStart(bool huffman_encoded, size_t len) {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  if (LiteralNameExpected()) {
+    ASSERT_TRUE(name_.HasEnded());
+  }
+  ASSERT_TRUE(LiteralValueExpected()) << ToString();
+  ASSERT_TRUE(value_.IsClear()) << value_.ToString();
+  value_.OnStringStart(huffman_encoded, len);
+}
+void HpackEntryCollector::OnValueData(const char* data, size_t len) {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  ASSERT_TRUE(LiteralValueExpected()) << ToString();
+  ASSERT_TRUE(value_.IsInProgress());
+  value_.OnStringData(data, len);
+}
+void HpackEntryCollector::OnValueEnd() {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  ASSERT_TRUE(LiteralValueExpected()) << ToString();
+  ASSERT_TRUE(value_.IsInProgress());
+  value_.OnStringEnd();
+  ended_ = true;
+}
+void HpackEntryCollector::OnDynamicTableSizeUpdate(size_t size) {
+  ASSERT_FALSE(started_);
+  ASSERT_TRUE(IsClear()) << ToString();
+  Init(HpackEntryType::kDynamicTableSizeUpdate, size);
+  ended_ = true;
+}
+
+void HpackEntryCollector::Clear() {
+  header_type_ = kInvalidHeaderType;
+  index_ = kInvalidIndex;
+  name_.Clear();
+  value_.Clear();
+  started_ = ended_ = false;
+}
+bool HpackEntryCollector::IsClear() const {
+  return header_type_ == kInvalidHeaderType && index_ == kInvalidIndex &&
+         name_.IsClear() && value_.IsClear() && !started_ && !ended_;
+}
+bool HpackEntryCollector::IsComplete() const { return started_ && ended_; }
+bool HpackEntryCollector::LiteralNameExpected() const {
+  switch (header_type_) {
+    case HpackEntryType::kIndexedLiteralHeader:
+    case HpackEntryType::kUnindexedLiteralHeader:
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      return index_ == 0;
+    default:
+      return false;
+  }
+}
+bool HpackEntryCollector::LiteralValueExpected() const {
+  switch (header_type_) {
+    case HpackEntryType::kIndexedLiteralHeader:
+    case HpackEntryType::kUnindexedLiteralHeader:
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      return true;
+    default:
+      return false;
+  }
+}
+AssertionResult HpackEntryCollector::ValidateIndexedHeader(
+    size_t expected_index) const {
+  VERIFY_TRUE(started_);
+  VERIFY_TRUE(ended_);
+  VERIFY_EQ(HpackEntryType::kIndexedHeader, header_type_);
+  VERIFY_EQ(expected_index, index_);
+  return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateLiteralValueHeader(
+    HpackEntryType expected_type, size_t expected_index,
+    bool expected_value_huffman, absl::string_view expected_value) const {
+  VERIFY_TRUE(started_);
+  VERIFY_TRUE(ended_);
+  VERIFY_EQ(expected_type, header_type_);
+  VERIFY_NE(0u, expected_index);
+  VERIFY_EQ(expected_index, index_);
+  VERIFY_TRUE(name_.IsClear());
+  VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman));
+  return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateLiteralNameValueHeader(
+    HpackEntryType expected_type, bool expected_name_huffman,
+    absl::string_view expected_name, bool expected_value_huffman,
+    absl::string_view expected_value) const {
+  VERIFY_TRUE(started_);
+  VERIFY_TRUE(ended_);
+  VERIFY_EQ(expected_type, header_type_);
+  VERIFY_EQ(0u, index_);
+  VERIFY_SUCCESS(name_.Collected(expected_name, expected_name_huffman));
+  VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman));
+  return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateDynamicTableSizeUpdate(
+    size_t size) const {
+  VERIFY_TRUE(started_);
+  VERIFY_TRUE(ended_);
+  VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, header_type_);
+  VERIFY_EQ(index_, size);
+  return ::testing::AssertionSuccess();
+}
+
+void HpackEntryCollector::AppendToHpackBlockBuilder(
+    HpackBlockBuilder* hbb) const {
+  ASSERT_TRUE(started_ && ended_) << *this;
+  switch (header_type_) {
+    case HpackEntryType::kIndexedHeader:
+      hbb->AppendIndexedHeader(index_);
+      return;
+
+    case HpackEntryType::kDynamicTableSizeUpdate:
+      hbb->AppendDynamicTableSizeUpdate(index_);
+      return;
+
+    case HpackEntryType::kIndexedLiteralHeader:
+    case HpackEntryType::kUnindexedLiteralHeader:
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      ASSERT_TRUE(value_.HasEnded()) << *this;
+      if (index_ != 0) {
+        QUICHE_CHECK(name_.IsClear());
+        hbb->AppendNameIndexAndLiteralValue(header_type_, index_,
+                                            value_.huffman_encoded, value_.s);
+      } else {
+        QUICHE_CHECK(name_.HasEnded()) << *this;
+        hbb->AppendLiteralNameAndValue(header_type_, name_.huffman_encoded,
+                                       name_.s, value_.huffman_encoded,
+                                       value_.s);
+      }
+      return;
+
+    default:
+      ADD_FAILURE() << *this;
+  }
+}
+
+std::string HpackEntryCollector::ToString() const {
+  std::string result("Type=");
+  switch (header_type_) {
+    case HpackEntryType::kIndexedHeader:
+      result += "IndexedHeader";
+      break;
+    case HpackEntryType::kDynamicTableSizeUpdate:
+      result += "DynamicTableSizeUpdate";
+      break;
+    case HpackEntryType::kIndexedLiteralHeader:
+      result += "IndexedLiteralHeader";
+      break;
+    case HpackEntryType::kUnindexedLiteralHeader:
+      result += "UnindexedLiteralHeader";
+      break;
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      result += "NeverIndexedLiteralHeader";
+      break;
+    default:
+      if (header_type_ == kInvalidHeaderType) {
+        result += "<unset>";
+      } else {
+        absl::StrAppend(&result, header_type_);
+      }
+  }
+  if (index_ != 0) {
+    absl::StrAppend(&result, " Index=", index_);
+  }
+  if (!name_.IsClear()) {
+    absl::StrAppend(&result, " Name", name_.ToString());
+  }
+  if (!value_.IsClear()) {
+    absl::StrAppend(&result, " Value", value_.ToString());
+  }
+  if (!started_) {
+    EXPECT_FALSE(ended_);
+    absl::StrAppend(&result, " !started");
+  } else if (!ended_) {
+    absl::StrAppend(&result, " !ended");
+  } else {
+    absl::StrAppend(&result, " Complete");
+  }
+  return result;
+}
+
+void HpackEntryCollector::Init(HpackEntryType type, size_t maybe_index) {
+  ASSERT_TRUE(IsClear()) << ToString();
+  header_type_ = type;
+  index_ = maybe_index;
+  started_ = true;
+}
+
+bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b) {
+  return a.name() == b.name() && a.value() == b.value() &&
+         a.index() == b.index() && a.header_type() == b.header_type() &&
+         a.started() == b.started() && a.ended() == b.ended();
+}
+bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b) {
+  return !(a == b);
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v) {
+  return out << v.ToString();
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/hpack_entry_collector.h b/quiche/http2/test_tools/hpack_entry_collector.h
new file mode 100644
index 0000000..93ad860
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_entry_collector.h
@@ -0,0 +1,151 @@
+// Copyright 2016 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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HPACK_ENTRY_COLLECTOR_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HPACK_ENTRY_COLLECTOR_H_
+
+// HpackEntryCollector records calls to HpackEntryDecoderListener in support
+// of tests of HpackEntryDecoder, or which use it. Can only record the callbacks
+// for the decoding of a single entry; call Clear() between decoding successive
+// entries or use a distinct HpackEntryCollector for each entry.
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "quiche/http2/hpack/http2_hpack_constants.h"
+#include "quiche/http2/test_tools/hpack_block_builder.h"
+#include "quiche/http2/test_tools/hpack_string_collector.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+
+class QUICHE_NO_EXPORT HpackEntryCollector : public HpackEntryDecoderListener {
+ public:
+  HpackEntryCollector();
+  HpackEntryCollector(const HpackEntryCollector& other);
+
+  // These next three constructors are intended for use in tests that create
+  // an HpackEntryCollector "manually", and then compare it against another
+  // that is populated via calls to the HpackEntryDecoderListener methods.
+  HpackEntryCollector(HpackEntryType type, size_t index_or_size);
+  HpackEntryCollector(HpackEntryType type, size_t index, bool value_huffman,
+                      const std::string& value);
+  HpackEntryCollector(HpackEntryType type, bool name_huffman,
+                      const std::string& name, bool value_huffman,
+                      const std::string& value);
+
+  ~HpackEntryCollector() override;
+
+  // Methods defined by HpackEntryDecoderListener.
+  void OnIndexedHeader(size_t index) override;
+  void OnStartLiteralHeader(HpackEntryType header_type,
+                            size_t maybe_name_index) override;
+  void OnNameStart(bool huffman_encoded, size_t len) override;
+  void OnNameData(const char* data, size_t len) override;
+  void OnNameEnd() override;
+  void OnValueStart(bool huffman_encoded, size_t len) override;
+  void OnValueData(const char* data, size_t len) override;
+  void OnValueEnd() override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+
+  // Clears the fields of the collector so that it is ready to start collecting
+  // another HPACK block entry.
+  void Clear();
+
+  // Is the collector ready to start collecting another HPACK block entry.
+  bool IsClear() const;
+
+  // Has a complete entry been collected?
+  bool IsComplete() const;
+
+  // Based on the HpackEntryType, is a literal name expected?
+  bool LiteralNameExpected() const;
+
+  // Based on the HpackEntryType, is a literal value expected?
+  bool LiteralValueExpected() const;
+
+  // Returns success if collected an Indexed Header (i.e. OnIndexedHeader was
+  // called).
+  ::testing::AssertionResult ValidateIndexedHeader(size_t expected_index) const;
+
+  // Returns success if collected a Header with an indexed name and literal
+  // value (i.e. OnStartLiteralHeader was called with a non-zero index for
+  // the name, which must match expected_index).
+  ::testing::AssertionResult ValidateLiteralValueHeader(
+      HpackEntryType expected_type, size_t expected_index,
+      bool expected_value_huffman, absl::string_view expected_value) const;
+
+  // Returns success if collected a Header with an literal name and literal
+  // value.
+  ::testing::AssertionResult ValidateLiteralNameValueHeader(
+      HpackEntryType expected_type, bool expected_name_huffman,
+      absl::string_view expected_name, bool expected_value_huffman,
+      absl::string_view expected_value) const;
+
+  // Returns success if collected a Dynamic Table Size Update,
+  // with the specified size.
+  ::testing::AssertionResult ValidateDynamicTableSizeUpdate(
+      size_t expected_size) const;
+
+  void set_header_type(HpackEntryType v) { header_type_ = v; }
+  HpackEntryType header_type() const { return header_type_; }
+
+  void set_index(size_t v) { index_ = v; }
+  size_t index() const { return index_; }
+
+  void set_name(const HpackStringCollector& v) { name_ = v; }
+  const HpackStringCollector& name() const { return name_; }
+
+  void set_value(const HpackStringCollector& v) { value_ = v; }
+  const HpackStringCollector& value() const { return value_; }
+
+  void set_started(bool v) { started_ = v; }
+  bool started() const { return started_; }
+
+  void set_ended(bool v) { ended_ = v; }
+  bool ended() const { return ended_; }
+
+  void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const;
+
+  // Returns a debug string.
+  std::string ToString() const;
+
+ private:
+  void Init(HpackEntryType type, size_t maybe_index);
+
+  HpackEntryType header_type_;
+  size_t index_;
+
+  HpackStringCollector name_;
+  HpackStringCollector value_;
+
+  // True if has received a call to an HpackEntryDecoderListener method
+  // indicating the start of decoding an HPACK entry; for example,
+  // OnIndexedHeader set it true, but OnNameStart does not change it.
+  bool started_ = false;
+
+  // True if has received a call to an HpackEntryDecoderListener method
+  // indicating the end of decoding an HPACK entry; for example,
+  // OnIndexedHeader and OnValueEnd both set it true, but OnNameEnd does
+  // not change it.
+  bool ended_ = false;
+};
+
+QUICHE_NO_EXPORT bool operator==(const HpackEntryCollector& a,
+                                 const HpackEntryCollector& b);
+QUICHE_NO_EXPORT bool operator!=(const HpackEntryCollector& a,
+                                 const HpackEntryCollector& b);
+QUICHE_NO_EXPORT std::ostream& operator<<(std::ostream& out,
+                                          const HpackEntryCollector& v);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_HPACK_ENTRY_COLLECTOR_H_
diff --git a/quiche/http2/test_tools/hpack_example.cc b/quiche/http2/test_tools/hpack_example.cc
new file mode 100644
index 0000000..42d9261
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_example.cc
@@ -0,0 +1,59 @@
+// Copyright 2016 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/http2/test_tools/hpack_example.h"
+
+#include <ctype.h>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+void HpackExampleToStringOrDie(absl::string_view example, std::string* output) {
+  while (!example.empty()) {
+    const char c0 = example[0];
+    if (isxdigit(c0)) {
+      QUICHE_CHECK_GT(example.size(), 1u) << "Truncated hex byte?";
+      const char c1 = example[1];
+      QUICHE_CHECK(isxdigit(c1)) << "Found half a byte?";
+      *output += absl::HexStringToBytes(example.substr(0, 2));
+      example.remove_prefix(2);
+      continue;
+    }
+    if (isspace(c0)) {
+      example.remove_prefix(1);
+      continue;
+    }
+    if (!example.empty() && example[0] == '|') {
+      // Start of a comment. Skip to end of line or of input.
+      auto pos = example.find('\n');
+      if (pos == absl::string_view::npos) {
+        // End of input.
+        break;
+      }
+      example.remove_prefix(pos + 1);
+      continue;
+    }
+    QUICHE_BUG(http2_bug_107_1)
+        << "Can't parse byte " << static_cast<int>(c0)
+        << absl::StrCat(" (0x", absl::Hex(c0), ")") << "\nExample: " << example;
+  }
+  QUICHE_CHECK_LT(0u, output->size()) << "Example is empty.";
+}
+
+}  // namespace
+
+std::string HpackExampleToStringOrDie(absl::string_view example) {
+  std::string output;
+  HpackExampleToStringOrDie(example, &output);
+  return output;
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/hpack_example.h b/quiche/http2/test_tools/hpack_example.h
new file mode 100644
index 0000000..007b969
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_example.h
@@ -0,0 +1,32 @@
+// Copyright 2016 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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HPACK_EXAMPLE_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HPACK_EXAMPLE_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+
+// Parses HPACK examples in the format seen in the HPACK specification,
+// RFC 7541. For example:
+//
+//       10                                      | == Literal never indexed ==
+//       08                                      |   Literal name (len = 8)
+//       7061 7373 776f 7264                     | password
+//       06                                      |   Literal value (len = 6)
+//       7365 6372 6574                          | secret
+//                                               | -> password: secret
+//
+// (excluding the leading "//").
+
+namespace http2 {
+namespace test {
+
+std::string HpackExampleToStringOrDie(absl::string_view example);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_HPACK_EXAMPLE_H_
diff --git a/quiche/http2/test_tools/hpack_example_test.cc b/quiche/http2/test_tools/hpack_example_test.cc
new file mode 100644
index 0000000..4dd24bd
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_example_test.cc
@@ -0,0 +1,45 @@
+#include "quiche/http2/test_tools/hpack_example.h"
+
+// Tests of HpackExampleToStringOrDie.
+
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+TEST(HpackExampleToStringOrDie, GoodInput) {
+  std::string bytes = HpackExampleToStringOrDie(R"(
+      40                                      | == Literal never indexed ==
+                                              | Blank lines are OK in example:
+
+      08                                      |   Literal name (len = 8)
+      7061 7373 776f 7264                     | password
+      06                                      |   Literal value (len = 6)
+      7365 6372 6574                          | secret
+                                              | -> password: secret
+      )");
+
+  // clang-format off
+  const char kExpected[] = {
+    0x40,                      // Never Indexed, Literal Name and Value
+    0x08,                      //  Name Len: 8
+    0x70, 0x61, 0x73, 0x73,    //      Name: password
+    0x77, 0x6f, 0x72, 0x64,    //
+    0x06,                      // Value Len: 6
+    0x73, 0x65, 0x63, 0x72,    //     Value: secret
+    0x65, 0x74,                //
+  };
+  // clang-format on
+  EXPECT_EQ(absl::string_view(kExpected, sizeof kExpected), bytes);
+}
+
+TEST(HpackExampleToStringOrDie, InvalidInput) {
+  EXPECT_DEATH(HpackExampleToStringOrDie("4"), "Truncated");
+  EXPECT_DEATH(HpackExampleToStringOrDie("4x"), "half");
+  EXPECT_DEATH(HpackExampleToStringOrDie(""), "empty");
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/hpack_string_collector.cc b/quiche/http2/test_tools/hpack_string_collector.cc
new file mode 100644
index 0000000..63a78b2
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_string_collector.cc
@@ -0,0 +1,117 @@
+// Copyright 2016 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/http2/test_tools/hpack_string_collector.h"
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <ostream>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/platform/api/quiche_test_helpers.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+std::ostream& operator<<(std::ostream& out,
+                         HpackStringCollector::CollectorState v) {
+  switch (v) {
+    case HpackStringCollector::CollectorState::kGenesis:
+      return out << "kGenesis";
+    case HpackStringCollector::CollectorState::kStarted:
+      return out << "kStarted";
+    case HpackStringCollector::CollectorState::kEnded:
+      return out << "kEnded";
+  }
+  return out << "UnknownCollectorState";
+}
+
+}  // namespace
+
+HpackStringCollector::HpackStringCollector() { Clear(); }
+
+HpackStringCollector::HpackStringCollector(const std::string& str, bool huffman)
+    : s(str), len(str.size()), huffman_encoded(huffman), state(kEnded) {}
+
+void HpackStringCollector::Clear() {
+  s = "";
+  len = 0;
+  huffman_encoded = false;
+  state = kGenesis;
+}
+
+bool HpackStringCollector::IsClear() const {
+  return s.empty() && len == 0 && huffman_encoded == false && state == kGenesis;
+}
+
+bool HpackStringCollector::IsInProgress() const { return state == kStarted; }
+
+bool HpackStringCollector::HasEnded() const { return state == kEnded; }
+
+void HpackStringCollector::OnStringStart(bool huffman, size_t length) {
+  EXPECT_TRUE(IsClear()) << ToString();
+  state = kStarted;
+  huffman_encoded = huffman;
+  len = length;
+}
+
+void HpackStringCollector::OnStringData(const char* data, size_t length) {
+  absl::string_view sp(data, length);
+  EXPECT_TRUE(IsInProgress()) << ToString();
+  EXPECT_LE(sp.size(), len) << ToString();
+  absl::StrAppend(&s, sp);
+  EXPECT_LE(s.size(), len) << ToString();
+}
+
+void HpackStringCollector::OnStringEnd() {
+  EXPECT_TRUE(IsInProgress()) << ToString();
+  EXPECT_EQ(s.size(), len) << ToString();
+  state = kEnded;
+}
+
+::testing::AssertionResult HpackStringCollector::Collected(
+    absl::string_view str, bool is_huffman_encoded) const {
+  VERIFY_TRUE(HasEnded());
+  VERIFY_EQ(str.size(), len);
+  VERIFY_EQ(is_huffman_encoded, huffman_encoded);
+  VERIFY_EQ(str, s);
+  return ::testing::AssertionSuccess();
+}
+
+std::string HpackStringCollector::ToString() const {
+  std::stringstream ss;
+  ss << *this;
+  return ss.str();
+}
+
+bool operator==(const HpackStringCollector& a, const HpackStringCollector& b) {
+  return a.s == b.s && a.len == b.len &&
+         a.huffman_encoded == b.huffman_encoded && a.state == b.state;
+}
+
+bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b) {
+  return !(a == b);
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v) {
+  out << "HpackStringCollector(state=" << v.state;
+  if (v.state == HpackStringCollector::kGenesis) {
+    return out << ")";
+  }
+  if (v.huffman_encoded) {
+    out << ", Huffman Encoded";
+  }
+  out << ", Length=" << v.len;
+  if (!v.s.empty() && v.len != v.s.size()) {
+    out << " (" << v.s.size() << ")";
+  }
+  return out << ", String=\"" << absl::CHexEscape(v.s) << "\")";
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/hpack_string_collector.h b/quiche/http2/test_tools/hpack_string_collector.h
new file mode 100644
index 0000000..15dab67
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_string_collector.h
@@ -0,0 +1,66 @@
+// Copyright 2016 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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HPACK_STRING_COLLECTOR_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HPACK_STRING_COLLECTOR_H_
+
+// Supports tests of decoding HPACK strings.
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/http2/hpack/decoder/hpack_string_decoder_listener.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+
+// Records the callbacks associated with a decoding a string; must
+// call Clear() between decoding successive strings.
+struct QUICHE_NO_EXPORT HpackStringCollector
+    : public HpackStringDecoderListener {
+  enum CollectorState {
+    kGenesis,
+    kStarted,
+    kEnded,
+  };
+
+  HpackStringCollector();
+  HpackStringCollector(const std::string& str, bool huffman);
+
+  void Clear();
+  bool IsClear() const;
+  bool IsInProgress() const;
+  bool HasEnded() const;
+
+  void OnStringStart(bool huffman, size_t length) override;
+  void OnStringData(const char* data, size_t length) override;
+  void OnStringEnd() override;
+
+  ::testing::AssertionResult Collected(absl::string_view str,
+                                       bool is_huffman_encoded) const;
+
+  std::string ToString() const;
+
+  std::string s;
+  size_t len;
+  bool huffman_encoded;
+  CollectorState state;
+};
+
+bool operator==(const HpackStringCollector& a, const HpackStringCollector& b);
+
+bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b);
+
+QUICHE_NO_EXPORT std::ostream& operator<<(std::ostream& out,
+                                          const HpackStringCollector& v);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_HPACK_STRING_COLLECTOR_H_
diff --git a/quiche/http2/test_tools/http2_constants_test_util.cc b/quiche/http2/test_tools/http2_constants_test_util.cc
new file mode 100644
index 0000000..ddb5cbd
--- /dev/null
+++ b/quiche/http2/test_tools/http2_constants_test_util.cc
@@ -0,0 +1,84 @@
+// Copyright 2016 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/http2/test_tools/http2_constants_test_util.h"
+
+namespace http2 {
+namespace test {
+
+std::vector<Http2ErrorCode> AllHttp2ErrorCodes() {
+  // clang-format off
+  return {
+      Http2ErrorCode::HTTP2_NO_ERROR,
+      Http2ErrorCode::PROTOCOL_ERROR,
+      Http2ErrorCode::INTERNAL_ERROR,
+      Http2ErrorCode::FLOW_CONTROL_ERROR,
+      Http2ErrorCode::SETTINGS_TIMEOUT,
+      Http2ErrorCode::STREAM_CLOSED,
+      Http2ErrorCode::FRAME_SIZE_ERROR,
+      Http2ErrorCode::REFUSED_STREAM,
+      Http2ErrorCode::CANCEL,
+      Http2ErrorCode::COMPRESSION_ERROR,
+      Http2ErrorCode::CONNECT_ERROR,
+      Http2ErrorCode::ENHANCE_YOUR_CALM,
+      Http2ErrorCode::INADEQUATE_SECURITY,
+      Http2ErrorCode::HTTP_1_1_REQUIRED,
+  };
+  // clang-format on
+}
+
+std::vector<Http2SettingsParameter> AllHttp2SettingsParameters() {
+  // clang-format off
+  return {
+      Http2SettingsParameter::HEADER_TABLE_SIZE,
+      Http2SettingsParameter::ENABLE_PUSH,
+      Http2SettingsParameter::MAX_CONCURRENT_STREAMS,
+      Http2SettingsParameter::INITIAL_WINDOW_SIZE,
+      Http2SettingsParameter::MAX_FRAME_SIZE,
+      Http2SettingsParameter::MAX_HEADER_LIST_SIZE,
+  };
+  // clang-format on
+}
+
+// Returns a mask of flags supported for the specified frame type. Returns
+// zero for unknown frame types.
+uint8_t KnownFlagsMaskForFrameType(Http2FrameType type) {
+  switch (type) {
+    case Http2FrameType::DATA:
+      return Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED;
+    case Http2FrameType::HEADERS:
+      return Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS |
+             Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY;
+    case Http2FrameType::PRIORITY:
+      return 0x00;
+    case Http2FrameType::RST_STREAM:
+      return 0x00;
+    case Http2FrameType::SETTINGS:
+      return Http2FrameFlag::ACK;
+    case Http2FrameType::PUSH_PROMISE:
+      return Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED;
+    case Http2FrameType::PING:
+      return Http2FrameFlag::ACK;
+    case Http2FrameType::GOAWAY:
+      return 0x00;
+    case Http2FrameType::WINDOW_UPDATE:
+      return 0x00;
+    case Http2FrameType::CONTINUATION:
+      return Http2FrameFlag::END_HEADERS;
+    case Http2FrameType::ALTSVC:
+      return 0x00;
+    default:
+      return 0x00;
+  }
+}
+
+uint8_t InvalidFlagMaskForFrameType(Http2FrameType type) {
+  if (IsSupportedHttp2FrameType(type)) {
+    return ~KnownFlagsMaskForFrameType(type);
+  }
+  return 0x00;
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/http2_constants_test_util.h b/quiche/http2/test_tools/http2_constants_test_util.h
new file mode 100644
index 0000000..20edc8d
--- /dev/null
+++ b/quiche/http2/test_tools/http2_constants_test_util.h
@@ -0,0 +1,34 @@
+// Copyright 2016 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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HTTP2_CONSTANTS_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_CONSTANTS_TEST_UTIL_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "quiche/http2/http2_constants.h"
+
+namespace http2 {
+namespace test {
+
+// Returns a vector of all supported RST_STREAM and GOAWAY error codes.
+std::vector<Http2ErrorCode> AllHttp2ErrorCodes();
+
+// Returns a vector of all supported parameters in SETTINGS frames.
+std::vector<Http2SettingsParameter> AllHttp2SettingsParameters();
+
+// Returns a mask of flags supported for the specified frame type. Returns
+// zero for unknown frame types.
+uint8_t KnownFlagsMaskForFrameType(Http2FrameType type);
+
+// Returns a mask of flag bits known to be invalid for the frame type.
+// For unknown frame types, the mask is zero; i.e., we don't know that any
+// are invalid.
+uint8_t InvalidFlagMaskForFrameType(Http2FrameType type);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_HTTP2_CONSTANTS_TEST_UTIL_H_
diff --git a/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.cc b/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.cc
new file mode 100644
index 0000000..fea56f2
--- /dev/null
+++ b/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.cc
@@ -0,0 +1,511 @@
+// Copyright 2016 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/http2/test_tools/http2_frame_decoder_listener_test_util.h"
+
+#include "quiche/http2/decoder/http2_frame_decoder_listener.h"
+#include "quiche/http2/http2_constants.h"
+#include "quiche/http2/http2_structures.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+
+FailingHttp2FrameDecoderListener::FailingHttp2FrameDecoderListener() = default;
+FailingHttp2FrameDecoderListener::~FailingHttp2FrameDecoderListener() = default;
+
+bool FailingHttp2FrameDecoderListener::OnFrameHeader(
+    const Http2FrameHeader& header) {
+  ADD_FAILURE() << "OnFrameHeader: " << header;
+  return false;
+}
+
+void FailingHttp2FrameDecoderListener::OnDataStart(
+    const Http2FrameHeader& header) {
+  FAIL() << "OnDataStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnDataPayload(const char* /*data*/,
+                                                     size_t len) {
+  FAIL() << "OnDataPayload: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnDataEnd() { FAIL() << "OnDataEnd"; }
+
+void FailingHttp2FrameDecoderListener::OnHeadersStart(
+    const Http2FrameHeader& header) {
+  FAIL() << "OnHeadersStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnHeadersPriority(
+    const Http2PriorityFields& priority) {
+  FAIL() << "OnHeadersPriority: " << priority;
+}
+
+void FailingHttp2FrameDecoderListener::OnHpackFragment(const char* /*data*/,
+                                                       size_t len) {
+  FAIL() << "OnHpackFragment: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnHeadersEnd() {
+  FAIL() << "OnHeadersEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPriorityFrame(
+    const Http2FrameHeader& header, const Http2PriorityFields& priority) {
+  FAIL() << "OnPriorityFrame: " << header << "; priority: " << priority;
+}
+
+void FailingHttp2FrameDecoderListener::OnContinuationStart(
+    const Http2FrameHeader& header) {
+  FAIL() << "OnContinuationStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnContinuationEnd() {
+  FAIL() << "OnContinuationEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPadLength(size_t trailing_length) {
+  FAIL() << "OnPadLength: trailing_length=" << trailing_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnPadding(const char* /*padding*/,
+                                                 size_t skipped_length) {
+  FAIL() << "OnPadding: skipped_length=" << skipped_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnRstStream(
+    const Http2FrameHeader& header, Http2ErrorCode error_code) {
+  FAIL() << "OnRstStream: " << header << "; code=" << error_code;
+}
+
+void FailingHttp2FrameDecoderListener::OnSettingsStart(
+    const Http2FrameHeader& header) {
+  FAIL() << "OnSettingsStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnSetting(
+    const Http2SettingFields& setting_fields) {
+  FAIL() << "OnSetting: " << setting_fields;
+}
+
+void FailingHttp2FrameDecoderListener::OnSettingsEnd() {
+  FAIL() << "OnSettingsEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnSettingsAck(
+    const Http2FrameHeader& header) {
+  FAIL() << "OnSettingsAck: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnPushPromiseStart(
+    const Http2FrameHeader& header, const Http2PushPromiseFields& promise,
+    size_t total_padding_length) {
+  FAIL() << "OnPushPromiseStart: " << header << "; promise: " << promise
+         << "; total_padding_length: " << total_padding_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnPushPromiseEnd() {
+  FAIL() << "OnPushPromiseEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPing(const Http2FrameHeader& header,
+                                              const Http2PingFields& ping) {
+  FAIL() << "OnPing: " << header << "; ping: " << ping;
+}
+
+void FailingHttp2FrameDecoderListener::OnPingAck(const Http2FrameHeader& header,
+                                                 const Http2PingFields& ping) {
+  FAIL() << "OnPingAck: " << header << "; ping: " << ping;
+}
+
+void FailingHttp2FrameDecoderListener::OnGoAwayStart(
+    const Http2FrameHeader& header, const Http2GoAwayFields& goaway) {
+  FAIL() << "OnGoAwayStart: " << header << "; goaway: " << goaway;
+}
+
+void FailingHttp2FrameDecoderListener::OnGoAwayOpaqueData(const char* /*data*/,
+                                                          size_t len) {
+  FAIL() << "OnGoAwayOpaqueData: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnGoAwayEnd() {
+  FAIL() << "OnGoAwayEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnWindowUpdate(
+    const Http2FrameHeader& header, uint32_t increment) {
+  FAIL() << "OnWindowUpdate: " << header << "; increment=" << increment;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcStart(
+    const Http2FrameHeader& header, size_t origin_length, size_t value_length) {
+  FAIL() << "OnAltSvcStart: " << header << "; origin_length: " << origin_length
+         << "; value_length: " << value_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcOriginData(const char* /*data*/,
+                                                          size_t len) {
+  FAIL() << "OnAltSvcOriginData: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcValueData(const char* /*data*/,
+                                                         size_t len) {
+  FAIL() << "OnAltSvcValueData: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcEnd() {
+  FAIL() << "OnAltSvcEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPriorityUpdateStart(
+    const Http2FrameHeader& header,
+    const Http2PriorityUpdateFields& priority_update) {
+  FAIL() << "OnPriorityUpdateStart: " << header << "; prioritized_stream_id: "
+         << priority_update.prioritized_stream_id;
+}
+
+void FailingHttp2FrameDecoderListener::OnPriorityUpdatePayload(
+    const char* /*data*/, size_t len) {
+  FAIL() << "OnPriorityUpdatePayload: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnPriorityUpdateEnd() {
+  FAIL() << "OnPriorityUpdateEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnUnknownStart(
+    const Http2FrameHeader& header) {
+  FAIL() << "OnUnknownStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnUnknownPayload(const char* /*data*/,
+                                                        size_t len) {
+  FAIL() << "OnUnknownPayload: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnUnknownEnd() {
+  FAIL() << "OnUnknownEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPaddingTooLong(
+    const Http2FrameHeader& header, size_t missing_length) {
+  FAIL() << "OnPaddingTooLong: " << header
+         << "; missing_length: " << missing_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnFrameSizeError(
+    const Http2FrameHeader& header) {
+  FAIL() << "OnFrameSizeError: " << header;
+}
+
+LoggingHttp2FrameDecoderListener::LoggingHttp2FrameDecoderListener()
+    : wrapped_(nullptr) {}
+LoggingHttp2FrameDecoderListener::LoggingHttp2FrameDecoderListener(
+    Http2FrameDecoderListener* wrapped)
+    : wrapped_(wrapped) {}
+LoggingHttp2FrameDecoderListener::~LoggingHttp2FrameDecoderListener() = default;
+
+bool LoggingHttp2FrameDecoderListener::OnFrameHeader(
+    const Http2FrameHeader& header) {
+  QUICHE_VLOG(1) << "OnFrameHeader: " << header;
+  if (wrapped_ != nullptr) {
+    return wrapped_->OnFrameHeader(header);
+  }
+  return true;
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataStart(
+    const Http2FrameHeader& header) {
+  QUICHE_VLOG(1) << "OnDataStart: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnDataStart(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataPayload(const char* data,
+                                                     size_t len) {
+  QUICHE_VLOG(1) << "OnDataPayload: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnDataPayload(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataEnd() {
+  QUICHE_VLOG(1) << "OnDataEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnDataEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersStart(
+    const Http2FrameHeader& header) {
+  QUICHE_VLOG(1) << "OnHeadersStart: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnHeadersStart(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersPriority(
+    const Http2PriorityFields& priority) {
+  QUICHE_VLOG(1) << "OnHeadersPriority: " << priority;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnHeadersPriority(priority);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHpackFragment(const char* data,
+                                                       size_t len) {
+  QUICHE_VLOG(1) << "OnHpackFragment: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnHpackFragment(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersEnd() {
+  QUICHE_VLOG(1) << "OnHeadersEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnHeadersEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityFrame(
+    const Http2FrameHeader& header, const Http2PriorityFields& priority) {
+  QUICHE_VLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPriorityFrame(header, priority);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnContinuationStart(
+    const Http2FrameHeader& header) {
+  QUICHE_VLOG(1) << "OnContinuationStart: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnContinuationStart(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnContinuationEnd() {
+  QUICHE_VLOG(1) << "OnContinuationEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnContinuationEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPadLength(size_t trailing_length) {
+  QUICHE_VLOG(1) << "OnPadLength: trailing_length=" << trailing_length;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPadLength(trailing_length);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPadding(const char* padding,
+                                                 size_t skipped_length) {
+  QUICHE_VLOG(1) << "OnPadding: skipped_length=" << skipped_length;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPadding(padding, skipped_length);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnRstStream(
+    const Http2FrameHeader& header, Http2ErrorCode error_code) {
+  QUICHE_VLOG(1) << "OnRstStream: " << header << "; code=" << error_code;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnRstStream(header, error_code);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsStart(
+    const Http2FrameHeader& header) {
+  QUICHE_VLOG(1) << "OnSettingsStart: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnSettingsStart(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSetting(
+    const Http2SettingFields& setting_fields) {
+  QUICHE_VLOG(1) << "OnSetting: " << setting_fields;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnSetting(setting_fields);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsEnd() {
+  QUICHE_VLOG(1) << "OnSettingsEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnSettingsEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsAck(
+    const Http2FrameHeader& header) {
+  QUICHE_VLOG(1) << "OnSettingsAck: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnSettingsAck(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPushPromiseStart(
+    const Http2FrameHeader& header, const Http2PushPromiseFields& promise,
+    size_t total_padding_length) {
+  QUICHE_VLOG(1) << "OnPushPromiseStart: " << header << "; promise: " << promise
+                 << "; total_padding_length: " << total_padding_length;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPushPromiseStart(header, promise, total_padding_length);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPushPromiseEnd() {
+  QUICHE_VLOG(1) << "OnPushPromiseEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPushPromiseEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPing(const Http2FrameHeader& header,
+                                              const Http2PingFields& ping) {
+  QUICHE_VLOG(1) << "OnPing: " << header << "; ping: " << ping;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPing(header, ping);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPingAck(const Http2FrameHeader& header,
+                                                 const Http2PingFields& ping) {
+  QUICHE_VLOG(1) << "OnPingAck: " << header << "; ping: " << ping;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPingAck(header, ping);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayStart(
+    const Http2FrameHeader& header, const Http2GoAwayFields& goaway) {
+  QUICHE_VLOG(1) << "OnGoAwayStart: " << header << "; goaway: " << goaway;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnGoAwayStart(header, goaway);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayOpaqueData(const char* data,
+                                                          size_t len) {
+  QUICHE_VLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnGoAwayOpaqueData(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayEnd() {
+  QUICHE_VLOG(1) << "OnGoAwayEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnGoAwayEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnWindowUpdate(
+    const Http2FrameHeader& header, uint32_t increment) {
+  QUICHE_VLOG(1) << "OnWindowUpdate: " << header << "; increment=" << increment;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnWindowUpdate(header, increment);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcStart(
+    const Http2FrameHeader& header, size_t origin_length, size_t value_length) {
+  QUICHE_VLOG(1) << "OnAltSvcStart: " << header
+                 << "; origin_length: " << origin_length
+                 << "; value_length: " << value_length;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnAltSvcStart(header, origin_length, value_length);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcOriginData(const char* data,
+                                                          size_t len) {
+  QUICHE_VLOG(1) << "OnAltSvcOriginData: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnAltSvcOriginData(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcValueData(const char* data,
+                                                         size_t len) {
+  QUICHE_VLOG(1) << "OnAltSvcValueData: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnAltSvcValueData(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcEnd() {
+  QUICHE_VLOG(1) << "OnAltSvcEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnAltSvcEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityUpdateStart(
+    const Http2FrameHeader& header,
+    const Http2PriorityUpdateFields& priority_update) {
+  QUICHE_VLOG(1) << "OnPriorityUpdateStart";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPriorityUpdateStart(header, priority_update);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityUpdatePayload(const char* data,
+                                                               size_t len) {
+  QUICHE_VLOG(1) << "OnPriorityUpdatePayload";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPriorityUpdatePayload(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityUpdateEnd() {
+  QUICHE_VLOG(1) << "OnPriorityUpdateEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPriorityUpdateEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownStart(
+    const Http2FrameHeader& header) {
+  QUICHE_VLOG(1) << "OnUnknownStart: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnUnknownStart(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownPayload(const char* data,
+                                                        size_t len) {
+  QUICHE_VLOG(1) << "OnUnknownPayload: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnUnknownPayload(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownEnd() {
+  QUICHE_VLOG(1) << "OnUnknownEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnUnknownEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPaddingTooLong(
+    const Http2FrameHeader& header, size_t missing_length) {
+  QUICHE_VLOG(1) << "OnPaddingTooLong: " << header
+                 << "; missing_length: " << missing_length;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPaddingTooLong(header, missing_length);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnFrameSizeError(
+    const Http2FrameHeader& header) {
+  QUICHE_VLOG(1) << "OnFrameSizeError: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnFrameSizeError(header);
+  }
+}
+
+}  // namespace http2
diff --git a/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.h b/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.h
new file mode 100644
index 0000000..db341ce
--- /dev/null
+++ b/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.h
@@ -0,0 +1,154 @@
+// Copyright 2016 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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "quiche/http2/decoder/http2_frame_decoder_listener.h"
+#include "quiche/http2/http2_constants.h"
+#include "quiche/http2/http2_structures.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace http2 {
+
+// Fail if any of the methods are called. Allows a test to override only the
+// expected calls.
+class QUICHE_NO_EXPORT FailingHttp2FrameDecoderListener
+    : public Http2FrameDecoderListener {
+ public:
+  FailingHttp2FrameDecoderListener();
+  ~FailingHttp2FrameDecoderListener() override;
+
+  // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+  // SpdyFramer's exact states.
+  bool OnFrameHeader(const Http2FrameHeader& header) override;
+  void OnDataStart(const Http2FrameHeader& header) override;
+  void OnDataPayload(const char* data, size_t len) override;
+  void OnDataEnd() override;
+  void OnHeadersStart(const Http2FrameHeader& header) override;
+  void OnHeadersPriority(const Http2PriorityFields& priority) override;
+  void OnHpackFragment(const char* data, size_t len) override;
+  void OnHeadersEnd() override;
+  void OnPriorityFrame(const Http2FrameHeader& header,
+                       const Http2PriorityFields& priority) override;
+  void OnContinuationStart(const Http2FrameHeader& header) override;
+  void OnContinuationEnd() override;
+  void OnPadLength(size_t trailing_length) override;
+  void OnPadding(const char* padding, size_t skipped_length) override;
+  void OnRstStream(const Http2FrameHeader& header,
+                   Http2ErrorCode error_code) override;
+  void OnSettingsStart(const Http2FrameHeader& header) override;
+  void OnSetting(const Http2SettingFields& setting_fields) override;
+  void OnSettingsEnd() override;
+  void OnSettingsAck(const Http2FrameHeader& header) override;
+  void OnPushPromiseStart(const Http2FrameHeader& header,
+                          const Http2PushPromiseFields& promise,
+                          size_t total_padding_length) override;
+  void OnPushPromiseEnd() override;
+  void OnPing(const Http2FrameHeader& header,
+              const Http2PingFields& ping) override;
+  void OnPingAck(const Http2FrameHeader& header,
+                 const Http2PingFields& ping) override;
+  void OnGoAwayStart(const Http2FrameHeader& header,
+                     const Http2GoAwayFields& goaway) override;
+  void OnGoAwayOpaqueData(const char* data, size_t len) override;
+  void OnGoAwayEnd() override;
+  void OnWindowUpdate(const Http2FrameHeader& header,
+                      uint32_t increment) override;
+  void OnAltSvcStart(const Http2FrameHeader& header, size_t origin_length,
+                     size_t value_length) override;
+  void OnAltSvcOriginData(const char* data, size_t len) override;
+  void OnAltSvcValueData(const char* data, size_t len) override;
+  void OnAltSvcEnd() override;
+  void OnPriorityUpdateStart(
+      const Http2FrameHeader& header,
+      const Http2PriorityUpdateFields& priority_update) override;
+  void OnPriorityUpdatePayload(const char* data, size_t len) override;
+  void OnPriorityUpdateEnd() override;
+  void OnUnknownStart(const Http2FrameHeader& header) override;
+  void OnUnknownPayload(const char* data, size_t len) override;
+  void OnUnknownEnd() override;
+  void OnPaddingTooLong(const Http2FrameHeader& header,
+                        size_t missing_length) override;
+  void OnFrameSizeError(const Http2FrameHeader& header) override;
+
+ private:
+  void EnsureNotAbstract() { FailingHttp2FrameDecoderListener instance; }
+};
+
+// QUICHE_VLOG's all the calls it receives, and forwards those calls to an
+// optional listener.
+class QUICHE_NO_EXPORT LoggingHttp2FrameDecoderListener
+    : public Http2FrameDecoderListener {
+ public:
+  LoggingHttp2FrameDecoderListener();
+  explicit LoggingHttp2FrameDecoderListener(Http2FrameDecoderListener* wrapped);
+  ~LoggingHttp2FrameDecoderListener() override;
+
+  // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+  // SpdyFramer's exact states.
+  bool OnFrameHeader(const Http2FrameHeader& header) override;
+  void OnDataStart(const Http2FrameHeader& header) override;
+  void OnDataPayload(const char* data, size_t len) override;
+  void OnDataEnd() override;
+  void OnHeadersStart(const Http2FrameHeader& header) override;
+  void OnHeadersPriority(const Http2PriorityFields& priority) override;
+  void OnHpackFragment(const char* data, size_t len) override;
+  void OnHeadersEnd() override;
+  void OnPriorityFrame(const Http2FrameHeader& header,
+                       const Http2PriorityFields& priority) override;
+  void OnContinuationStart(const Http2FrameHeader& header) override;
+  void OnContinuationEnd() override;
+  void OnPadLength(size_t trailing_length) override;
+  void OnPadding(const char* padding, size_t skipped_length) override;
+  void OnRstStream(const Http2FrameHeader& header,
+                   Http2ErrorCode error_code) override;
+  void OnSettingsStart(const Http2FrameHeader& header) override;
+  void OnSetting(const Http2SettingFields& setting_fields) override;
+  void OnSettingsEnd() override;
+  void OnSettingsAck(const Http2FrameHeader& header) override;
+  void OnPushPromiseStart(const Http2FrameHeader& header,
+                          const Http2PushPromiseFields& promise,
+                          size_t total_padding_length) override;
+  void OnPushPromiseEnd() override;
+  void OnPing(const Http2FrameHeader& header,
+              const Http2PingFields& ping) override;
+  void OnPingAck(const Http2FrameHeader& header,
+                 const Http2PingFields& ping) override;
+  void OnGoAwayStart(const Http2FrameHeader& header,
+                     const Http2GoAwayFields& goaway) override;
+  void OnGoAwayOpaqueData(const char* data, size_t len) override;
+  void OnGoAwayEnd() override;
+  void OnWindowUpdate(const Http2FrameHeader& header,
+                      uint32_t increment) override;
+  void OnAltSvcStart(const Http2FrameHeader& header, size_t origin_length,
+                     size_t value_length) override;
+  void OnAltSvcOriginData(const char* data, size_t len) override;
+  void OnAltSvcValueData(const char* data, size_t len) override;
+  void OnAltSvcEnd() override;
+  void OnPriorityUpdateStart(
+      const Http2FrameHeader& header,
+      const Http2PriorityUpdateFields& priority_update) override;
+  void OnPriorityUpdatePayload(const char* data, size_t len) override;
+  void OnPriorityUpdateEnd() override;
+  void OnUnknownStart(const Http2FrameHeader& header) override;
+  void OnUnknownPayload(const char* data, size_t len) override;
+  void OnUnknownEnd() override;
+  void OnPaddingTooLong(const Http2FrameHeader& header,
+                        size_t missing_length) override;
+  void OnFrameSizeError(const Http2FrameHeader& header) override;
+
+ private:
+  void EnsureNotAbstract() { LoggingHttp2FrameDecoderListener instance; }
+
+  Http2FrameDecoderListener* wrapped_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
diff --git a/quiche/http2/test_tools/http2_structure_decoder_test_util.cc b/quiche/http2/test_tools/http2_structure_decoder_test_util.cc
new file mode 100644
index 0000000..214fd85
--- /dev/null
+++ b/quiche/http2/test_tools/http2_structure_decoder_test_util.cc
@@ -0,0 +1,22 @@
+// Copyright 2017 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/http2/test_tools/http2_structure_decoder_test_util.h"
+
+#include <cstddef>
+
+namespace http2 {
+namespace test {
+
+// static
+void Http2StructureDecoderPeer::Randomize(Http2StructureDecoder* p,
+                                          Http2Random* rng) {
+  p->offset_ = rng->Rand32();
+  for (size_t i = 0; i < sizeof p->buffer_; ++i) {
+    p->buffer_[i] = rng->Rand8();
+  }
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/http2_structure_decoder_test_util.h b/quiche/http2/test_tools/http2_structure_decoder_test_util.h
new file mode 100644
index 0000000..ad367c3
--- /dev/null
+++ b/quiche/http2/test_tools/http2_structure_decoder_test_util.h
@@ -0,0 +1,24 @@
+// Copyright 2016 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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
+
+#include "quiche/http2/decoder/http2_structure_decoder.h"
+#include "quiche/http2/test_tools/http2_random.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace http2 {
+namespace test {
+
+class QUICHE_NO_EXPORT Http2StructureDecoderPeer {
+ public:
+  // Overwrite the Http2StructureDecoder instance with random values.
+  static void Randomize(Http2StructureDecoder* p, Http2Random* rng);
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
diff --git a/quiche/http2/test_tools/http2_structures_test_util.cc b/quiche/http2/test_tools/http2_structures_test_util.cc
new file mode 100644
index 0000000..09bf1ee
--- /dev/null
+++ b/quiche/http2/test_tools/http2_structures_test_util.cc
@@ -0,0 +1,112 @@
+// Copyright 2016 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/http2/test_tools/http2_structures_test_util.h"
+
+#include <cstdint>
+
+#include "quiche/http2/http2_constants.h"
+#include "quiche/http2/http2_structures.h"
+#include "quiche/http2/test_tools/http2_constants_test_util.h"
+#include "quiche/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+void Randomize(Http2FrameHeader* out, Http2Random* rng) {
+  out->payload_length = rng->Rand32() & 0xffffff;
+  out->type = static_cast<Http2FrameType>(rng->Rand8());
+  out->flags = static_cast<Http2FrameFlag>(rng->Rand8());
+  out->stream_id = rng->Rand32() & StreamIdMask();
+}
+void Randomize(Http2PriorityFields* out, Http2Random* rng) {
+  out->stream_dependency = rng->Rand32() & StreamIdMask();
+  out->weight = rng->Rand8() + 1;
+  out->is_exclusive = rng->OneIn(2);
+}
+void Randomize(Http2RstStreamFields* out, Http2Random* rng) {
+  out->error_code = static_cast<Http2ErrorCode>(rng->Rand32());
+}
+void Randomize(Http2SettingFields* out, Http2Random* rng) {
+  out->parameter = static_cast<Http2SettingsParameter>(rng->Rand16());
+  out->value = rng->Rand32();
+}
+void Randomize(Http2PushPromiseFields* out, Http2Random* rng) {
+  out->promised_stream_id = rng->Rand32() & StreamIdMask();
+}
+void Randomize(Http2PingFields* out, Http2Random* rng) {
+  for (int ndx = 0; ndx < 8; ++ndx) {
+    out->opaque_bytes[ndx] = rng->Rand8();
+  }
+}
+void Randomize(Http2GoAwayFields* out, Http2Random* rng) {
+  out->last_stream_id = rng->Rand32() & StreamIdMask();
+  out->error_code = static_cast<Http2ErrorCode>(rng->Rand32());
+}
+void Randomize(Http2WindowUpdateFields* out, Http2Random* rng) {
+  out->window_size_increment = rng->Rand32() & 0x7fffffff;
+}
+void Randomize(Http2AltSvcFields* out, Http2Random* rng) {
+  out->origin_length = rng->Rand16();
+}
+void Randomize(Http2PriorityUpdateFields* out, Http2Random* rng) {
+  out->prioritized_stream_id = rng->Rand32() & StreamIdMask();
+}
+
+void ScrubFlagsOfHeader(Http2FrameHeader* header) {
+  uint8_t invalid_mask = InvalidFlagMaskForFrameType(header->type);
+  uint8_t keep_mask = ~invalid_mask;
+  header->RetainFlags(keep_mask);
+}
+
+bool FrameIsPadded(const Http2FrameHeader& header) {
+  switch (header.type) {
+    case Http2FrameType::DATA:
+    case Http2FrameType::HEADERS:
+    case Http2FrameType::PUSH_PROMISE:
+      return header.IsPadded();
+    default:
+      return false;
+  }
+}
+
+bool FrameHasPriority(const Http2FrameHeader& header) {
+  switch (header.type) {
+    case Http2FrameType::HEADERS:
+      return header.HasPriority();
+    case Http2FrameType::PRIORITY:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool FrameCanHavePayload(const Http2FrameHeader& header) {
+  switch (header.type) {
+    case Http2FrameType::DATA:
+    case Http2FrameType::HEADERS:
+    case Http2FrameType::PUSH_PROMISE:
+    case Http2FrameType::CONTINUATION:
+    case Http2FrameType::PING:
+    case Http2FrameType::GOAWAY:
+    case Http2FrameType::ALTSVC:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool FrameCanHaveHpackPayload(const Http2FrameHeader& header) {
+  switch (header.type) {
+    case Http2FrameType::HEADERS:
+    case Http2FrameType::PUSH_PROMISE:
+    case Http2FrameType::CONTINUATION:
+      return true;
+    default:
+      return false;
+  }
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/http2_structures_test_util.h b/quiche/http2/test_tools/http2_structures_test_util.h
new file mode 100644
index 0000000..5a5353e
--- /dev/null
+++ b/quiche/http2/test_tools/http2_structures_test_util.h
@@ -0,0 +1,61 @@
+// Copyright 2016 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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HTTP2_STRUCTURES_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_STRUCTURES_TEST_UTIL_H_
+
+#include <string>
+
+#include "quiche/http2/http2_structures.h"
+#include "quiche/http2/test_tools/http2_frame_builder.h"
+#include "quiche/http2/test_tools/http2_random.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+
+template <class S>
+std::string SerializeStructure(const S& s) {
+  Http2FrameBuilder fb;
+  fb.Append(s);
+  EXPECT_EQ(S::EncodedSize(), fb.size());
+  return fb.buffer();
+}
+
+// Randomize the members of out, in a manner that yields encodeable contents
+// (e.g. a "uint24" field has only the low 24 bits set).
+void Randomize(Http2FrameHeader* out, Http2Random* rng);
+void Randomize(Http2PriorityFields* out, Http2Random* rng);
+void Randomize(Http2RstStreamFields* out, Http2Random* rng);
+void Randomize(Http2SettingFields* out, Http2Random* rng);
+void Randomize(Http2PushPromiseFields* out, Http2Random* rng);
+void Randomize(Http2PingFields* out, Http2Random* rng);
+void Randomize(Http2GoAwayFields* out, Http2Random* rng);
+void Randomize(Http2WindowUpdateFields* out, Http2Random* rng);
+void Randomize(Http2AltSvcFields* out, Http2Random* rng);
+void Randomize(Http2PriorityUpdateFields* out, Http2Random* rng);
+
+// Clear bits of header->flags that are known to be invalid for the
+// type. For unknown frame types, no change is made.
+void ScrubFlagsOfHeader(Http2FrameHeader* header);
+
+// Is the frame with this header padded? Only true for known/supported frame
+// types.
+bool FrameIsPadded(const Http2FrameHeader& header);
+
+// Does the frame with this header have Http2PriorityFields?
+bool FrameHasPriority(const Http2FrameHeader& header);
+
+// Does the frame with this header have a variable length payload (including
+// empty) payload (e.g. DATA or HEADERS)? Really a test of the frame type.
+bool FrameCanHavePayload(const Http2FrameHeader& header);
+
+// Does the frame with this header have a variable length HPACK payload
+// (including empty) payload (e.g. HEADERS)? Really a test of the frame type.
+bool FrameCanHaveHpackPayload(const Http2FrameHeader& header);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_HTTP2_STRUCTURES_TEST_UTIL_H_
diff --git a/quiche/http2/test_tools/payload_decoder_base_test_util.cc b/quiche/http2/test_tools/payload_decoder_base_test_util.cc
new file mode 100644
index 0000000..5467658
--- /dev/null
+++ b/quiche/http2/test_tools/payload_decoder_base_test_util.cc
@@ -0,0 +1,97 @@
+// Copyright 2016 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/http2/test_tools/payload_decoder_base_test_util.h"
+
+#include "quiche/http2/test_tools/frame_decoder_state_test_util.h"
+#include "quiche/http2/test_tools/http2_structures_test_util.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+PayloadDecoderBaseTest::PayloadDecoderBaseTest() {
+  // If the test adds more data after the frame payload,
+  // stop as soon as the payload is decoded.
+  stop_decode_on_done_ = true;
+  frame_header_is_set_ = false;
+  Randomize(&frame_header_, RandomPtr());
+}
+
+DecodeStatus PayloadDecoderBaseTest::StartDecoding(DecodeBuffer* db) {
+  QUICHE_DVLOG(2) << "StartDecoding, db->Remaining=" << db->Remaining();
+  // Make sure sub-class has set frame_header_ so that we can inject it
+  // into the payload decoder below.
+  if (!frame_header_is_set_) {
+    ADD_FAILURE() << "frame_header_ is not set";
+    return DecodeStatus::kDecodeError;
+  }
+  // The contract with the payload decoders is that they won't receive a
+  // decode buffer that extends beyond the end of the frame.
+  if (db->Remaining() > frame_header_.payload_length) {
+    ADD_FAILURE() << "DecodeBuffer has too much data: " << db->Remaining()
+                  << " > " << frame_header_.payload_length;
+    return DecodeStatus::kDecodeError;
+  }
+
+  // Prepare the payload decoder.
+  PreparePayloadDecoder();
+
+  // Reconstruct the FrameDecoderState, prepare the listener, and add it to
+  // the FrameDecoderState.
+  frame_decoder_state_ = std::make_unique<FrameDecoderState>();
+  frame_decoder_state_->set_listener(PrepareListener());
+
+  // Make sure that a listener was provided.
+  if (frame_decoder_state_->listener() == nullptr) {
+    ADD_FAILURE() << "PrepareListener must return a listener.";
+    return DecodeStatus::kDecodeError;
+  }
+
+  // Now that nothing in the payload decoder should be valid, inject the
+  // Http2FrameHeader whose payload we're about to decode. That header is the
+  // only state that a payload decoder should expect is valid when its Start
+  // method is called.
+  FrameDecoderStatePeer::set_frame_header(frame_header_,
+                                          frame_decoder_state_.get());
+  DecodeStatus status = StartDecodingPayload(db);
+  if (status != DecodeStatus::kDecodeInProgress) {
+    // Keep track of this so that a concrete test can verify that both fast
+    // and slow decoding paths have been tested.
+    ++fast_decode_count_;
+  }
+  return status;
+}
+
+DecodeStatus PayloadDecoderBaseTest::ResumeDecoding(DecodeBuffer* db) {
+  QUICHE_DVLOG(2) << "ResumeDecoding, db->Remaining=" << db->Remaining();
+  DecodeStatus status = ResumeDecodingPayload(db);
+  if (status != DecodeStatus::kDecodeInProgress) {
+    // Keep track of this so that a concrete test can verify that both fast
+    // and slow decoding paths have been tested.
+    ++slow_decode_count_;
+  }
+  return status;
+}
+
+::testing::AssertionResult
+PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(
+    absl::string_view payload, Validator validator) {
+  VERIFY_TRUE(frame_header_is_set_);
+  // Cap the payload to be decoded at the declared payload length. This is
+  // required by the decoders' preconditions; they are designed on the
+  // assumption that they're never passed more than they're permitted to
+  // consume.
+  // Note that it is OK if the payload is too short; the validator may be
+  // designed to check for that.
+  if (payload.size() > frame_header_.payload_length) {
+    payload = absl::string_view(payload.data(), frame_header_.payload_length);
+  }
+  DecodeBuffer db(payload);
+  ResetDecodeSpeedCounters();
+  const bool kMayReturnZeroOnFirst = false;
+  return DecodeAndValidateSeveralWays(&db, kMayReturnZeroOnFirst, validator);
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/payload_decoder_base_test_util.h b/quiche/http2/test_tools/payload_decoder_base_test_util.h
new file mode 100644
index 0000000..5f65982
--- /dev/null
+++ b/quiche/http2/test_tools/payload_decoder_base_test_util.h
@@ -0,0 +1,441 @@
+// Copyright 2016 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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
+
+// Base class for testing concrete payload decoder classes.
+
+#include <stddef.h>
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/http2/decoder/decode_buffer.h"
+#include "quiche/http2/decoder/decode_status.h"
+#include "quiche/http2/decoder/frame_decoder_state.h"
+#include "quiche/http2/decoder/http2_frame_decoder_listener.h"
+#include "quiche/http2/http2_constants.h"
+#include "quiche/http2/http2_structures.h"
+#include "quiche/http2/test_tools/frame_parts.h"
+#include "quiche/http2/test_tools/http2_constants_test_util.h"
+#include "quiche/http2/test_tools/http2_frame_builder.h"
+#include "quiche/http2/test_tools/random_decoder_test.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_test_helpers.h"
+
+namespace http2 {
+namespace test {
+
+// Base class for tests of payload decoders. Below this there is a templated
+// sub-class that adds a bunch of type specific features.
+class QUICHE_NO_EXPORT PayloadDecoderBaseTest : public RandomDecoderTest {
+ protected:
+  PayloadDecoderBaseTest();
+
+  // Virtual functions to be implemented by the test classes for the individual
+  // payload decoders...
+
+  // Start decoding the payload.
+  virtual DecodeStatus StartDecodingPayload(DecodeBuffer* db) = 0;
+
+  // Resume decoding the payload.
+  virtual DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) = 0;
+
+  // In support of ensuring that we're really accessing and updating the
+  // decoder, prepare the decoder by, for example, overwriting the decoder.
+  virtual void PreparePayloadDecoder() = 0;
+
+  // Get the listener to be inserted into the FrameDecoderState, ready for
+  // listening (e.g. reset if it is a FramePartsCollector).
+  virtual Http2FrameDecoderListener* PrepareListener() = 0;
+
+  // Record a frame header for use on each call to StartDecoding.
+  void set_frame_header(const Http2FrameHeader& header) {
+    EXPECT_EQ(0, InvalidFlagMaskForFrameType(header.type) & header.flags);
+    if (!frame_header_is_set_ || frame_header_ != header) {
+      QUICHE_VLOG(2) << "set_frame_header: " << frame_header_;
+    }
+    frame_header_ = header;
+    frame_header_is_set_ = true;
+  }
+
+  FrameDecoderState* mutable_state() { return frame_decoder_state_.get(); }
+
+  // Randomize the payload decoder, sets the payload decoder's frame_header_,
+  // then start decoding the payload. Called by RandomDecoderTest. This method
+  // is final so that we can always perform certain actions when
+  // RandomDecoderTest starts the decoding of a payload, such as randomizing the
+  // the payload decoder, injecting the frame header and counting fast decoding
+  // cases. Sub-classes must implement StartDecodingPayload to perform their
+  // initial decoding of a frame's payload.
+  DecodeStatus StartDecoding(DecodeBuffer* db) final;
+
+  // Called by RandomDecoderTest. This method is final so that we can always
+  // perform certain actions when RandomDecoderTest calls it, such as counting
+  // slow decode cases. Sub-classes must implement ResumeDecodingPayload to
+  // continue decoding the frame's payload, which must not all be in one buffer.
+  DecodeStatus ResumeDecoding(DecodeBuffer* db) final;
+
+  // Given the specified payload (without the common frame header), decode
+  // it with several partitionings of the payload.
+  ::testing::AssertionResult DecodePayloadAndValidateSeveralWays(
+      absl::string_view payload, Validator validator);
+
+  // TODO(jamessynge): Add helper method for verifying these are both non-zero,
+  // and call the new method from tests that expect successful decoding.
+  void ResetDecodeSpeedCounters() {
+    fast_decode_count_ = 0;
+    slow_decode_count_ = 0;
+  }
+
+  // Count of payloads that are full decoded by StartDecodingPayload, or that
+  // an error was detected by StartDecodingPayload.
+  size_t fast_decode_count_ = 0;
+
+  // Count of payloads that require calling ResumeDecodingPayload in order to
+  // decode them completely (or to detect an error during decoding).
+  size_t slow_decode_count_ = 0;
+
+ private:
+  bool frame_header_is_set_ = false;
+  Http2FrameHeader frame_header_;
+  std::unique_ptr<FrameDecoderState> frame_decoder_state_;
+};
+
+// Base class for payload decoders of type Decoder, with corresponding test
+// peer of type DecoderPeer, and using class Listener as the implementation
+// of Http2FrameDecoderListenerInterface to be used during decoding.
+// Typically Listener is a sub-class of FramePartsCollector.
+// SupportedFrameType is set to false only for UnknownPayloadDecoder.
+template <class Decoder, class DecoderPeer, class Listener,
+          bool SupportedFrameType = true>
+class QUICHE_NO_EXPORT AbstractPayloadDecoderTest
+    : public PayloadDecoderBaseTest {
+ protected:
+  // An ApproveSize function returns true to approve decoding the specified
+  // size of payload, else false to skip that size. Typically used for negative
+  // tests; for example, decoding a SETTINGS frame at all sizes except for
+  // multiples of 6.
+  typedef std::function<bool(size_t size)> ApproveSize;
+
+  AbstractPayloadDecoderTest() {}
+
+  // These tests are in setup rather than the constructor for two reasons:
+  // 1) Constructors are not allowed to fail, so gUnit documents that EXPECT_*
+  //    and ASSERT_* are not allowed in constructors, and should instead be in
+  //    SetUp if they are needed before the body of the test is executed.
+  // 2) To allow the sub-class constructor to make any desired modifications to
+  //    the DecoderPeer before these tests are executed; in particular,
+  //    UnknownPayloadDecoderPeer has not got a fixed frame type, but it is
+  //    instead set during the test's constructor.
+  void SetUp() override {
+    PayloadDecoderBaseTest::SetUp();
+
+    // Confirm that DecoderPeer et al returns sensible values. Using auto as the
+    // variable type so that no (narrowing) conversions take place that hide
+    // problems; i.e. if someone changes KnownFlagsMaskForFrameType so that it
+    // doesn't return a uint8, and has bits above the low-order 8 bits set, this
+    // bit of paranoia should detect the problem before we get too far.
+    auto frame_type = DecoderPeer::FrameType();
+    if (SupportedFrameType) {
+      EXPECT_TRUE(IsSupportedHttp2FrameType(frame_type)) << frame_type;
+    } else {
+      EXPECT_FALSE(IsSupportedHttp2FrameType(frame_type)) << frame_type;
+    }
+
+    auto known_flags = KnownFlagsMaskForFrameType(frame_type);
+    EXPECT_EQ(known_flags, known_flags & 0xff);
+
+    auto flags_to_avoid = DecoderPeer::FlagsAffectingPayloadDecoding();
+    EXPECT_EQ(flags_to_avoid, flags_to_avoid & known_flags);
+  }
+
+  void PreparePayloadDecoder() override {
+    payload_decoder_ = std::make_unique<Decoder>();
+  }
+
+  Http2FrameDecoderListener* PrepareListener() override {
+    listener_.Reset();
+    return &listener_;
+  }
+
+  // Returns random flags, but only those valid for the frame type, yet not
+  // those that the DecoderPeer says will affect the decoding of the payload
+  // (e.g. the PRIORTY flag on a HEADERS frame or PADDED on DATA frames).
+  uint8_t RandFlags() {
+    return Random().Rand8() &
+           KnownFlagsMaskForFrameType(DecoderPeer::FrameType()) &
+           ~DecoderPeer::FlagsAffectingPayloadDecoding();
+  }
+
+  // Start decoding the payload.
+  DecodeStatus StartDecodingPayload(DecodeBuffer* db) override {
+    QUICHE_DVLOG(2) << "StartDecodingPayload, db->Remaining="
+                    << db->Remaining();
+    return payload_decoder_->StartDecodingPayload(mutable_state(), db);
+  }
+
+  // Resume decoding the payload.
+  DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) override {
+    QUICHE_DVLOG(2) << "ResumeDecodingPayload, db->Remaining="
+                    << db->Remaining();
+    return payload_decoder_->ResumeDecodingPayload(mutable_state(), db);
+  }
+
+  // Decode one frame's payload and confirm that the listener recorded the
+  // expected FrameParts instance, and only FrameParts instance. The payload
+  // will be decoded several times with different partitionings of the payload,
+  // and after each the validator will be called.
+  AssertionResult DecodePayloadAndValidateSeveralWays(
+      absl::string_view payload, const FrameParts& expected) {
+    auto validator = [&expected, this]() -> AssertionResult {
+      VERIFY_FALSE(listener_.IsInProgress());
+      VERIFY_EQ(1u, listener_.size());
+      return expected.VerifyEquals(*listener_.frame(0));
+    };
+    return PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(
+        payload, ValidateDoneAndEmpty(validator));
+  }
+
+  // Decode one frame's payload, expecting that the final status will be
+  // kDecodeError, and that OnFrameSizeError will have been called on the
+  // listener. The payload will be decoded several times with different
+  // partitionings of the payload. The type WrappedValidator is either
+  // RandomDecoderTest::Validator, RandomDecoderTest::NoArgValidator or
+  // std::nullptr_t (not extra validation).
+  template <typename WrappedValidator>
+  ::testing::AssertionResult VerifyDetectsFrameSizeError(
+      absl::string_view payload, const Http2FrameHeader& header,
+      WrappedValidator wrapped_validator) {
+    set_frame_header(header);
+    // If wrapped_validator is not a RandomDecoderTest::Validator, make it so.
+    Validator validator = ToValidator(wrapped_validator);
+    // And wrap that validator in another which will check that we've reached
+    // the expected state of kDecodeError with OnFrameSizeError having been
+    // called by the payload decoder.
+    validator = [header, validator, this](
+                    const DecodeBuffer& input,
+                    DecodeStatus status) -> ::testing::AssertionResult {
+      QUICHE_DVLOG(2) << "VerifyDetectsFrameSizeError validator; status="
+                      << status << "; input.Remaining=" << input.Remaining();
+      VERIFY_EQ(DecodeStatus::kDecodeError, status);
+      VERIFY_FALSE(listener_.IsInProgress());
+      VERIFY_EQ(1u, listener_.size());
+      const FrameParts* frame = listener_.frame(0);
+      VERIFY_EQ(header, frame->GetFrameHeader());
+      VERIFY_TRUE(frame->GetHasFrameSizeError());
+      // Verify did not get OnPaddingTooLong, as we should only ever produce
+      // one of these two errors for a single frame.
+      VERIFY_FALSE(frame->GetOptMissingLength());
+      return validator(input, status);
+    };
+    return PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(
+        payload, validator);
+  }
+
+  // Confirm that we get OnFrameSizeError when trying to decode unpadded_payload
+  // at all sizes from zero to unpadded_payload.size(), except those sizes not
+  // approved by approve_size.
+  // If total_pad_length is greater than zero, then that amount of padding
+  // is added to the payload (including the Pad Length field).
+  // The flags will be required_flags, PADDED if total_pad_length > 0, and some
+  // randomly selected flag bits not excluded by FlagsAffectingPayloadDecoding.
+  ::testing::AssertionResult VerifyDetectsMultipleFrameSizeErrors(
+      uint8_t required_flags, absl::string_view unpadded_payload,
+      ApproveSize approve_size, int total_pad_length) {
+    // required_flags should come from those that are defined for the frame
+    // type AND are those that affect the decoding of the payload (otherwise,
+    // the flag shouldn't be required).
+    Http2FrameType frame_type = DecoderPeer::FrameType();
+    VERIFY_EQ(required_flags,
+              required_flags & KnownFlagsMaskForFrameType(frame_type));
+    VERIFY_EQ(required_flags,
+              required_flags & DecoderPeer::FlagsAffectingPayloadDecoding());
+
+    if (0 !=
+        (Http2FrameFlag::PADDED & KnownFlagsMaskForFrameType(frame_type))) {
+      // Frame type supports padding.
+      if (total_pad_length == 0) {
+        required_flags &= ~Http2FrameFlag::PADDED;
+      } else {
+        required_flags |= Http2FrameFlag::PADDED;
+      }
+    } else {
+      VERIFY_EQ(0, total_pad_length);
+    }
+
+    bool validated = false;
+    for (size_t real_payload_size = 0;
+         real_payload_size <= unpadded_payload.size(); ++real_payload_size) {
+      if (approve_size != nullptr && !approve_size(real_payload_size)) {
+        continue;
+      }
+      QUICHE_VLOG(1) << "real_payload_size=" << real_payload_size;
+      uint8_t flags = required_flags | RandFlags();
+      Http2FrameBuilder fb;
+      if (total_pad_length > 0) {
+        // total_pad_length_ includes the size of the Pad Length field, and thus
+        // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
+        fb.AppendUInt8(total_pad_length - 1);
+      }
+      // Append a subset of the unpadded_payload, which the decoder should
+      // determine is not a valid amount.
+      fb.Append(unpadded_payload.substr(0, real_payload_size));
+      if (total_pad_length > 0) {
+        fb.AppendZeroes(total_pad_length - 1);
+      }
+      // We choose a random stream id because the payload decoders aren't
+      // checking stream ids.
+      uint32_t stream_id = RandStreamId();
+      Http2FrameHeader header(fb.size(), frame_type, flags, stream_id);
+      VERIFY_SUCCESS(VerifyDetectsFrameSizeError(fb.buffer(), header, nullptr));
+      validated = true;
+    }
+    VERIFY_TRUE(validated);
+    return ::testing::AssertionSuccess();
+  }
+
+  // As above, but for frames without padding.
+  ::testing::AssertionResult VerifyDetectsFrameSizeError(
+      uint8_t required_flags, absl::string_view unpadded_payload,
+      const ApproveSize& approve_size) {
+    Http2FrameType frame_type = DecoderPeer::FrameType();
+    uint8_t known_flags = KnownFlagsMaskForFrameType(frame_type);
+    VERIFY_EQ(0, known_flags & Http2FrameFlag::PADDED);
+    VERIFY_EQ(0, required_flags & Http2FrameFlag::PADDED);
+    return VerifyDetectsMultipleFrameSizeErrors(
+        required_flags, unpadded_payload, approve_size, 0);
+  }
+
+  Listener listener_;
+  std::unique_ptr<Decoder> payload_decoder_;
+};
+
+// A base class for tests parameterized by the total number of bytes of
+// padding, including the Pad Length field (i.e. a total_pad_length of 0
+// means unpadded as there is then no room for the Pad Length field).
+// The frame type must support padding.
+template <class Decoder, class DecoderPeer, class Listener>
+class QUICHE_NO_EXPORT AbstractPaddablePayloadDecoderTest
+    : public AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener>,
+      public ::testing::WithParamInterface<int> {
+  typedef AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener> Base;
+
+ protected:
+  using Base::listener_;
+  using Base::Random;
+  using Base::RandStreamId;
+  using Base::set_frame_header;
+  typedef typename Base::Validator Validator;
+
+  AbstractPaddablePayloadDecoderTest() : total_pad_length_(GetParam()) {
+    QUICHE_LOG(INFO) << "total_pad_length_ = " << total_pad_length_;
+  }
+
+  // Note that total_pad_length_ includes the size of the Pad Length field,
+  // and thus ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
+  bool IsPadded() const { return total_pad_length_ > 0; }
+
+  // Value of the Pad Length field. Only call if IsPadded.
+  size_t pad_length() const {
+    EXPECT_TRUE(IsPadded());
+    return total_pad_length_ - 1;
+  }
+
+  // Clear the frame builder and add the Pad Length field if appropriate.
+  void Reset() {
+    frame_builder_ = Http2FrameBuilder();
+    if (IsPadded()) {
+      frame_builder_.AppendUInt8(pad_length());
+    }
+  }
+
+  void MaybeAppendTrailingPadding() {
+    if (IsPadded()) {
+      frame_builder_.AppendZeroes(pad_length());
+    }
+  }
+
+  uint8_t RandFlags() {
+    uint8_t flags = Base::RandFlags();
+    if (IsPadded()) {
+      flags |= Http2FrameFlag::PADDED;
+    } else {
+      flags &= ~Http2FrameFlag::PADDED;
+    }
+    return flags;
+  }
+
+  // Verify that we get OnPaddingTooLong when decoding payload, and that the
+  // amount of missing padding is as specified. header.IsPadded must be true,
+  // and the payload must be empty or the PadLength field must be too large.
+  ::testing::AssertionResult VerifyDetectsPaddingTooLong(
+      absl::string_view payload, const Http2FrameHeader& header,
+      size_t expected_missing_length) {
+    set_frame_header(header);
+    auto& listener = listener_;
+    Validator validator =
+        [header, expected_missing_length, &listener](
+            const DecodeBuffer&,
+            DecodeStatus status) -> ::testing::AssertionResult {
+      VERIFY_EQ(DecodeStatus::kDecodeError, status);
+      VERIFY_FALSE(listener.IsInProgress());
+      VERIFY_EQ(1u, listener.size());
+      const FrameParts* frame = listener.frame(0);
+      VERIFY_EQ(header, frame->GetFrameHeader());
+      VERIFY_TRUE(frame->GetOptMissingLength());
+      VERIFY_EQ(expected_missing_length, frame->GetOptMissingLength().value());
+      // Verify did not get OnFrameSizeError.
+      VERIFY_FALSE(frame->GetHasFrameSizeError());
+      return ::testing::AssertionSuccess();
+    };
+    return PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(
+        payload, validator);
+  }
+
+  // Verifies that we get OnPaddingTooLong for a padded frame payload whose
+  // (randomly selected) payload length is less than total_pad_length_.
+  // Flags will be selected at random, except PADDED will be set and
+  // flags_to_avoid will not be set. The stream id is selected at random.
+  ::testing::AssertionResult VerifyDetectsPaddingTooLong() {
+    uint8_t flags = RandFlags() | Http2FrameFlag::PADDED;
+
+    // Create an all padding payload for total_pad_length_.
+    int payload_length = 0;
+    Http2FrameBuilder fb;
+    if (IsPadded()) {
+      fb.AppendUInt8(pad_length());
+      fb.AppendZeroes(pad_length());
+      QUICHE_VLOG(1) << "fb.size=" << fb.size();
+      // Pick a random length for the payload that is shorter than neccesary.
+      payload_length = Random().Uniform(fb.size());
+    }
+
+    QUICHE_VLOG(1) << "payload_length=" << payload_length;
+    std::string payload = fb.buffer().substr(0, payload_length);
+
+    // The missing length is the amount we cut off the end, unless
+    // payload_length is zero, in which case the decoder knows only that 1
+    // byte, the Pad Length field, is missing.
+    size_t missing_length =
+        payload_length == 0 ? 1 : fb.size() - payload_length;
+    QUICHE_VLOG(1) << "missing_length=" << missing_length;
+
+    const Http2FrameHeader header(payload_length, DecoderPeer::FrameType(),
+                                  flags, RandStreamId());
+    return VerifyDetectsPaddingTooLong(payload, header, missing_length);
+  }
+
+  // total_pad_length_ includes the size of the Pad Length field, and thus
+  // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
+  const size_t total_pad_length_;
+  Http2FrameBuilder frame_builder_;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_