Project import generated by Copybara.

PiperOrigin-RevId: 224614037
Change-Id: I14e53449d4aeccb328f86828c76b5f09dea0d4b8
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5d4e1b7..3a93363 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -20,7 +20,7 @@
 All submissions, including submissions by project members, require review. We
 use Gerrit pull requests for this purpose.
 
-TODO: write up the Gerrit set up instructions.
+TODO: write up the contributing guidelines.
 
 ## Community Guidelines
 
diff --git a/http2/decoder/decode_buffer.cc b/http2/decoder/decode_buffer.cc
new file mode 100644
index 0000000..b24420a
--- /dev/null
+++ b/http2/decoder/decode_buffer.cc
@@ -0,0 +1,93 @@
+// 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 "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+
+namespace http2 {
+
+uint8_t DecodeBuffer::DecodeUInt8() {
+  return static_cast<uint8_t>(DecodeChar());
+}
+
+uint16_t DecodeBuffer::DecodeUInt16() {
+  DCHECK_LE(2u, Remaining());
+  const uint8_t b1 = DecodeUInt8();
+  const uint8_t b2 = DecodeUInt8();
+  // Note that chars are automatically promoted to ints during arithmetic,
+  // so the b1 << 8 doesn't end up as zero before being or-ed with b2.
+  // And the left-shift operator has higher precedence than the or operator.
+  return b1 << 8 | b2;
+}
+
+uint32_t DecodeBuffer::DecodeUInt24() {
+  DCHECK_LE(3u, Remaining());
+  const uint8_t b1 = DecodeUInt8();
+  const uint8_t b2 = DecodeUInt8();
+  const uint8_t b3 = DecodeUInt8();
+  return b1 << 16 | b2 << 8 | b3;
+}
+
+uint32_t DecodeBuffer::DecodeUInt31() {
+  DCHECK_LE(4u, Remaining());
+  const uint8_t b1 = DecodeUInt8() & 0x7f;  // Mask out the high order bit.
+  const uint8_t b2 = DecodeUInt8();
+  const uint8_t b3 = DecodeUInt8();
+  const uint8_t b4 = DecodeUInt8();
+  return b1 << 24 | b2 << 16 | b3 << 8 | b4;
+}
+
+uint32_t DecodeBuffer::DecodeUInt32() {
+  DCHECK_LE(4u, Remaining());
+  const uint8_t b1 = DecodeUInt8();
+  const uint8_t b2 = DecodeUInt8();
+  const uint8_t b3 = DecodeUInt8();
+  const uint8_t b4 = DecodeUInt8();
+  return b1 << 24 | b2 << 16 | b3 << 8 | b4;
+}
+
+#ifndef NDEBUG
+void DecodeBuffer::set_subset_of_base(DecodeBuffer* base,
+                                      const DecodeBufferSubset* subset) {
+  DCHECK_EQ(this, subset);
+  base->set_subset(subset);
+}
+void DecodeBuffer::clear_subset_of_base(DecodeBuffer* base,
+                                        const DecodeBufferSubset* subset) {
+  DCHECK_EQ(this, subset);
+  base->clear_subset(subset);
+}
+void DecodeBuffer::set_subset(const DecodeBufferSubset* subset) {
+  DCHECK(subset != nullptr);
+  DCHECK_EQ(subset_, nullptr) << "There is already a subset";
+  subset_ = subset;
+}
+void DecodeBuffer::clear_subset(const DecodeBufferSubset* subset) {
+  DCHECK(subset != nullptr);
+  DCHECK_EQ(subset_, subset);
+  subset_ = nullptr;
+}
+void DecodeBufferSubset::DebugSetup() {
+  start_base_offset_ = base_buffer_->Offset();
+  max_base_offset_ = start_base_offset_ + FullSize();
+  DCHECK_LE(max_base_offset_, base_buffer_->FullSize());
+
+  // Ensure that there is only one DecodeBufferSubset at a time for a base.
+  set_subset_of_base(base_buffer_, this);
+}
+void DecodeBufferSubset::DebugTearDown() {
+  // Ensure that the base hasn't been modified.
+  DCHECK_EQ(start_base_offset_, base_buffer_->Offset())
+      << "The base buffer was modified";
+
+  // Ensure that we haven't gone beyond the maximum allowed offset.
+  size_t offset = Offset();
+  DCHECK_LE(offset, FullSize());
+  DCHECK_LE(start_base_offset_ + offset, max_base_offset_);
+  DCHECK_LE(max_base_offset_, base_buffer_->FullSize());
+
+  clear_subset_of_base(base_buffer_, this);
+}
+#endif
+
+}  // namespace http2
diff --git a/http2/decoder/decode_buffer.h b/http2/decoder/decode_buffer.h
new file mode 100644
index 0000000..3b213c2
--- /dev/null
+++ b/http2/decoder/decode_buffer.h
@@ -0,0 +1,166 @@
+// 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_DECODER_DECODE_BUFFER_H_
+#define QUICHE_HTTP2_DECODER_DECODE_BUFFER_H_
+
+// DecodeBuffer provides primitives for decoding various integer types found in
+// HTTP/2 frames. It wraps a byte array from which we can read and decode
+// serialized HTTP/2 frames, or parts thereof. DecodeBuffer is intended only for
+// stack allocation, where the caller is typically going to use the DecodeBuffer
+// instance as part of decoding the entire buffer before returning to its own
+// caller.
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+class DecodeBufferSubset;
+
+class HTTP2_EXPORT_PRIVATE DecodeBuffer {
+ public:
+  DecodeBuffer(const char* buffer, size_t len)
+      : buffer_(buffer), cursor_(buffer), beyond_(buffer + len) {
+    DCHECK(buffer != nullptr);
+    // We assume the decode buffers will typically be modest in size (i.e. often
+    // a few KB, perhaps as high as 100KB). Let's make sure during testing that
+    // we don't go very high, with 32MB selected rather arbitrarily.
+    const size_t kMaxDecodeBufferLength = 1 << 25;
+    DCHECK_LE(len, kMaxDecodeBufferLength);
+  }
+  explicit DecodeBuffer(Http2StringPiece s)
+      : DecodeBuffer(s.data(), s.size()) {}
+  // Constructor for character arrays, typically in tests. For example:
+  //    const char input[] = { 0x11 };
+  //    DecodeBuffer b(input);
+  template <size_t N>
+  explicit DecodeBuffer(const char (&buf)[N]) : DecodeBuffer(buf, N) {}
+
+  DecodeBuffer(const DecodeBuffer&) = delete;
+  DecodeBuffer operator=(const DecodeBuffer&) = delete;
+
+  bool Empty() const { return cursor_ >= beyond_; }
+  bool HasData() const { return cursor_ < beyond_; }
+  size_t Remaining() const {
+    DCHECK_LE(cursor_, beyond_);
+    return beyond_ - cursor_;
+  }
+  size_t Offset() const { return cursor_ - buffer_; }
+  size_t FullSize() const { return beyond_ - buffer_; }
+
+  // Returns the minimum of the number of bytes remaining in this DecodeBuffer
+  // and |length|, in support of determining how much of some structure/payload
+  // is in this DecodeBuffer.
+  size_t MinLengthRemaining(size_t length) const {
+    return std::min(length, Remaining());
+  }
+
+  // For string decoding, returns a pointer to the next byte/char to be decoded.
+  const char* cursor() const { return cursor_; }
+  // Advances the cursor (pointer to the next byte/char to be decoded).
+  void AdvanceCursor(size_t amount) {
+    DCHECK_LE(amount, Remaining());  // Need at least that much remaining.
+    DCHECK_EQ(subset_, nullptr) << "Access via subset only when present.";
+    cursor_ += amount;
+  }
+
+  // Only call methods starting "Decode" when there is enough input remaining.
+  char DecodeChar() {
+    DCHECK_LE(1u, Remaining());  // Need at least one byte remaining.
+    DCHECK_EQ(subset_, nullptr) << "Access via subset only when present.";
+    return *cursor_++;
+  }
+
+  uint8_t DecodeUInt8();
+  uint16_t DecodeUInt16();
+  uint32_t DecodeUInt24();
+
+  // For 31-bit unsigned integers, where the 32nd bit is reserved for future
+  // use (i.e. the high-bit of the first byte of the encoding); examples:
+  // the Stream Id in a frame header or the Window Size Increment in a
+  // WINDOW_UPDATE frame.
+  uint32_t DecodeUInt31();
+
+  uint32_t DecodeUInt32();
+
+ protected:
+#ifndef NDEBUG
+  // These are part of validating during tests that there is at most one
+  // DecodeBufferSubset instance at a time for any DecodeBuffer instance.
+  void set_subset_of_base(DecodeBuffer* base, const DecodeBufferSubset* subset);
+  void clear_subset_of_base(DecodeBuffer* base,
+                            const DecodeBufferSubset* subset);
+#endif
+
+ private:
+#ifndef NDEBUG
+  void set_subset(const DecodeBufferSubset* subset);
+  void clear_subset(const DecodeBufferSubset* subset);
+#endif
+
+  // Prevent heap allocation of DecodeBuffer.
+  static void* operator new(size_t s);
+  static void* operator new[](size_t s);
+  static void operator delete(void* p);
+  static void operator delete[](void* p);
+
+  const char* const buffer_;
+  const char* cursor_;
+  const char* const beyond_;
+  const DecodeBufferSubset* subset_ = nullptr;  // Used for DCHECKs.
+};
+
+// DecodeBufferSubset is used when decoding a known sized chunk of data, which
+// starts at base->cursor(), and continues for subset_len, which may be
+// entirely in |base|, or may extend beyond it (hence the MinLengthRemaining
+// in the constructor).
+// There are two benefits to using DecodeBufferSubset: it ensures that the
+// cursor of |base| is advanced when the subset's destructor runs, and it
+// ensures that the consumer of the subset can't go beyond the subset which
+// it is intended to decode.
+// There must be only a single DecodeBufferSubset at a time for a base
+// DecodeBuffer, though they can be nested (i.e. a DecodeBufferSubset's
+// base may itself be a DecodeBufferSubset). This avoids the AdvanceCursor
+// being called erroneously.
+class HTTP2_EXPORT_PRIVATE DecodeBufferSubset : public DecodeBuffer {
+ public:
+  DecodeBufferSubset(DecodeBuffer* base, size_t subset_len)
+      : DecodeBuffer(base->cursor(), base->MinLengthRemaining(subset_len)),
+        base_buffer_(base) {
+#ifndef NDEBUG
+    DebugSetup();
+#endif
+  }
+
+  DecodeBufferSubset(const DecodeBufferSubset&) = delete;
+  DecodeBufferSubset operator=(const DecodeBufferSubset&) = delete;
+
+  ~DecodeBufferSubset() {
+    size_t offset = Offset();
+#ifndef NDEBUG
+    DebugTearDown();
+#endif
+    base_buffer_->AdvanceCursor(offset);
+  }
+
+ private:
+  DecodeBuffer* const base_buffer_;
+#ifndef NDEBUG
+  size_t start_base_offset_;  // Used for DCHECKs.
+  size_t max_base_offset_;    // Used for DCHECKs.
+
+  void DebugSetup();
+  void DebugTearDown();
+#endif
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_DECODE_BUFFER_H_
diff --git a/http2/decoder/decode_buffer_test.cc b/http2/decoder/decode_buffer_test.cc
new file mode 100644
index 0000000..e8e3d3b
--- /dev/null
+++ b/http2/decoder/decode_buffer_test.cc
@@ -0,0 +1,203 @@
+// 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 "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+
+#include <functional>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+enum class TestEnumClass32 {
+  kValue1 = 1,
+  kValue99 = 99,
+  kValue1M = 1000000,
+};
+
+enum class TestEnumClass8 {
+  kValue1 = 1,
+  kValue2 = 1,
+  kValue99 = 99,
+  kValue255 = 255,
+};
+
+enum TestEnum8 {
+  kMaskLo = 0x01,
+  kMaskHi = 0x80,
+};
+
+struct TestStruct {
+  uint8_t f1;
+  uint16_t f2;
+  uint32_t f3;  // Decoded as a uint24
+  uint32_t f4;
+  uint32_t f5;  // Decoded as if uint31
+  TestEnumClass32 f6;
+  TestEnumClass8 f7;
+  TestEnum8 f8;
+};
+
+class DecodeBufferTest : public ::testing::Test {
+ protected:
+  Http2Random random_;
+  uint32_t decode_offset_;
+};
+
+TEST_F(DecodeBufferTest, DecodesFixedInts) {
+  const char data[] = "\x01\x12\x23\x34\x45\x56\x67\x78\x89\x9a";
+  DecodeBuffer b1(data, strlen(data));
+  EXPECT_EQ(1, b1.DecodeUInt8());
+  EXPECT_EQ(0x1223u, b1.DecodeUInt16());
+  EXPECT_EQ(0x344556u, b1.DecodeUInt24());
+  EXPECT_EQ(0x6778899Au, b1.DecodeUInt32());
+}
+
+// Make sure that DecodeBuffer is not copying input, just pointing into
+// provided input buffer.
+TEST_F(DecodeBufferTest, HasNotCopiedInput) {
+  const char data[] = "ab";
+  DecodeBuffer b1(data, 2);
+
+  EXPECT_EQ(2u, b1.Remaining());
+  EXPECT_EQ(0u, b1.Offset());
+  EXPECT_FALSE(b1.Empty());
+  EXPECT_EQ(data, b1.cursor());  // cursor points to input buffer
+  EXPECT_TRUE(b1.HasData());
+
+  b1.AdvanceCursor(1);
+
+  EXPECT_EQ(1u, b1.Remaining());
+  EXPECT_EQ(1u, b1.Offset());
+  EXPECT_FALSE(b1.Empty());
+  EXPECT_EQ(&data[1], b1.cursor());
+  EXPECT_TRUE(b1.HasData());
+
+  b1.AdvanceCursor(1);
+
+  EXPECT_EQ(0u, b1.Remaining());
+  EXPECT_EQ(2u, b1.Offset());
+  EXPECT_TRUE(b1.Empty());
+  EXPECT_EQ(&data[2], b1.cursor());
+  EXPECT_FALSE(b1.HasData());
+
+  DecodeBuffer b2(data, 0);
+
+  EXPECT_EQ(0u, b2.Remaining());
+  EXPECT_EQ(0u, b2.Offset());
+  EXPECT_TRUE(b2.Empty());
+  EXPECT_EQ(data, b2.cursor());
+  EXPECT_FALSE(b2.HasData());
+}
+
+// DecodeBufferSubset can't go beyond the end of the base buffer.
+TEST_F(DecodeBufferTest, DecodeBufferSubsetLimited) {
+  const char data[] = "abc";
+  DecodeBuffer base(data, 3);
+  base.AdvanceCursor(1);
+  DecodeBufferSubset subset(&base, 100);
+  EXPECT_EQ(2u, subset.FullSize());
+}
+
+// DecodeBufferSubset advances the cursor of its base upon destruction.
+TEST_F(DecodeBufferTest, DecodeBufferSubsetAdvancesCursor) {
+  const char data[] = "abc";
+  const size_t size = sizeof(data) - 1;
+  EXPECT_EQ(3u, size);
+  DecodeBuffer base(data, size);
+  {
+    // First no change to the cursor.
+    DecodeBufferSubset subset(&base, size + 100);
+    EXPECT_EQ(size, subset.FullSize());
+    EXPECT_EQ(base.FullSize(), subset.FullSize());
+    EXPECT_EQ(0u, subset.Offset());
+  }
+  EXPECT_EQ(0u, base.Offset());
+  EXPECT_EQ(size, base.Remaining());
+}
+
+// Make sure that DecodeBuffer ctor complains about bad args.
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+TEST(DecodeBufferDeathTest, NonNullBufferRequired) {
+  EXPECT_DEBUG_DEATH({ DecodeBuffer b(nullptr, 3); }, "nullptr");
+}
+
+// Make sure that DecodeBuffer ctor complains about bad args.
+TEST(DecodeBufferDeathTest, ModestBufferSizeRequired) {
+  EXPECT_DEBUG_DEATH(
+      {
+        const char data[] = "abc";
+        size_t len = 0;
+        DecodeBuffer b(data, ~len);
+      },
+      "Max.*Length");
+}
+
+// Make sure that DecodeBuffer detects advance beyond end, in debug mode.
+TEST(DecodeBufferDeathTest, LimitedAdvance) {
+  {
+    // Advance right up to end is OK.
+    const char data[] = "abc";
+    DecodeBuffer b(data, 3);
+    b.AdvanceCursor(3);  // OK
+    EXPECT_TRUE(b.Empty());
+  }
+  EXPECT_DEBUG_DEATH(
+      {
+        // Going beyond is not OK.
+        const char data[] = "abc";
+        DecodeBuffer b(data, 3);
+        b.AdvanceCursor(4);
+      },
+      "4 vs. 3");
+}
+
+// Make sure that DecodeBuffer detects decode beyond end, in debug mode.
+TEST(DecodeBufferDeathTest, DecodeUInt8PastEnd) {
+  const char data[] = {0x12, 0x23};
+  DecodeBuffer b(data, sizeof data);
+  EXPECT_EQ(2u, b.FullSize());
+  EXPECT_EQ(0x1223, b.DecodeUInt16());
+  EXPECT_DEBUG_DEATH({ b.DecodeUInt8(); }, "1 vs. 0");
+}
+
+// Make sure that DecodeBuffer detects decode beyond end, in debug mode.
+TEST(DecodeBufferDeathTest, DecodeUInt16OverEnd) {
+  const char data[] = {0x12, 0x23, 0x34};
+  DecodeBuffer b(data, sizeof data);
+  EXPECT_EQ(3u, b.FullSize());
+  EXPECT_EQ(0x1223, b.DecodeUInt16());
+  EXPECT_DEBUG_DEATH({ b.DecodeUInt16(); }, "2 vs. 1");
+}
+
+// Make sure that DecodeBuffer doesn't agree with having two subsets.
+TEST(DecodeBufferSubsetDeathTest, TwoSubsets) {
+  const char data[] = "abc";
+  DecodeBuffer base(data, 3);
+  DecodeBufferSubset subset1(&base, 1);
+  EXPECT_DEBUG_DEATH({ DecodeBufferSubset subset2(&base, 1); },
+                     "There is already a subset");
+}
+
+// Make sure that DecodeBufferSubset notices when the base's cursor has moved.
+TEST(DecodeBufferSubsetDeathTest, BaseCursorAdvanced) {
+  const char data[] = "abc";
+  DecodeBuffer base(data, 3);
+  base.AdvanceCursor(1);
+  EXPECT_DEBUG_DEATH(
+      {
+        DecodeBufferSubset subset1(&base, 2);
+        base.AdvanceCursor(1);
+      },
+      "Access via subset only when present");
+}
+#endif  // GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/decode_http2_structures.cc b/http2/decoder/decode_http2_structures.cc
new file mode 100644
index 0000000..71f6cce
--- /dev/null
+++ b/http2/decoder/decode_http2_structures.cc
@@ -0,0 +1,111 @@
+// 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 "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h"
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+
+namespace http2 {
+
+// Http2FrameHeader decoding:
+
+void DoDecode(Http2FrameHeader* out, DecodeBuffer* b) {
+  DCHECK_NE(nullptr, out);
+  DCHECK_NE(nullptr, b);
+  DCHECK_LE(Http2FrameHeader::EncodedSize(), b->Remaining());
+  out->payload_length = b->DecodeUInt24();
+  out->type = static_cast<Http2FrameType>(b->DecodeUInt8());
+  out->flags = static_cast<Http2FrameFlag>(b->DecodeUInt8());
+  out->stream_id = b->DecodeUInt31();
+}
+
+// Http2PriorityFields decoding:
+
+void DoDecode(Http2PriorityFields* out, DecodeBuffer* b) {
+  DCHECK_NE(nullptr, out);
+  DCHECK_NE(nullptr, b);
+  DCHECK_LE(Http2PriorityFields::EncodedSize(), b->Remaining());
+  uint32_t stream_id_and_flag = b->DecodeUInt32();
+  out->stream_dependency = stream_id_and_flag & StreamIdMask();
+  if (out->stream_dependency == stream_id_and_flag) {
+    out->is_exclusive = false;
+  } else {
+    out->is_exclusive = true;
+  }
+  // Note that chars are automatically promoted to ints during arithmetic,
+  // so 255 + 1 doesn't end up as zero.
+  out->weight = b->DecodeUInt8() + 1;
+}
+
+// Http2RstStreamFields decoding:
+
+void DoDecode(Http2RstStreamFields* out, DecodeBuffer* b) {
+  DCHECK_NE(nullptr, out);
+  DCHECK_NE(nullptr, b);
+  DCHECK_LE(Http2RstStreamFields::EncodedSize(), b->Remaining());
+  out->error_code = static_cast<Http2ErrorCode>(b->DecodeUInt32());
+}
+
+// Http2SettingFields decoding:
+
+void DoDecode(Http2SettingFields* out, DecodeBuffer* b) {
+  DCHECK_NE(nullptr, out);
+  DCHECK_NE(nullptr, b);
+  DCHECK_LE(Http2SettingFields::EncodedSize(), b->Remaining());
+  out->parameter = static_cast<Http2SettingsParameter>(b->DecodeUInt16());
+  out->value = b->DecodeUInt32();
+}
+
+// Http2PushPromiseFields decoding:
+
+void DoDecode(Http2PushPromiseFields* out, DecodeBuffer* b) {
+  DCHECK_NE(nullptr, out);
+  DCHECK_NE(nullptr, b);
+  DCHECK_LE(Http2PushPromiseFields::EncodedSize(), b->Remaining());
+  out->promised_stream_id = b->DecodeUInt31();
+}
+
+// Http2PingFields decoding:
+
+void DoDecode(Http2PingFields* out, DecodeBuffer* b) {
+  DCHECK_NE(nullptr, out);
+  DCHECK_NE(nullptr, b);
+  DCHECK_LE(Http2PingFields::EncodedSize(), b->Remaining());
+  memcpy(out->opaque_bytes, b->cursor(), Http2PingFields::EncodedSize());
+  b->AdvanceCursor(Http2PingFields::EncodedSize());
+}
+
+// Http2GoAwayFields decoding:
+
+void DoDecode(Http2GoAwayFields* out, DecodeBuffer* b) {
+  DCHECK_NE(nullptr, out);
+  DCHECK_NE(nullptr, b);
+  DCHECK_LE(Http2GoAwayFields::EncodedSize(), b->Remaining());
+  out->last_stream_id = b->DecodeUInt31();
+  out->error_code = static_cast<Http2ErrorCode>(b->DecodeUInt32());
+}
+
+// Http2WindowUpdateFields decoding:
+
+void DoDecode(Http2WindowUpdateFields* out, DecodeBuffer* b) {
+  DCHECK_NE(nullptr, out);
+  DCHECK_NE(nullptr, b);
+  DCHECK_LE(Http2WindowUpdateFields::EncodedSize(), b->Remaining());
+  out->window_size_increment = b->DecodeUInt31();
+}
+
+// Http2AltSvcFields decoding:
+
+void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b) {
+  DCHECK_NE(nullptr, out);
+  DCHECK_NE(nullptr, b);
+  DCHECK_LE(Http2AltSvcFields::EncodedSize(), b->Remaining());
+  out->origin_length = b->DecodeUInt16();
+}
+
+}  // namespace http2
diff --git a/http2/decoder/decode_http2_structures.h b/http2/decoder/decode_http2_structures.h
new file mode 100644
index 0000000..3f6a317
--- /dev/null
+++ b/http2/decoder/decode_http2_structures.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_DECODER_DECODE_HTTP2_STRUCTURES_H_
+#define QUICHE_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_
+
+// Provides functions for decoding the fixed size structures in the HTTP/2 spec.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+// DoDecode(STRUCTURE* out, DecodeBuffer* b) decodes the structure from start
+// to end, advancing the cursor by STRUCTURE::EncodedSize(). The decode buffer
+// must be large enough (i.e. b->Remaining() >= STRUCTURE::EncodedSize()).
+
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2FrameHeader* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2PriorityFields* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2RstStreamFields* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2SettingFields* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2PushPromiseFields* out,
+                                   DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2PingFields* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2GoAwayFields* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2WindowUpdateFields* out,
+                                   DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_
diff --git a/http2/decoder/decode_http2_structures_test.cc b/http2/decoder/decode_http2_structures_test.cc
new file mode 100644
index 0000000..43bd7e8
--- /dev/null
+++ b/http2/decoder/decode_http2_structures_test.cc
@@ -0,0 +1,461 @@
+// 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 "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h"
+
+// Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined
+// in net/third_party/quiche/src/http2/http2_structures.h).
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+template <typename T, size_t N>
+Http2StringPiece ToStringPiece(T (&data)[N]) {
+  return Http2StringPiece(reinterpret_cast<const char*>(data), N * sizeof(T));
+}
+
+template <class S>
+Http2String SerializeStructure(const S& s) {
+  Http2FrameBuilder fb;
+  fb.Append(s);
+  EXPECT_EQ(S::EncodedSize(), fb.size());
+  return fb.buffer();
+}
+
+template <class S>
+class StructureDecoderTest : public ::testing::Test {
+ protected:
+  typedef S Structure;
+
+  StructureDecoderTest() : random_(), random_decode_count_(100) {}
+
+  // Set the fields of |*p| to random values.
+  void Randomize(S* p) { ::http2::test::Randomize(p, &random_); }
+
+  // Fully decodes the Structure at the start of data, and confirms it matches
+  // *expected (if provided).
+  void DecodeLeadingStructure(const S* expected, Http2StringPiece data) {
+    ASSERT_LE(S::EncodedSize(), data.size());
+    DecodeBuffer db(data);
+    Randomize(&structure_);
+    DoDecode(&structure_, &db);
+    EXPECT_EQ(db.Offset(), S::EncodedSize());
+    if (expected != nullptr) {
+      EXPECT_EQ(structure_, *expected);
+    }
+  }
+
+  template <size_t N>
+  void DecodeLeadingStructure(const char (&data)[N]) {
+    DecodeLeadingStructure(nullptr, Http2StringPiece(data, N));
+  }
+
+  // Encode the structure |in_s| into bytes, then decode the bytes
+  // and validate that the decoder produced the same field values.
+  void EncodeThenDecode(const S& in_s) {
+    Http2String bytes = SerializeStructure(in_s);
+    EXPECT_EQ(S::EncodedSize(), bytes.size());
+    DecodeLeadingStructure(&in_s, bytes);
+  }
+
+  // Generate
+  void TestDecodingRandomizedStructures(size_t count) {
+    for (size_t i = 0; i < count && !HasFailure(); ++i) {
+      Structure input;
+      Randomize(&input);
+      EncodeThenDecode(input);
+    }
+  }
+
+  void TestDecodingRandomizedStructures() {
+    TestDecodingRandomizedStructures(random_decode_count_);
+  }
+
+  Http2Random random_;
+  const size_t random_decode_count_;
+  uint32_t decode_offset_ = 0;
+  S structure_;
+  size_t fast_decode_count_ = 0;
+  size_t slow_decode_count_ = 0;
+};
+
+class FrameHeaderDecoderTest : public StructureDecoderTest<Http2FrameHeader> {};
+
+TEST_F(FrameHeaderDecoderTest, DecodesLiteral) {
+  {
+    // Realistic input.
+    const char kData[] = {
+        '\x00', '\x00', '\x05',          // Payload length: 5
+        '\x01',                          // Frame type: HEADERS
+        '\x08',                          // Flags: PADDED
+        '\x00', '\x00', '\x00', '\x01',  // Stream ID: 1
+        '\x04',                          // Padding length: 4
+        '\x00', '\x00', '\x00', '\x00',  // Padding bytes
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(5u, structure_.payload_length);
+      EXPECT_EQ(Http2FrameType::HEADERS, structure_.type);
+      EXPECT_EQ(Http2FrameFlag::PADDED, structure_.flags);
+      EXPECT_EQ(1u, structure_.stream_id);
+    }
+  }
+  {
+    // Unlikely input.
+    const char kData[] = {
+        '\xff', '\xff', '\xff',          // Payload length: uint24 max
+        '\xff',                          // Frame type: Unknown
+        '\xff',                          // Flags: Unknown/All
+        '\xff', '\xff', '\xff', '\xff',  // Stream ID: uint31 max, plus R-bit
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ((1u << 24) - 1, structure_.payload_length);
+      EXPECT_EQ(static_cast<Http2FrameType>(255), structure_.type);
+      EXPECT_EQ(255, structure_.flags);
+      EXPECT_EQ(0x7FFFFFFFu, structure_.stream_id);
+    }
+  }
+}
+
+TEST_F(FrameHeaderDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class PriorityFieldsDecoderTest
+    : public StructureDecoderTest<Http2PriorityFields> {};
+
+TEST_F(PriorityFieldsDecoderTest, DecodesLiteral) {
+  {
+    const char kData[] = {
+        '\x80', '\x00', '\x00', '\x05',  // Exclusive (yes) and Dependency (5)
+        '\xff',                          // Weight: 256 (after adding 1)
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(5u, structure_.stream_dependency);
+      EXPECT_EQ(256u, structure_.weight);
+      EXPECT_EQ(true, structure_.is_exclusive);
+    }
+  }
+  {
+    const char kData[] = {
+        '\x7f', '\xff',
+        '\xff', '\xff',  // Exclusive (no) and Dependency (0x7fffffff)
+        '\x00',          // Weight: 1 (after adding 1)
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(StreamIdMask(), structure_.stream_dependency);
+      EXPECT_EQ(1u, structure_.weight);
+      EXPECT_FALSE(structure_.is_exclusive);
+    }
+  }
+}
+
+TEST_F(PriorityFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class RstStreamFieldsDecoderTest
+    : public StructureDecoderTest<Http2RstStreamFields> {};
+
+TEST_F(RstStreamFieldsDecoderTest, DecodesLiteral) {
+  {
+    const char kData[] = {
+        '\x00', '\x00', '\x00', '\x01',  // Error: PROTOCOL_ERROR
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_TRUE(structure_.IsSupportedErrorCode());
+      EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code);
+    }
+  }
+  {
+    const char kData[] = {
+        '\xff', '\xff', '\xff',
+        '\xff',  // Error: max uint32 (Unknown error code)
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_FALSE(structure_.IsSupportedErrorCode());
+      EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code);
+    }
+  }
+}
+
+TEST_F(RstStreamFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class SettingFieldsDecoderTest
+    : public StructureDecoderTest<Http2SettingFields> {};
+
+TEST_F(SettingFieldsDecoderTest, DecodesLiteral) {
+  {
+    const char kData[] = {
+        '\x00', '\x01',                  // Setting: HEADER_TABLE_SIZE
+        '\x00', '\x00', '\x40', '\x00',  // Value: 16K
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_TRUE(structure_.IsSupportedParameter());
+      EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE,
+                structure_.parameter);
+      EXPECT_EQ(1u << 14, structure_.value);
+    }
+  }
+  {
+    const char kData[] = {
+        '\x00', '\x00',                  // Setting: Unknown (0)
+        '\xff', '\xff', '\xff', '\xff',  // Value: max uint32
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_FALSE(structure_.IsSupportedParameter());
+      EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter);
+    }
+  }
+}
+
+TEST_F(SettingFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class PushPromiseFieldsDecoderTest
+    : public StructureDecoderTest<Http2PushPromiseFields> {};
+
+TEST_F(PushPromiseFieldsDecoderTest, DecodesLiteral) {
+  {
+    const char kData[] = {
+        '\x00', '\x01', '\x8a', '\x92',  // Promised Stream ID: 101010
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(101010u, structure_.promised_stream_id);
+    }
+  }
+  {
+    // Promised stream id has R-bit (reserved for future use) set, which
+    // should be cleared by the decoder.
+    const char kData[] = {
+        '\xff', '\xff', '\xff',
+        '\xff',  // Promised Stream ID: max uint31 and R-bit
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id);
+    }
+  }
+}
+
+TEST_F(PushPromiseFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class PingFieldsDecoderTest : public StructureDecoderTest<Http2PingFields> {};
+
+TEST_F(PingFieldsDecoderTest, DecodesLiteral) {
+  {
+    // Each byte is different, so can detect if order changed.
+    const char kData[] = {
+        '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(Http2StringPiece(kData, 8),
+                ToStringPiece(structure_.opaque_bytes));
+    }
+  }
+  {
+    // All zeros, detect problems handling NULs.
+    const char kData[] = {
+        '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(Http2StringPiece(kData, 8),
+                ToStringPiece(structure_.opaque_bytes));
+    }
+  }
+  {
+    const char kData[] = {
+        '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(Http2StringPiece(kData, 8),
+                ToStringPiece(structure_.opaque_bytes));
+    }
+  }
+}
+
+TEST_F(PingFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class GoAwayFieldsDecoderTest : public StructureDecoderTest<Http2GoAwayFields> {
+};
+
+TEST_F(GoAwayFieldsDecoderTest, DecodesLiteral) {
+  {
+    const char kData[] = {
+        '\x00', '\x00', '\x00', '\x00',  // Last Stream ID: 0
+        '\x00', '\x00', '\x00', '\x00',  // Error: NO_ERROR (0)
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(0u, structure_.last_stream_id);
+      EXPECT_TRUE(structure_.IsSupportedErrorCode());
+      EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, structure_.error_code);
+    }
+  }
+  {
+    const char kData[] = {
+        '\x00', '\x00', '\x00', '\x01',  // Last Stream ID: 1
+        '\x00', '\x00', '\x00', '\x0d',  // Error: HTTP_1_1_REQUIRED
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(1u, structure_.last_stream_id);
+      EXPECT_TRUE(structure_.IsSupportedErrorCode());
+      EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, structure_.error_code);
+    }
+  }
+  {
+    const char kData[] = {
+        '\xff', '\xff',
+        '\xff', '\xff',  // Last Stream ID: max uint31 and R-bit
+        '\xff', '\xff',
+        '\xff', '\xff',  // Error: max uint32 (Unknown error code)
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(StreamIdMask(), structure_.last_stream_id);  // No high-bit.
+      EXPECT_FALSE(structure_.IsSupportedErrorCode());
+      EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code);
+    }
+  }
+}
+
+TEST_F(GoAwayFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class WindowUpdateFieldsDecoderTest
+    : public StructureDecoderTest<Http2WindowUpdateFields> {};
+
+TEST_F(WindowUpdateFieldsDecoderTest, DecodesLiteral) {
+  {
+    const char kData[] = {
+        '\x00', '\x01', '\x00', '\x00',  // Window Size Increment: 2 ^ 16
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(1u << 16, structure_.window_size_increment);
+    }
+  }
+  {
+    // Increment must be non-zero, but we need to be able to decode the invalid
+    // zero to detect it.
+    const char kData[] = {
+        '\x00', '\x00', '\x00', '\x00',  // Window Size Increment: 0
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(0u, structure_.window_size_increment);
+    }
+  }
+  {
+    // Increment has R-bit (reserved for future use) set, which
+    // should be cleared by the decoder.
+    // clang-format off
+    const char kData[] = {
+        // Window Size Increment: max uint31 and R-bit
+        '\xff', '\xff', '\xff', '\xff',
+    };
+    // clang-format on
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(StreamIdMask(), structure_.window_size_increment);
+    }
+  }
+}
+
+TEST_F(WindowUpdateFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class AltSvcFieldsDecoderTest : public StructureDecoderTest<Http2AltSvcFields> {
+};
+
+TEST_F(AltSvcFieldsDecoderTest, DecodesLiteral) {
+  {
+    const char kData[] = {
+        '\x00', '\x00',  // Origin Length: 0
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(0, structure_.origin_length);
+    }
+  }
+  {
+    const char kData[] = {
+        '\x00', '\x14',  // Origin Length: 20
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(20, structure_.origin_length);
+    }
+  }
+  {
+    const char kData[] = {
+        '\xff', '\xff',  // Origin Length: uint16 max
+    };
+    DecodeLeadingStructure(kData);
+    if (!HasFailure()) {
+      EXPECT_EQ(65535, structure_.origin_length);
+    }
+  }
+}
+
+TEST_F(AltSvcFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/decode_status.cc b/http2/decoder/decode_status.cc
new file mode 100644
index 0000000..6a913f0
--- /dev/null
+++ b/http2/decoder/decode_status.cc
@@ -0,0 +1,28 @@
+// 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 "net/third_party/quiche/src/http2/decoder/decode_status.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out, DecodeStatus v) {
+  switch (v) {
+    case DecodeStatus::kDecodeDone:
+      return out << "DecodeDone";
+    case DecodeStatus::kDecodeInProgress:
+      return out << "DecodeInProgress";
+    case DecodeStatus::kDecodeError:
+      return out << "DecodeError";
+  }
+  // Since the value doesn't come over the wire, only a programming bug should
+  // result in reaching this point.
+  int unknown = static_cast<int>(v);
+  HTTP2_BUG << "Unknown DecodeStatus " << unknown;
+  return out << "DecodeStatus(" << unknown << ")";
+}
+
+}  // namespace http2
diff --git a/http2/decoder/decode_status.h b/http2/decoder/decode_status.h
new file mode 100644
index 0000000..fdecd22
--- /dev/null
+++ b/http2/decoder/decode_status.h
@@ -0,0 +1,33 @@
+// 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_DECODER_DECODE_STATUS_H_
+#define QUICHE_HTTP2_DECODER_DECODE_STATUS_H_
+
+// Enum DecodeStatus is used to report the status of decoding of many
+// types of HTTP/2 and HPACK objects.
+
+#include <ostream>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+enum class DecodeStatus {
+  // Decoding is done.
+  kDecodeDone,
+
+  // Decoder needs more input to be able to make progress.
+  kDecodeInProgress,
+
+  // Decoding failed (e.g. HPACK variable length integer is too large, or
+  // an HTTP/2 frame has padding declared to be larger than the payload).
+  kDecodeError,
+};
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              DecodeStatus v);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_DECODE_STATUS_H_
diff --git a/http2/decoder/frame_decoder_state.cc b/http2/decoder/frame_decoder_state.cc
new file mode 100644
index 0000000..946a6ee
--- /dev/null
+++ b/http2/decoder/frame_decoder_state.cc
@@ -0,0 +1,81 @@
+// 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 "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+
+namespace http2 {
+
+DecodeStatus FrameDecoderState::ReadPadLength(DecodeBuffer* db,
+                                              bool report_pad_length) {
+  DVLOG(2) << "ReadPadLength db->Remaining=" << db->Remaining()
+           << "; payload_length=" << frame_header().payload_length;
+  DCHECK(IsPaddable());
+  DCHECK(frame_header().IsPadded());
+
+  // Pad Length is always at the start of the frame, so remaining_payload_
+  // should equal payload_length at this point.
+  const uint32_t total_payload = frame_header().payload_length;
+  DCHECK_EQ(total_payload, remaining_payload_);
+  DCHECK_EQ(0u, remaining_padding_);
+
+  if (db->HasData()) {
+    const uint32_t pad_length = db->DecodeUInt8();
+    const uint32_t total_padding = pad_length + 1;
+    if (total_padding <= total_payload) {
+      remaining_padding_ = pad_length;
+      remaining_payload_ = total_payload - total_padding;
+      if (report_pad_length) {
+        listener()->OnPadLength(pad_length);
+      }
+      return DecodeStatus::kDecodeDone;
+    }
+    const uint32_t missing_length = total_padding - total_payload;
+    // To allow for the possibility of recovery, record the number of
+    // remaining bytes of the frame's payload (invalid though it is)
+    // in remaining_payload_.
+    remaining_payload_ = total_payload - 1;  // 1 for sizeof(Pad Length).
+    remaining_padding_ = 0;
+    listener()->OnPaddingTooLong(frame_header(), missing_length);
+    return DecodeStatus::kDecodeError;
+  }
+
+  if (total_payload == 0) {
+    remaining_payload_ = 0;
+    remaining_padding_ = 0;
+    listener()->OnPaddingTooLong(frame_header(), 1);
+    return DecodeStatus::kDecodeError;
+  }
+  // Need to wait for another buffer.
+  return DecodeStatus::kDecodeInProgress;
+}
+
+bool FrameDecoderState::SkipPadding(DecodeBuffer* db) {
+  DVLOG(2) << "SkipPadding remaining_padding_=" << remaining_padding_
+           << ", db->Remaining=" << db->Remaining()
+           << ", header: " << frame_header();
+  DCHECK_EQ(remaining_payload_, 0u);
+  DCHECK(IsPaddable()) << "header: " << frame_header();
+  DCHECK_GE(remaining_padding_, 0u);
+  DCHECK(remaining_padding_ == 0 || frame_header().IsPadded())
+      << "remaining_padding_=" << remaining_padding_
+      << ", header: " << frame_header();
+  const size_t avail = AvailablePadding(db);
+  if (avail > 0) {
+    listener()->OnPadding(db->cursor(), avail);
+    db->AdvanceCursor(avail);
+    remaining_padding_ -= avail;
+  }
+  return remaining_padding_ == 0;
+}
+
+DecodeStatus FrameDecoderState::ReportFrameSizeError() {
+  DVLOG(2) << "FrameDecoderState::ReportFrameSizeError: "
+           << " remaining_payload_=" << remaining_payload_
+           << "; remaining_padding_=" << remaining_padding_
+           << ", header: " << frame_header();
+  listener()->OnFrameSizeError(frame_header());
+  return DecodeStatus::kDecodeError;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/frame_decoder_state.h b/http2/decoder/frame_decoder_state.h
new file mode 100644
index 0000000..1581752
--- /dev/null
+++ b/http2/decoder/frame_decoder_state.h
@@ -0,0 +1,250 @@
+// 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_DECODER_FRAME_DECODER_STATE_H_
+#define QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_H_
+
+// FrameDecoderState provides state and behaviors in support of decoding
+// the common frame header and the payload of all frame types.
+// It is an input to all of the payload decoders.
+
+// TODO(jamessynge): Since FrameDecoderState has far more than state in it,
+// rename to FrameDecoderHelper, or similar.
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class FrameDecoderStatePeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE FrameDecoderState {
+ public:
+  FrameDecoderState() {}
+
+  // Sets the listener which the decoders should call as they decode HTTP/2
+  // frames. The listener can be changed at any time, which allows for replacing
+  // it with a no-op listener when an error is detected, either by the payload
+  // decoder (OnPaddingTooLong or OnFrameSizeError) or by the "real" listener.
+  // That in turn allows us to define Http2FrameDecoderListener such that all
+  // methods have return type void, with no direct way to indicate whether the
+  // decoder should stop, and to eliminate from the decoder all checks of the
+  // return value. Instead the listener/caller can simply replace the current
+  // listener with a no-op listener implementation.
+  // TODO(jamessynge): Make set_listener private as only Http2FrameDecoder
+  // and tests need to set it, so it doesn't need to be public.
+  void set_listener(Http2FrameDecoderListener* listener) {
+    listener_ = listener;
+  }
+  Http2FrameDecoderListener* listener() const { return listener_; }
+
+  // The most recently decoded frame header.
+  const Http2FrameHeader& frame_header() const { return frame_header_; }
+
+  // Decode a structure in the payload, adjusting remaining_payload_ to account
+  // for the consumed portion of the payload. Returns kDecodeDone when fully
+  // decoded, kDecodeError if it ran out of payload before decoding completed,
+  // and kDecodeInProgress if the decode buffer didn't have enough of the
+  // remaining payload.
+  template <class S>
+  DecodeStatus StartDecodingStructureInPayload(S* out, DecodeBuffer* db) {
+    DVLOG(2) << __func__ << "\n\tdb->Remaining=" << db->Remaining()
+             << "\n\tremaining_payload_=" << remaining_payload_
+             << "\n\tneed=" << S::EncodedSize();
+    DecodeStatus status =
+        structure_decoder_.Start(out, db, &remaining_payload_);
+    if (status != DecodeStatus::kDecodeError) {
+      return status;
+    }
+    DVLOG(2) << "StartDecodingStructureInPayload: detected frame size error";
+    return ReportFrameSizeError();
+  }
+
+  // Resume decoding of a structure that has been split across buffers,
+  // adjusting remaining_payload_ to account for the consumed portion of
+  // the payload. Returns values are as for StartDecodingStructureInPayload.
+  template <class S>
+  DecodeStatus ResumeDecodingStructureInPayload(S* out, DecodeBuffer* db) {
+    DVLOG(2) << __func__ << "\n\tdb->Remaining=" << db->Remaining()
+             << "\n\tremaining_payload_=" << remaining_payload_;
+    if (structure_decoder_.Resume(out, db, &remaining_payload_)) {
+      return DecodeStatus::kDecodeDone;
+    } else if (remaining_payload_ > 0) {
+      return DecodeStatus::kDecodeInProgress;
+    } else {
+      DVLOG(2) << "ResumeDecodingStructureInPayload: detected frame size error";
+      return ReportFrameSizeError();
+    }
+  }
+
+  // Initializes the two remaining* fields, which is needed if the frame's
+  // payload is split across buffers, or the decoder calls ReadPadLength or
+  // StartDecodingStructureInPayload, and of course the methods below which
+  // read those fields, as their names imply.
+  void InitializeRemainders() {
+    remaining_payload_ = frame_header().payload_length;
+    // Note that remaining_total_payload() relies on remaining_padding_ being
+    // zero for frames that have no padding.
+    remaining_padding_ = 0;
+  }
+
+  // Returns the number of bytes of the frame's payload that remain to be
+  // decoded, including any trailing padding. This method must only be called
+  // after the variables have been initialized, which in practice means once a
+  // payload decoder has called InitializeRemainders and/or ReadPadLength.
+  size_t remaining_total_payload() const {
+    DCHECK(IsPaddable() || remaining_padding_ == 0) << frame_header();
+    return remaining_payload_ + remaining_padding_;
+  }
+
+  // Returns the number of bytes of the frame's payload that remain to be
+  // decoded, excluding any trailing padding. This method must only be called
+  // after the variable has been initialized, which in practice means once a
+  // payload decoder has called InitializeRemainders; ReadPadLength will deduct
+  // the total number of padding bytes from remaining_payload_, including the
+  // size of the Pad Length field itself (1 byte).
+  size_t remaining_payload() const { return remaining_payload_; }
+
+  // Returns the number of bytes of the frame's payload that remain to be
+  // decoded, including any trailing padding. This method must only be called if
+  // the frame type allows padding, and after the variable has been initialized,
+  // which in practice means once a payload decoder has called
+  // InitializeRemainders and/or ReadPadLength.
+  size_t remaining_payload_and_padding() const {
+    DCHECK(IsPaddable()) << frame_header();
+    return remaining_payload_ + remaining_padding_;
+  }
+
+  // Returns the number of bytes of trailing padding after the payload that
+  // remain to be decoded. This method must only be called if the frame type
+  // allows padding, and after the variable has been initialized, which in
+  // practice means once a payload decoder has called InitializeRemainders,
+  // and isn't set to a non-zero value until ReadPadLength has been called.
+  uint32_t remaining_padding() const {
+    DCHECK(IsPaddable()) << frame_header();
+    return remaining_padding_;
+  }
+
+  // How many bytes of the remaining payload are in db?
+  size_t AvailablePayload(DecodeBuffer* db) const {
+    return db->MinLengthRemaining(remaining_payload_);
+  }
+
+  // How many bytes of the remaining payload and padding are in db?
+  // Call only for frames whose type is paddable.
+  size_t AvailablePayloadAndPadding(DecodeBuffer* db) const {
+    DCHECK(IsPaddable()) << frame_header();
+    return db->MinLengthRemaining(remaining_payload_ + remaining_padding_);
+  }
+
+  // How many bytes of the padding that have not yet been skipped are in db?
+  // Call only after remaining_padding_ has been set (for padded frames), or
+  // been cleared (for unpadded frames); and after all of the non-padding
+  // payload has been decoded.
+  size_t AvailablePadding(DecodeBuffer* db) const {
+    DCHECK(IsPaddable()) << frame_header();
+    DCHECK_EQ(remaining_payload_, 0u);
+    return db->MinLengthRemaining(remaining_padding_);
+  }
+
+  // Reduces remaining_payload_ by amount. To be called by a payload decoder
+  // after it has passed a variable length portion of the payload to the
+  // listener; remaining_payload_ will be automatically reduced when fixed
+  // size structures and padding, including the Pad Length field, are decoded.
+  void ConsumePayload(size_t amount) {
+    DCHECK_LE(amount, remaining_payload_);
+    remaining_payload_ -= amount;
+  }
+
+  // Reads the Pad Length field into remaining_padding_, and appropriately sets
+  // remaining_payload_. When present, the Pad Length field is always the first
+  // field in the payload, which this method relies on so that the caller need
+  // not set remaining_payload_ before calling this method.
+  // If report_pad_length is true, calls the listener's OnPadLength method when
+  // it decodes the Pad Length field.
+  // Returns kDecodeDone if the decode buffer was not empty (i.e. because the
+  // field is only a single byte long, it can always be decoded if the buffer is
+  // not empty).
+  // Returns kDecodeError if the buffer is empty because the frame has no
+  // payload (i.e. payload_length() == 0).
+  // Returns kDecodeInProgress if the buffer is empty but the frame has a
+  // payload.
+  DecodeStatus ReadPadLength(DecodeBuffer* db, bool report_pad_length);
+
+  // Skip the trailing padding bytes; only call once remaining_payload_==0.
+  // Returns true when the padding has been skipped.
+  // Does NOT check that the padding is all zeroes.
+  bool SkipPadding(DecodeBuffer* db);
+
+  // Calls the listener's OnFrameSizeError method and returns kDecodeError.
+  DecodeStatus ReportFrameSizeError();
+
+ private:
+  friend class Http2FrameDecoder;
+  friend class test::FrameDecoderStatePeer;
+
+  // Starts the decoding of a common frame header. Returns true if completed the
+  // decoding, false if the decode buffer didn't have enough data in it, in
+  // which case the decode buffer will have been drained and the caller should
+  // call ResumeDecodingFrameHeader when more data is available. This is called
+  // from Http2FrameDecoder, a friend class.
+  bool StartDecodingFrameHeader(DecodeBuffer* db) {
+    return structure_decoder_.Start(&frame_header_, db);
+  }
+
+  // Resumes decoding the common frame header after the preceding call to
+  // StartDecodingFrameHeader returned false, as did any subsequent calls to
+  // ResumeDecodingFrameHeader. This is called from Http2FrameDecoder,
+  // a friend class.
+  bool ResumeDecodingFrameHeader(DecodeBuffer* db) {
+    return structure_decoder_.Resume(&frame_header_, db);
+  }
+
+  // Clear any of the flags in the frame header that aren't set in valid_flags.
+  void RetainFlags(uint8_t valid_flags) {
+    frame_header_.RetainFlags(valid_flags);
+  }
+
+  // Clear all of the flags in the frame header; for use with frame types that
+  // don't define any flags, such as WINDOW_UPDATE.
+  void ClearFlags() { frame_header_.flags = Http2FrameFlag(); }
+
+  // Returns true if the type of frame being decoded can have padding.
+  bool IsPaddable() const {
+    return frame_header().type == Http2FrameType::DATA ||
+           frame_header().type == Http2FrameType::HEADERS ||
+           frame_header().type == Http2FrameType::PUSH_PROMISE;
+  }
+
+  Http2FrameDecoderListener* listener_ = nullptr;
+  Http2FrameHeader frame_header_;
+
+  // Number of bytes remaining to be decoded, if set; does not include the
+  // trailing padding once the length of padding has been determined.
+  // See ReadPadLength.
+  uint32_t remaining_payload_;
+
+  // The amount of trailing padding after the payload that remains to be
+  // decoded. See ReadPadLength.
+  uint32_t remaining_padding_;
+
+  // Generic decoder of structures, which takes care of buffering the needed
+  // bytes if the encoded structure is split across decode buffers.
+  Http2StructureDecoder structure_decoder_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_H_
diff --git a/http2/decoder/frame_decoder_state_test_util.cc b/http2/decoder/frame_decoder_state_test_util.cc
new file mode 100644
index 0000000..7c13ef0
--- /dev/null
+++ b/http2/decoder/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 "net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// static
+void FrameDecoderStatePeer::Randomize(FrameDecoderState* p, Http2Random* rng) {
+  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) {
+  VLOG(1) << "FrameDecoderStatePeer::set_frame_header " << header;
+  p->frame_header_ = header;
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/frame_decoder_state_test_util.h b/http2/decoder/frame_decoder_state_test_util.h
new file mode 100644
index 0000000..fc81a4a
--- /dev/null
+++ b/http2/decoder/frame_decoder_state_test_util.h
@@ -0,0 +1,36 @@
+// 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_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_
+#define QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_
+
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class 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_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_
diff --git a/http2/decoder/http2_frame_decoder.cc b/http2/decoder/http2_frame_decoder.cc
new file mode 100644
index 0000000..314dfbf
--- /dev/null
+++ b/http2/decoder/http2_frame_decoder.cc
@@ -0,0 +1,433 @@
+// 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 "net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h"
+
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out, Http2FrameDecoder::State v) {
+  switch (v) {
+    case Http2FrameDecoder::State::kStartDecodingHeader:
+      return out << "kStartDecodingHeader";
+    case Http2FrameDecoder::State::kResumeDecodingHeader:
+      return out << "kResumeDecodingHeader";
+    case Http2FrameDecoder::State::kResumeDecodingPayload:
+      return out << "kResumeDecodingPayload";
+    case Http2FrameDecoder::State::kDiscardPayload:
+      return out << "kDiscardPayload";
+  }
+  // Since the value doesn't come over the wire, only a programming bug should
+  // result in reaching this point.
+  int unknown = static_cast<int>(v);
+  HTTP2_BUG << "Http2FrameDecoder::State " << unknown;
+  return out << "Http2FrameDecoder::State(" << unknown << ")";
+}
+
+Http2FrameDecoder::Http2FrameDecoder(Http2FrameDecoderListener* listener)
+    : state_(State::kStartDecodingHeader),
+      maximum_payload_size_(Http2SettingsInfo::DefaultMaxFrameSize()) {
+  set_listener(listener);
+}
+
+void Http2FrameDecoder::set_listener(Http2FrameDecoderListener* listener) {
+  if (listener == nullptr) {
+    listener = &no_op_listener_;
+  }
+  frame_decoder_state_.set_listener(listener);
+}
+
+Http2FrameDecoderListener* Http2FrameDecoder::listener() const {
+  return frame_decoder_state_.listener();
+}
+
+DecodeStatus Http2FrameDecoder::DecodeFrame(DecodeBuffer* db) {
+  DVLOG(2) << "Http2FrameDecoder::DecodeFrame state=" << state_;
+  switch (state_) {
+    case State::kStartDecodingHeader:
+      if (frame_decoder_state_.StartDecodingFrameHeader(db)) {
+        return StartDecodingPayload(db);
+      }
+      state_ = State::kResumeDecodingHeader;
+      return DecodeStatus::kDecodeInProgress;
+
+    case State::kResumeDecodingHeader:
+      if (frame_decoder_state_.ResumeDecodingFrameHeader(db)) {
+        return StartDecodingPayload(db);
+      }
+      return DecodeStatus::kDecodeInProgress;
+
+    case State::kResumeDecodingPayload:
+      return ResumeDecodingPayload(db);
+
+    case State::kDiscardPayload:
+      return DiscardPayload(db);
+  }
+
+  HTTP2_UNREACHABLE();
+  return DecodeStatus::kDecodeError;
+}
+
+size_t Http2FrameDecoder::remaining_payload() const {
+  return frame_decoder_state_.remaining_payload();
+}
+
+uint32_t Http2FrameDecoder::remaining_padding() const {
+  return frame_decoder_state_.remaining_padding();
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingPayload(DecodeBuffer* db) {
+  const Http2FrameHeader& header = frame_header();
+
+  // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+  // SpdyFramer's exact states.
+  if (!listener()->OnFrameHeader(header)) {
+    DVLOG(2) << "OnFrameHeader rejected the frame, will discard; header: "
+             << header;
+    state_ = State::kDiscardPayload;
+    frame_decoder_state_.InitializeRemainders();
+    return DecodeStatus::kDecodeError;
+  }
+
+  if (header.payload_length > maximum_payload_size_) {
+    DVLOG(2) << "Payload length is greater than allowed: "
+             << header.payload_length << " > " << maximum_payload_size_
+             << "\n   header: " << header;
+    state_ = State::kDiscardPayload;
+    frame_decoder_state_.InitializeRemainders();
+    listener()->OnFrameSizeError(header);
+    return DecodeStatus::kDecodeError;
+  }
+
+  // The decode buffer can extend across many frames. Make sure that the
+  // buffer we pass to the start method that is specific to the frame type
+  // does not exend beyond this frame.
+  DecodeBufferSubset subset(db, header.payload_length);
+  DecodeStatus status;
+  switch (header.type) {
+    case Http2FrameType::DATA:
+      status = StartDecodingDataPayload(&subset);
+      break;
+
+    case Http2FrameType::HEADERS:
+      status = StartDecodingHeadersPayload(&subset);
+      break;
+
+    case Http2FrameType::PRIORITY:
+      status = StartDecodingPriorityPayload(&subset);
+      break;
+
+    case Http2FrameType::RST_STREAM:
+      status = StartDecodingRstStreamPayload(&subset);
+      break;
+
+    case Http2FrameType::SETTINGS:
+      status = StartDecodingSettingsPayload(&subset);
+      break;
+
+    case Http2FrameType::PUSH_PROMISE:
+      status = StartDecodingPushPromisePayload(&subset);
+      break;
+
+    case Http2FrameType::PING:
+      status = StartDecodingPingPayload(&subset);
+      break;
+
+    case Http2FrameType::GOAWAY:
+      status = StartDecodingGoAwayPayload(&subset);
+      break;
+
+    case Http2FrameType::WINDOW_UPDATE:
+      status = StartDecodingWindowUpdatePayload(&subset);
+      break;
+
+    case Http2FrameType::CONTINUATION:
+      status = StartDecodingContinuationPayload(&subset);
+      break;
+
+    case Http2FrameType::ALTSVC:
+      status = StartDecodingAltSvcPayload(&subset);
+      break;
+
+    default:
+      status = StartDecodingUnknownPayload(&subset);
+      break;
+  }
+
+  if (status == DecodeStatus::kDecodeDone) {
+    state_ = State::kStartDecodingHeader;
+    return status;
+  } else if (status == DecodeStatus::kDecodeInProgress) {
+    state_ = State::kResumeDecodingPayload;
+    return status;
+  } else {
+    state_ = State::kDiscardPayload;
+    return status;
+  }
+}
+
+DecodeStatus Http2FrameDecoder::ResumeDecodingPayload(DecodeBuffer* db) {
+  // The decode buffer can extend across many frames. Make sure that the
+  // buffer we pass to the start method that is specific to the frame type
+  // does not exend beyond this frame.
+  size_t remaining = frame_decoder_state_.remaining_total_payload();
+  DCHECK_LE(remaining, frame_header().payload_length);
+  DecodeBufferSubset subset(db, remaining);
+  DecodeStatus status;
+  switch (frame_header().type) {
+    case Http2FrameType::DATA:
+      status = ResumeDecodingDataPayload(&subset);
+      break;
+
+    case Http2FrameType::HEADERS:
+      status = ResumeDecodingHeadersPayload(&subset);
+      break;
+
+    case Http2FrameType::PRIORITY:
+      status = ResumeDecodingPriorityPayload(&subset);
+      break;
+
+    case Http2FrameType::RST_STREAM:
+      status = ResumeDecodingRstStreamPayload(&subset);
+      break;
+
+    case Http2FrameType::SETTINGS:
+      status = ResumeDecodingSettingsPayload(&subset);
+      break;
+
+    case Http2FrameType::PUSH_PROMISE:
+      status = ResumeDecodingPushPromisePayload(&subset);
+      break;
+
+    case Http2FrameType::PING:
+      status = ResumeDecodingPingPayload(&subset);
+      break;
+
+    case Http2FrameType::GOAWAY:
+      status = ResumeDecodingGoAwayPayload(&subset);
+      break;
+
+    case Http2FrameType::WINDOW_UPDATE:
+      status = ResumeDecodingWindowUpdatePayload(&subset);
+      break;
+
+    case Http2FrameType::CONTINUATION:
+      status = ResumeDecodingContinuationPayload(&subset);
+      break;
+
+    case Http2FrameType::ALTSVC:
+      status = ResumeDecodingAltSvcPayload(&subset);
+      break;
+
+    default:
+      status = ResumeDecodingUnknownPayload(&subset);
+      break;
+  }
+
+  if (status == DecodeStatus::kDecodeDone) {
+    state_ = State::kStartDecodingHeader;
+    return status;
+  } else if (status == DecodeStatus::kDecodeInProgress) {
+    return status;
+  } else {
+    state_ = State::kDiscardPayload;
+    return status;
+  }
+}
+
+// Clear any of the flags in the frame header that aren't set in valid_flags.
+void Http2FrameDecoder::RetainFlags(uint8_t valid_flags) {
+  frame_decoder_state_.RetainFlags(valid_flags);
+}
+
+// Clear all of the flags in the frame header; for use with frame types that
+// don't define any flags, such as WINDOW_UPDATE.
+void Http2FrameDecoder::ClearFlags() {
+  frame_decoder_state_.ClearFlags();
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingAltSvcPayload(DecodeBuffer* db) {
+  ClearFlags();
+  return altsvc_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+                                                      db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingAltSvcPayload(DecodeBuffer* db) {
+  // The frame is not paddable.
+  DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+            frame_decoder_state_.remaining_payload());
+  return altsvc_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+                                                       db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingContinuationPayload(
+    DecodeBuffer* db) {
+  RetainFlags(Http2FrameFlag::END_HEADERS);
+  return continuation_payload_decoder_.StartDecodingPayload(
+      &frame_decoder_state_, db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingContinuationPayload(
+    DecodeBuffer* db) {
+  // The frame is not paddable.
+  DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+            frame_decoder_state_.remaining_payload());
+  return continuation_payload_decoder_.ResumeDecodingPayload(
+      &frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingDataPayload(DecodeBuffer* db) {
+  RetainFlags(Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED);
+  return data_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingDataPayload(DecodeBuffer* db) {
+  return data_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingGoAwayPayload(DecodeBuffer* db) {
+  ClearFlags();
+  return goaway_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+                                                      db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingGoAwayPayload(DecodeBuffer* db) {
+  // The frame is not paddable.
+  DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+            frame_decoder_state_.remaining_payload());
+  return goaway_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+                                                       db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingHeadersPayload(DecodeBuffer* db) {
+  RetainFlags(Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS |
+              Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY);
+  return headers_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+                                                       db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingHeadersPayload(DecodeBuffer* db) {
+  DCHECK_LE(frame_decoder_state_.remaining_payload_and_padding(),
+            frame_header().payload_length);
+  return headers_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+                                                        db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingPingPayload(DecodeBuffer* db) {
+  RetainFlags(Http2FrameFlag::ACK);
+  return ping_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingPingPayload(DecodeBuffer* db) {
+  // The frame is not paddable.
+  DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+            frame_decoder_state_.remaining_payload());
+  return ping_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingPriorityPayload(DecodeBuffer* db) {
+  ClearFlags();
+  return priority_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+                                                        db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingPriorityPayload(
+    DecodeBuffer* db) {
+  // The frame is not paddable.
+  DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+            frame_decoder_state_.remaining_payload());
+  return priority_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+                                                         db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingPushPromisePayload(
+    DecodeBuffer* db) {
+  RetainFlags(Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED);
+  return push_promise_payload_decoder_.StartDecodingPayload(
+      &frame_decoder_state_, db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingPushPromisePayload(
+    DecodeBuffer* db) {
+  DCHECK_LE(frame_decoder_state_.remaining_payload_and_padding(),
+            frame_header().payload_length);
+  return push_promise_payload_decoder_.ResumeDecodingPayload(
+      &frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingRstStreamPayload(
+    DecodeBuffer* db) {
+  ClearFlags();
+  return rst_stream_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+                                                          db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingRstStreamPayload(
+    DecodeBuffer* db) {
+  // The frame is not paddable.
+  DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+            frame_decoder_state_.remaining_payload());
+  return rst_stream_payload_decoder_.ResumeDecodingPayload(
+      &frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingSettingsPayload(DecodeBuffer* db) {
+  RetainFlags(Http2FrameFlag::ACK);
+  return settings_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+                                                        db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingSettingsPayload(
+    DecodeBuffer* db) {
+  // The frame is not paddable.
+  DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+            frame_decoder_state_.remaining_payload());
+  return settings_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+                                                         db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingUnknownPayload(DecodeBuffer* db) {
+  // We don't known what type of frame this is, so we don't know which flags
+  // are valid, so we don't touch them.
+  return unknown_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+                                                       db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingUnknownPayload(DecodeBuffer* db) {
+  // We don't known what type of frame this is, so we treat it as not paddable.
+  DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+            frame_decoder_state_.remaining_payload());
+  return unknown_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+                                                        db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingWindowUpdatePayload(
+    DecodeBuffer* db) {
+  ClearFlags();
+  return window_update_payload_decoder_.StartDecodingPayload(
+      &frame_decoder_state_, db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingWindowUpdatePayload(
+    DecodeBuffer* db) {
+  // The frame is not paddable.
+  DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+            frame_decoder_state_.remaining_payload());
+  return window_update_payload_decoder_.ResumeDecodingPayload(
+      &frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::DiscardPayload(DecodeBuffer* db) {
+  DVLOG(2) << "remaining_payload=" << frame_decoder_state_.remaining_payload_
+           << "; remaining_padding=" << frame_decoder_state_.remaining_padding_;
+  frame_decoder_state_.remaining_payload_ +=
+      frame_decoder_state_.remaining_padding_;
+  frame_decoder_state_.remaining_padding_ = 0;
+  const size_t avail = frame_decoder_state_.AvailablePayload(db);
+  DVLOG(2) << "avail=" << avail;
+  if (avail > 0) {
+    frame_decoder_state_.ConsumePayload(avail);
+    db->AdvanceCursor(avail);
+  }
+  if (frame_decoder_state_.remaining_payload_ == 0) {
+    state_ = State::kStartDecodingHeader;
+    return DecodeStatus::kDecodeDone;
+  }
+  return DecodeStatus::kDecodeInProgress;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/http2_frame_decoder.h b/http2/decoder/http2_frame_decoder.h
new file mode 100644
index 0000000..6257bbe
--- /dev/null
+++ b/http2/decoder/http2_frame_decoder.h
@@ -0,0 +1,205 @@
+// 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_DECODER_HTTP2_FRAME_DECODER_H_
+#define QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_H_
+
+// Http2FrameDecoder decodes the available input until it reaches the end of
+// the input or it reaches the end of the first frame in the input.
+// Note that Http2FrameDecoder does only minimal validation; for example,
+// stream ids are not checked, nor is the sequence of frames such as
+// CONTINUATION frame placement.
+//
+// Http2FrameDecoder enters state kError once it has called the listener's
+// OnFrameSizeError or OnPaddingTooLong methods, and at this time has no
+// provision for leaving that state. While the HTTP/2 spec (RFC7540) allows
+// for some such errors to be considered as just stream errors in some cases,
+// this implementation treats them all as connection errors.
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class Http2FrameDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE Http2FrameDecoder {
+ public:
+  explicit Http2FrameDecoder(Http2FrameDecoderListener* listener);
+  Http2FrameDecoder() : Http2FrameDecoder(nullptr) {}
+
+  Http2FrameDecoder(const Http2FrameDecoder&) = delete;
+  Http2FrameDecoder& operator=(const Http2FrameDecoder&) = delete;
+
+  // The decoder will call the listener's methods as it decodes a frame.
+  void set_listener(Http2FrameDecoderListener* listener);
+  Http2FrameDecoderListener* listener() const;
+
+  // The decoder will reject frame's whose payload
+  // length field exceeds the maximum payload size.
+  void set_maximum_payload_size(size_t v) { maximum_payload_size_ = v; }
+  size_t maximum_payload_size() const { return maximum_payload_size_; }
+
+  // Decodes the input up to the next frame boundary (i.e. at most one frame).
+  //
+  // Returns kDecodeDone if it decodes the final byte of a frame, OR if there
+  // is no input and it is awaiting the start of a new frame (e.g. if this
+  // is the first call to DecodeFrame, or if the previous call returned
+  // kDecodeDone).
+  //
+  // Returns kDecodeInProgress if it decodes all of the decode buffer, but has
+  // not reached the end of the frame.
+  //
+  // Returns kDecodeError if the frame's padding or length wasn't valid (i.e. if
+  // the decoder called either the listener's OnPaddingTooLong or
+  // OnFrameSizeError method).
+  DecodeStatus DecodeFrame(DecodeBuffer* db);
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Methods that support Http2FrameDecoderAdapter.
+
+  // Is the remainder of the frame's payload being discarded?
+  bool IsDiscardingPayload() const { return state_ == State::kDiscardPayload; }
+
+  // Returns the number of bytes of the frame's payload that remain to be
+  // decoded, excluding any trailing padding. This method must only be called
+  // after the frame header has been decoded AND DecodeFrame has returned
+  // kDecodeInProgress.
+  size_t remaining_payload() const;
+
+  // Returns the number of bytes of trailing padding after the payload that
+  // remain to be decoded. This method must only be called if the frame type
+  // allows padding, and after the frame header has been decoded AND
+  // DecodeFrame has returned. Will return 0 if the Pad Length field has not
+  // yet been decoded.
+  uint32_t remaining_padding() const;
+
+ private:
+  enum class State {
+    // Ready to start decoding a new frame's header.
+    kStartDecodingHeader,
+    // Was in state kStartDecodingHeader, but unable to read the entire frame
+    // header, so needs more input to complete decoding the header.
+    kResumeDecodingHeader,
+
+    // Have decoded the frame header, and started decoding the available bytes
+    // of the frame's payload, but need more bytes to finish the job.
+    kResumeDecodingPayload,
+
+    // Decoding of the most recently started frame resulted in an error:
+    // OnPaddingTooLong or OnFrameSizeError was called to indicate that the
+    // decoder detected a problem, or OnFrameHeader returned false, indicating
+    // that the listener detected a problem. Regardless of which, the decoder
+    // will stay in state kDiscardPayload until it has been passed the rest
+    // of the bytes of the frame's payload that it hasn't yet seen, after
+    // which it will be ready to decode another frame.
+    kDiscardPayload,
+  };
+
+  friend class test::Http2FrameDecoderPeer;
+  friend std::ostream& operator<<(std::ostream& out, State v);
+
+  DecodeStatus StartDecodingPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingPayload(DecodeBuffer* db);
+  DecodeStatus DiscardPayload(DecodeBuffer* db);
+
+  const Http2FrameHeader& frame_header() const {
+    return frame_decoder_state_.frame_header();
+  }
+
+  // Clear any of the flags in the frame header that aren't set in valid_flags.
+  void RetainFlags(uint8_t valid_flags);
+
+  // Clear all of the flags in the frame header; for use with frame types that
+  // don't define any flags, such as WINDOW_UPDATE.
+  void ClearFlags();
+
+  // These methods call the StartDecodingPayload() method of the frame type's
+  // payload decoder, after first clearing invalid flags in the header. The
+  // caller must ensure that the decode buffer does not extend beyond the
+  // end of the payload (handled by Http2FrameDecoder::StartDecodingPayload).
+  DecodeStatus StartDecodingAltSvcPayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingContinuationPayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingDataPayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingGoAwayPayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingHeadersPayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingPingPayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingPriorityPayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingPushPromisePayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingRstStreamPayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingSettingsPayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingUnknownPayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingWindowUpdatePayload(DecodeBuffer* db);
+
+  // These methods call the ResumeDecodingPayload() method of the frame type's
+  // payload decoder; they are called only if the preceding call to the
+  // corresponding Start method (above) returned kDecodeInProgress, as did any
+  // subsequent calls to the resume method.
+  // Unlike the Start methods, the decode buffer may extend beyond the
+  // end of the payload, so the method will create a DecodeBufferSubset
+  // before calling the ResumeDecodingPayload method of the frame type's
+  // payload decoder.
+  DecodeStatus ResumeDecodingAltSvcPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingContinuationPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingDataPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingGoAwayPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingHeadersPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingPingPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingPriorityPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingPushPromisePayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingRstStreamPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingSettingsPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingUnknownPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingWindowUpdatePayload(DecodeBuffer* db);
+
+  FrameDecoderState frame_decoder_state_;
+
+  // We only need one payload decoder at a time, so they share the same storage.
+  union {
+    AltSvcPayloadDecoder altsvc_payload_decoder_;
+    ContinuationPayloadDecoder continuation_payload_decoder_;
+    DataPayloadDecoder data_payload_decoder_;
+    GoAwayPayloadDecoder goaway_payload_decoder_;
+    HeadersPayloadDecoder headers_payload_decoder_;
+    PingPayloadDecoder ping_payload_decoder_;
+    PriorityPayloadDecoder priority_payload_decoder_;
+    PushPromisePayloadDecoder push_promise_payload_decoder_;
+    RstStreamPayloadDecoder rst_stream_payload_decoder_;
+    SettingsPayloadDecoder settings_payload_decoder_;
+    UnknownPayloadDecoder unknown_payload_decoder_;
+    WindowUpdatePayloadDecoder window_update_payload_decoder_;
+  };
+
+  State state_;
+  size_t maximum_payload_size_;
+
+  // Listener used whenever caller passes nullptr to set_listener.
+  Http2FrameDecoderNoOpListener no_op_listener_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_H_
diff --git a/http2/decoder/http2_frame_decoder_listener.cc b/http2/decoder/http2_frame_decoder_listener.cc
new file mode 100644
index 0000000..4cbce66
--- /dev/null
+++ b/http2/decoder/http2_frame_decoder_listener.cc
@@ -0,0 +1,14 @@
+// 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 "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+
+namespace http2 {
+
+bool Http2FrameDecoderNoOpListener::OnFrameHeader(
+    const Http2FrameHeader& header) {
+  return true;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/http2_frame_decoder_listener.h b/http2/decoder/http2_frame_decoder_listener.h
new file mode 100644
index 0000000..b875122
--- /dev/null
+++ b/http2/decoder/http2_frame_decoder_listener.h
@@ -0,0 +1,357 @@
+// 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_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_
+#define QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_
+
+// Http2FrameDecoderListener is the interface which the HTTP/2 decoder uses
+// to report the decoded frames to a listener.
+//
+// The general design is to assume that the listener will copy the data it needs
+// (e.g. frame headers) and will keep track of the implicit state of the
+// decoding process (i.e. the decoder maintains just the information it needs in
+// order to perform the decoding). Therefore, the parameters are just those with
+// (potentially) new data, not previously provided info about the current frame.
+//
+// The calls are described as if they are made in quick succession, i.e. one
+// after another, but of course the decoder needs input to decode, and the
+// decoder will only call the listener once the necessary input has been
+// provided. For example: OnDataStart can only be called once the 9 bytes of
+// of an HTTP/2 common frame header have been received. The decoder will call
+// the listener methods as soon as possible to avoid almost all buffering.
+//
+// The listener interface is designed so that it is possible to exactly
+// reconstruct the serialized frames, with the exception of reserved bits,
+// including in the frame header's flags and stream_id fields, which will have
+// been cleared before the methods below are called.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <type_traits>
+
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+// TODO(jamessynge): Consider sorting the methods by frequency of call, if that
+// helps at all.
+class Http2FrameDecoderListener {
+ public:
+  Http2FrameDecoderListener() {}
+  virtual ~Http2FrameDecoderListener() {}
+
+  // Called once the common frame header has been decoded for any frame, and
+  // before any of the methods below, which will also be called. This method is
+  // included in this interface only for the purpose of supporting SpdyFramer
+  // semantics via an adapter. This is the only method that has a non-void
+  // return type, and this is just so that Http2FrameDecoderAdapter (called
+  // from SpdyFramer) can more readily pass existing tests that expect decoding
+  // to stop if the headers alone indicate an error. Return false to stop
+  // decoding just after decoding the header, else return true to continue
+  // decoding.
+  // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+  // SpdyFramer's exact states.
+  virtual bool OnFrameHeader(const Http2FrameHeader& header) = 0;
+
+  //////////////////////////////////////////////////////////////////////////////
+
+  // Called once the common frame header has been decoded for a DATA frame,
+  // before examining the frame's payload, after which:
+  //   OnPadLength will be called if header.IsPadded() is true, i.e. if the
+  //     PADDED flag is set;
+  //   OnDataPayload will be called as the non-padding portion of the payload
+  //     is available until all of it has been provided;
+  //   OnPadding will be called if the frame is padded AND the Pad Length field
+  //     is greater than zero;
+  //   OnDataEnd will be called last. If the frame is unpadded and has no
+  //     payload, then this will be called immediately after OnDataStart.
+  virtual void OnDataStart(const Http2FrameHeader& header) = 0;
+
+  // Called when the next non-padding portion of a DATA frame's payload is
+  // received.
+  // |data| The start of |len| bytes of data.
+  // |len| The length of the data buffer. Maybe zero in some cases, which does
+  //       not mean anything special.
+  virtual void OnDataPayload(const char* data, size_t len) = 0;
+
+  // Called after an entire DATA frame has been received.
+  // If header.IsEndStream() == true, this is the last data for the stream.
+  virtual void OnDataEnd() = 0;
+
+  // Called once the common frame header has been decoded for a HEADERS frame,
+  // before examining the frame's payload, after which:
+  //   OnPadLength will be called if header.IsPadded() is true, i.e. if the
+  //     PADDED flag is set;
+  //   OnHeadersPriority will be called if header.HasPriority() is true, i.e. if
+  //     the frame has the PRIORITY flag;
+  //   OnHpackFragment as the remainder of the non-padding payload is available
+  //     until all if has been provided;
+  //   OnPadding will be called if the frame is padded AND the Pad Length field
+  //     is greater than zero;
+  //   OnHeadersEnd will be called last; If the frame is unpadded and has no
+  //     payload, then this will be called immediately after OnHeadersStart;
+  //     OnHeadersEnd indicates the end of the HPACK block only if the frame
+  //     header had the END_HEADERS flag set, else the END_HEADERS should be
+  //     looked for on a subsequent CONTINUATION frame.
+  virtual void OnHeadersStart(const Http2FrameHeader& header) = 0;
+
+  // Called when a HEADERS frame is received with the PRIORITY flag set and
+  // the priority fields have been decoded.
+  virtual void OnHeadersPriority(
+      const Http2PriorityFields& priority_fields) = 0;
+
+  // Called when a fragment (i.e. some or all of an HPACK Block) is received;
+  // this may be part of a HEADERS, PUSH_PROMISE or CONTINUATION frame.
+  // |data| The start of |len| bytes of data.
+  // |len| The length of the data buffer. Maybe zero in some cases, which does
+  //       not mean anything special, except that it simplified the decoder.
+  virtual void OnHpackFragment(const char* data, size_t len) = 0;
+
+  // Called after an entire HEADERS frame has been received. The frame is the
+  // end of the HEADERS if the END_HEADERS flag is set; else there should be
+  // CONTINUATION frames after this frame.
+  virtual void OnHeadersEnd() = 0;
+
+  // Called when an entire PRIORITY frame has been decoded.
+  virtual void OnPriorityFrame(const Http2FrameHeader& header,
+                               const Http2PriorityFields& priority_fields) = 0;
+
+  // Called once the common frame header has been decoded for a CONTINUATION
+  // frame, before examining the frame's payload, after which:
+  //   OnHpackFragment as the frame's payload is available until all of it
+  //     has been provided;
+  //   OnContinuationEnd will be called last; If the frame has no payload,
+  //     then this will be called immediately after OnContinuationStart;
+  //     the HPACK block is at an end if and only if the frame header passed
+  //     to OnContinuationStart had the END_HEADERS flag set.
+  virtual void OnContinuationStart(const Http2FrameHeader& header) = 0;
+
+  // Called after an entire CONTINUATION frame has been received. The frame is
+  // the end of the HEADERS if the END_HEADERS flag is set.
+  virtual void OnContinuationEnd() = 0;
+
+  // Called when Pad Length field has been read. Applies to DATA and HEADERS
+  // frames. For PUSH_PROMISE frames, the Pad Length + 1 is provided in the
+  // OnPushPromiseStart call as total_padding_length.
+  virtual void OnPadLength(size_t pad_length) = 0;
+
+  // Called when padding is skipped over.
+  virtual void OnPadding(const char* padding, size_t skipped_length) = 0;
+
+  // Called when an entire RST_STREAM frame has been decoded.
+  // This is the only callback for RST_STREAM frames.
+  virtual void OnRstStream(const Http2FrameHeader& header,
+                           Http2ErrorCode error_code) = 0;
+
+  // Called once the common frame header has been decoded for a SETTINGS frame
+  // without the ACK flag, before examining the frame's payload, after which:
+  //   OnSetting will be called in turn for each pair of settings parameter and
+  //     value found in the payload;
+  //   OnSettingsEnd will be called last; If the frame has no payload,
+  //     then this will be called immediately after OnSettingsStart.
+  // The frame header is passed so that the caller can check the stream_id,
+  // which should be zero, but that hasn't been checked by the decoder.
+  virtual void OnSettingsStart(const Http2FrameHeader& header) = 0;
+
+  // Called for each setting parameter and value within a SETTINGS frame.
+  virtual void OnSetting(const Http2SettingFields& setting_fields) = 0;
+
+  // Called after parsing the complete payload of SETTINGS frame (non-ACK).
+  virtual void OnSettingsEnd() = 0;
+
+  // Called when an entire SETTINGS frame, with the ACK flag, has been decoded.
+  virtual void OnSettingsAck(const Http2FrameHeader& header) = 0;
+
+  // Called just before starting to process the HPACK block of a PUSH_PROMISE
+  // frame. The Pad Length field has already been decoded at this point, so
+  // OnPadLength will not be called; note that total_padding_length is Pad
+  // Length + 1. After OnPushPromiseStart:
+  //   OnHpackFragment as the remainder of the non-padding payload is available
+  //     until all if has been provided;
+  //   OnPadding will be called if the frame is padded AND the Pad Length field
+  //     is greater than zero (i.e. total_padding_length > 1);
+  //   OnPushPromiseEnd will be called last; If the frame is unpadded and has no
+  //     payload, then this will be called immediately after OnPushPromiseStart.
+  virtual void OnPushPromiseStart(const Http2FrameHeader& header,
+                                  const Http2PushPromiseFields& promise,
+                                  size_t total_padding_length) = 0;
+
+  // Called after all of the HPACK block fragment and padding of a PUSH_PROMISE
+  // has been decoded and delivered to the listener. This call indicates the end
+  // of the HPACK block if and only if the frame header had the END_HEADERS flag
+  // set (i.e. header.IsEndHeaders() is true); otherwise the next block must be
+  // a CONTINUATION frame with the same stream id (not the same promised stream
+  // id).
+  virtual void OnPushPromiseEnd() = 0;
+
+  // Called when an entire PING frame, without the ACK flag, has been decoded.
+  virtual void OnPing(const Http2FrameHeader& header,
+                      const Http2PingFields& ping) = 0;
+
+  // Called when an entire PING frame, with the ACK flag, has been decoded.
+  virtual void OnPingAck(const Http2FrameHeader& header,
+                         const Http2PingFields& ping) = 0;
+
+  // Called after parsing a GOAWAY frame's header and fixed size fields, after
+  // which:
+  //   OnGoAwayOpaqueData will be called as opaque data of the payload becomes
+  //     available to the decoder, until all of it has been provided to the
+  //     listener;
+  //   OnGoAwayEnd will be called last, after all the opaque data has been
+  //     provided to the listener; if there is no opaque data, then OnGoAwayEnd
+  //     will be called immediately after OnGoAwayStart.
+  virtual void OnGoAwayStart(const Http2FrameHeader& header,
+                             const Http2GoAwayFields& goaway) = 0;
+
+  // Called when the next portion of a GOAWAY frame's payload is received.
+  // |data| The start of |len| bytes of opaque data.
+  // |len| The length of the opaque data buffer. Maybe zero in some cases,
+  //       which does not mean anything special.
+  virtual void OnGoAwayOpaqueData(const char* data, size_t len) = 0;
+
+  // Called after finishing decoding all of a GOAWAY frame.
+  virtual void OnGoAwayEnd() = 0;
+
+  // Called when an entire WINDOW_UPDATE frame has been decoded. The
+  // window_size_increment is required to be non-zero, but that has not been
+  // checked. If header.stream_id==0, the connection's flow control window is
+  // being increased, else the specified stream's flow control is being
+  // increased.
+  virtual void OnWindowUpdate(const Http2FrameHeader& header,
+                              uint32_t window_size_increment) = 0;
+
+  // Called when an ALTSVC frame header and origin length have been parsed.
+  // Either or both lengths may be zero. After OnAltSvcStart:
+  //   OnAltSvcOriginData will be called until all of the (optional) Origin
+  //     has been provided;
+  //   OnAltSvcValueData will be called until all of the Alt-Svc-Field-Value
+  //     has been provided;
+  //   OnAltSvcEnd will called last, after all of the origin and
+  //     Alt-Svc-Field-Value have been delivered to the listener.
+  virtual void OnAltSvcStart(const Http2FrameHeader& header,
+                             size_t origin_length,
+                             size_t value_length) = 0;
+
+  // Called when decoding the (optional) origin of an ALTSVC;
+  // the field is uninterpreted.
+  virtual void OnAltSvcOriginData(const char* data, size_t len) = 0;
+
+  // Called when decoding the Alt-Svc-Field-Value of an ALTSVC;
+  // the field is uninterpreted.
+  virtual void OnAltSvcValueData(const char* data, size_t len) = 0;
+
+  // Called after decoding all of a ALTSVC frame and providing to the listener
+  // via the above methods.
+  virtual void OnAltSvcEnd() = 0;
+
+  // Called when the common frame header has been decoded, but the frame type
+  // is unknown, after which:
+  //   OnUnknownPayload is called as the payload of the frame is provided to the
+  //     decoder, until all of the payload has been decoded;
+  //   OnUnknownEnd will called last, after the entire frame of the unknown type
+  //     has been decoded and provided to the listener.
+  virtual void OnUnknownStart(const Http2FrameHeader& header) = 0;
+
+  // Called when the payload of an unknown frame type is received.
+  // |data| A buffer containing the data received.
+  // |len| The length of the data buffer.
+  virtual void OnUnknownPayload(const char* data, size_t len) = 0;
+
+  // Called after decoding all of the payload of an unknown frame type.
+  virtual void OnUnknownEnd() = 0;
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Below here are events indicating a problem has been detected during
+  // decoding (i.e. the received frames are malformed in some way).
+
+  // Padding field (uint8) has a value that is too large (i.e. the amount of
+  // padding is greater than the remainder of the payload that isn't required).
+  // From RFC Section 6.1, DATA:
+  //     If the length of the padding is the length of the frame payload or
+  //     greater, the recipient MUST treat this as a connection error
+  //     (Section 5.4.1) of type PROTOCOL_ERROR.
+  // The same is true for HEADERS and PUSH_PROMISE.
+  virtual void OnPaddingTooLong(const Http2FrameHeader& header,
+                                size_t missing_length) = 0;
+
+  // Frame size error. Depending upon the effected frame, this may or may not
+  // require terminating the connection, though that is probably the best thing
+  // to do.
+  // From RFC Section 4.2, Frame Size:
+  //     An endpoint MUST send an error code of FRAME_SIZE_ERROR if a frame
+  //     exceeds the size defined in SETTINGS_MAX_FRAME_SIZE, exceeds any limit
+  //     defined for the frame type, or is too small to contain mandatory frame
+  //     data. A frame size error in a frame that could alter the state of the
+  //     the entire connection MUST be treated as a connection error
+  //     (Section 5.4.1); this includes any frame carrying a header block
+  //     (Section 4.3) (that is, HEADERS, PUSH_PROMISE, and CONTINUATION),
+  //     SETTINGS, and any frame with a stream identifier of 0.
+  virtual void OnFrameSizeError(const Http2FrameHeader& header) = 0;
+};
+
+// Do nothing for each call. Useful for ignoring a frame that is invalid.
+class Http2FrameDecoderNoOpListener : public Http2FrameDecoderListener {
+ public:
+  Http2FrameDecoderNoOpListener() {}
+  ~Http2FrameDecoderNoOpListener() 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 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 {}
+};
+
+static_assert(!std::is_abstract<Http2FrameDecoderNoOpListener>(),
+              "Http2FrameDecoderNoOpListener ought to be concrete.");
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_
diff --git a/http2/decoder/http2_frame_decoder_listener_test_util.cc b/http2/decoder/http2_frame_decoder_listener_test_util.cc
new file mode 100644
index 0000000..bc8cf1d
--- /dev/null
+++ b/http2/decoder/http2_frame_decoder_listener_test_util.cc
@@ -0,0 +1,488 @@
+// 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 "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.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::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) {
+  VLOG(1) << "OnFrameHeader: " << header;
+  if (wrapped_ != nullptr) {
+    return wrapped_->OnFrameHeader(header);
+  }
+  return true;
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataStart(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnDataStart: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnDataStart(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataPayload(const char* data,
+                                                     size_t len) {
+  VLOG(1) << "OnDataPayload: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnDataPayload(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataEnd() {
+  VLOG(1) << "OnDataEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnDataEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersStart(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnHeadersStart: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnHeadersStart(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersPriority(
+    const Http2PriorityFields& priority) {
+  VLOG(1) << "OnHeadersPriority: " << priority;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnHeadersPriority(priority);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHpackFragment(const char* data,
+                                                       size_t len) {
+  VLOG(1) << "OnHpackFragment: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnHpackFragment(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersEnd() {
+  VLOG(1) << "OnHeadersEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnHeadersEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityFrame(
+    const Http2FrameHeader& header,
+    const Http2PriorityFields& priority) {
+  VLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPriorityFrame(header, priority);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnContinuationStart(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnContinuationStart: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnContinuationStart(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnContinuationEnd() {
+  VLOG(1) << "OnContinuationEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnContinuationEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPadLength(size_t trailing_length) {
+  VLOG(1) << "OnPadLength: trailing_length=" << trailing_length;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPadLength(trailing_length);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPadding(const char* padding,
+                                                 size_t skipped_length) {
+  VLOG(1) << "OnPadding: skipped_length=" << skipped_length;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPadding(padding, skipped_length);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnRstStream(
+    const Http2FrameHeader& header,
+    Http2ErrorCode error_code) {
+  VLOG(1) << "OnRstStream: " << header << "; code=" << error_code;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnRstStream(header, error_code);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsStart(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnSettingsStart: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnSettingsStart(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSetting(
+    const Http2SettingFields& setting_fields) {
+  VLOG(1) << "OnSetting: " << setting_fields;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnSetting(setting_fields);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsEnd() {
+  VLOG(1) << "OnSettingsEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnSettingsEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsAck(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnSettingsAck: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnSettingsAck(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPushPromiseStart(
+    const Http2FrameHeader& header,
+    const Http2PushPromiseFields& promise,
+    size_t total_padding_length) {
+  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() {
+  VLOG(1) << "OnPushPromiseEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPushPromiseEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPing(const Http2FrameHeader& header,
+                                              const Http2PingFields& ping) {
+  VLOG(1) << "OnPing: " << header << "; ping: " << ping;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPing(header, ping);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPingAck(const Http2FrameHeader& header,
+                                                 const Http2PingFields& ping) {
+  VLOG(1) << "OnPingAck: " << header << "; ping: " << ping;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPingAck(header, ping);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayStart(
+    const Http2FrameHeader& header,
+    const Http2GoAwayFields& goaway) {
+  VLOG(1) << "OnGoAwayStart: " << header << "; goaway: " << goaway;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnGoAwayStart(header, goaway);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayOpaqueData(const char* data,
+                                                          size_t len) {
+  VLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnGoAwayOpaqueData(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayEnd() {
+  VLOG(1) << "OnGoAwayEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnGoAwayEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnWindowUpdate(
+    const Http2FrameHeader& header,
+    uint32_t increment) {
+  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) {
+  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) {
+  VLOG(1) << "OnAltSvcOriginData: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnAltSvcOriginData(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcValueData(const char* data,
+                                                         size_t len) {
+  VLOG(1) << "OnAltSvcValueData: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnAltSvcValueData(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcEnd() {
+  VLOG(1) << "OnAltSvcEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnAltSvcEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownStart(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnUnknownStart: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnUnknownStart(header);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownPayload(const char* data,
+                                                        size_t len) {
+  VLOG(1) << "OnUnknownPayload: len=" << len;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnUnknownPayload(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownEnd() {
+  VLOG(1) << "OnUnknownEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnUnknownEnd();
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPaddingTooLong(
+    const Http2FrameHeader& header,
+    size_t missing_length) {
+  VLOG(1) << "OnPaddingTooLong: " << header
+          << "; missing_length: " << missing_length;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPaddingTooLong(header, missing_length);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnFrameSizeError(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnFrameSizeError: " << header;
+  if (wrapped_ != nullptr) {
+    wrapped_->OnFrameSizeError(header);
+  }
+}
+
+}  // namespace http2
diff --git a/http2/decoder/http2_frame_decoder_listener_test_util.h b/http2/decoder/http2_frame_decoder_listener_test_util.h
new file mode 100644
index 0000000..d6e84ef
--- /dev/null
+++ b/http2/decoder/http2_frame_decoder_listener_test_util.h
@@ -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.
+
+#ifndef QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
+#define QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+// Fail if any of the methods are called. Allows a test to override only the
+// expected calls.
+class 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 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; }
+};
+
+// VLOG's all the calls it receives, and forwards those calls to an optional
+// listener.
+class 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 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_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
diff --git a/http2/decoder/http2_frame_decoder_test.cc b/http2/decoder/http2_frame_decoder_test.cc
new file mode 100644
index 0000000..172bd62
--- /dev/null
+++ b/http2/decoder/http2_frame_decoder_test.cc
@@ -0,0 +1,929 @@
+// 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 "net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h"
+
+// Tests of Http2FrameDecoder.
+
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+class Http2FrameDecoderPeer {
+ public:
+  static size_t remaining_total_payload(Http2FrameDecoder* decoder) {
+    return decoder->frame_decoder_state_.remaining_total_payload();
+  }
+};
+
+namespace {
+
+class Http2FrameDecoderTest : public RandomDecoderTest {
+ protected:
+  void SetUp() override {
+    // On any one run of this suite, we'll always choose the same value for
+    // use_default_reconstruct_ because the random seed is the same for each
+    // test case, but across runs the random seed changes.
+    use_default_reconstruct_ = Random().OneIn(2);
+  }
+
+  DecodeStatus StartDecoding(DecodeBuffer* db) override {
+    DVLOG(2) << "StartDecoding, db->Remaining=" << db->Remaining();
+    collector_.Reset();
+    PrepareDecoder();
+
+    DecodeStatus status = decoder_.DecodeFrame(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_;
+      if (status == DecodeStatus::kDecodeError) {
+        ConfirmDiscardsRemainingPayload();
+      }
+    }
+    return status;
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* db) override {
+    DVLOG(2) << "ResumeDecoding, db->Remaining=" << db->Remaining();
+    DecodeStatus status = decoder_.DecodeFrame(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_;
+      if (status == DecodeStatus::kDecodeError) {
+        ConfirmDiscardsRemainingPayload();
+      }
+    }
+    return status;
+  }
+
+  // When an error is returned, the decoder is in state kDiscardPayload, and
+  // stays there until the remaining bytes of the frame's payload have been
+  // skipped over. There are no callbacks for this situation.
+  void ConfirmDiscardsRemainingPayload() {
+    ASSERT_TRUE(decoder_.IsDiscardingPayload());
+    size_t remaining =
+        Http2FrameDecoderPeer::remaining_total_payload(&decoder_);
+    // The decoder will discard the remaining bytes, but not go beyond that,
+    // which these conditions verify.
+    size_t extra = 10;
+    Http2String junk(remaining + extra, '0');
+    DecodeBuffer tmp(junk);
+    EXPECT_EQ(DecodeStatus::kDecodeDone, decoder_.DecodeFrame(&tmp));
+    EXPECT_EQ(remaining, tmp.Offset());
+    EXPECT_EQ(extra, tmp.Remaining());
+    EXPECT_FALSE(decoder_.IsDiscardingPayload());
+  }
+
+  void PrepareDecoder() {
+    // Save and restore the maximum_payload_size when reconstructing
+    // the decoder.
+    size_t maximum_payload_size = decoder_.maximum_payload_size();
+
+    // Alternate which constructor is used.
+    if (use_default_reconstruct_) {
+      Http2DefaultReconstructObject(&decoder_, RandomPtr());
+      decoder_.set_listener(&collector_);
+    } else {
+      Http2ReconstructObject(&decoder_, RandomPtr(), &collector_);
+    }
+    decoder_.set_maximum_payload_size(maximum_payload_size);
+
+    use_default_reconstruct_ = !use_default_reconstruct_;
+  }
+
+  void ResetDecodeSpeedCounters() {
+    fast_decode_count_ = 0;
+    slow_decode_count_ = 0;
+  }
+
+  AssertionResult VerifyCollected(const FrameParts& expected) {
+    VERIFY_FALSE(collector_.IsInProgress());
+    VERIFY_EQ(1u, collector_.size());
+    VERIFY_AND_RETURN_SUCCESS(expected.VerifyEquals(*collector_.frame(0)));
+  }
+
+  AssertionResult DecodePayloadAndValidateSeveralWays(Http2StringPiece payload,
+                                                      Validator validator) {
+    DecodeBuffer db(payload);
+    bool start_decoding_requires_non_empty = false;
+    return DecodeAndValidateSeveralWays(&db, start_decoding_requires_non_empty,
+                                        validator);
+  }
+
+  // Decode one frame's payload and confirm that the listener recorded the
+  // expected FrameParts instance, and only one 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(
+      Http2StringPiece payload,
+      const FrameParts& expected) {
+    auto validator = [&expected, this](const DecodeBuffer& input,
+                                       DecodeStatus status) -> AssertionResult {
+      VERIFY_EQ(status, DecodeStatus::kDecodeDone);
+      VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected));
+    };
+    ResetDecodeSpeedCounters();
+    VERIFY_SUCCESS(DecodePayloadAndValidateSeveralWays(
+        payload, ValidateDoneAndEmpty(validator)));
+    VERIFY_GT(fast_decode_count_, 0u);
+    VERIFY_GT(slow_decode_count_, 0u);
+
+    // Repeat with more input; it should stop without reading that input.
+    Http2String next_frame = Random().RandString(10);
+    Http2String input(payload.data(), payload.size());
+    input += next_frame;
+
+    ResetDecodeSpeedCounters();
+    VERIFY_SUCCESS(DecodePayloadAndValidateSeveralWays(
+        payload, ValidateDoneAndOffset(payload.size(), validator)));
+    VERIFY_GT(fast_decode_count_, 0u);
+    VERIFY_GT(slow_decode_count_, 0u);
+
+    return AssertionSuccess();
+  }
+
+  template <size_t N>
+  AssertionResult DecodePayloadAndValidateSeveralWays(
+      const char (&buf)[N],
+      const FrameParts& expected) {
+    return DecodePayloadAndValidateSeveralWays(Http2StringPiece(buf, N),
+                                               expected);
+  }
+
+  template <size_t N>
+  AssertionResult DecodePayloadAndValidateSeveralWays(
+      const char (&buf)[N],
+      const Http2FrameHeader& header) {
+    return DecodePayloadAndValidateSeveralWays(Http2StringPiece(buf, N),
+                                               FrameParts(header));
+  }
+
+  template <size_t N>
+  AssertionResult DecodePayloadExpectingError(const char (&buf)[N],
+                                              const FrameParts& expected) {
+    auto validator = [&expected, this](const DecodeBuffer& input,
+                                       DecodeStatus status) -> AssertionResult {
+      VERIFY_EQ(status, DecodeStatus::kDecodeError);
+      VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected));
+    };
+    ResetDecodeSpeedCounters();
+    EXPECT_TRUE(
+        DecodePayloadAndValidateSeveralWays(ToStringPiece(buf), validator));
+    EXPECT_GT(fast_decode_count_, 0u);
+    EXPECT_GT(slow_decode_count_, 0u);
+    return AssertionSuccess();
+  }
+
+  template <size_t N>
+  AssertionResult DecodePayloadExpectingFrameSizeError(const char (&buf)[N],
+                                                       FrameParts expected) {
+    expected.SetHasFrameSizeError(true);
+    VERIFY_AND_RETURN_SUCCESS(DecodePayloadExpectingError(buf, expected));
+  }
+
+  template <size_t N>
+  AssertionResult DecodePayloadExpectingFrameSizeError(
+      const char (&buf)[N],
+      const Http2FrameHeader& header) {
+    return DecodePayloadExpectingFrameSizeError(buf, FrameParts(header));
+  }
+
+  // Count of payloads that are fully decoded by StartDecodingPayload or for
+  // which an error was detected by StartDecodingPayload.
+  size_t fast_decode_count_ = 0;
+
+  // Count of payloads that required calling ResumeDecodingPayload in order to
+  // decode completely, or for which an error was detected by
+  // ResumeDecodingPayload.
+  size_t slow_decode_count_ = 0;
+
+  FramePartsCollectorListener collector_;
+  Http2FrameDecoder decoder_;
+  bool use_default_reconstruct_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Tests that pass the minimum allowed size for the frame type, which is often
+// empty. The tests are in order by frame type value (i.e. 0 for DATA frames).
+
+TEST_F(Http2FrameDecoderTest, DataEmpty) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x00',  // Payload length: 0
+      '\x00',                  // DATA
+      '\x00',                  // Flags: none
+      '\x00', '\x00', '\x00',
+      '\x00',  // Stream ID: 0 (invalid but unchecked here)
+  };
+  Http2FrameHeader header(0, Http2FrameType::DATA, 0, 0);
+  FrameParts expected(header, "");
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeadersEmpty) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x00',          // Payload length: 0
+      '\x01',                          // HEADERS
+      '\x00',                          // Flags: none
+      '\x00', '\x00', '\x00', '\x01',  // Stream ID: 0  (REQUIRES ID)
+  };
+  Http2FrameHeader header(0, Http2FrameType::HEADERS, 0, 1);
+  FrameParts expected(header, "");
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, Priority) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x05',          // Length: 5
+      '\x02',                          //   Type: PRIORITY
+      '\x00',                          //  Flags: none
+      '\x00', '\x00', '\x00', '\x02',  // Stream: 2
+      '\x80', '\x00', '\x00', '\x01',  // Parent: 1 (Exclusive)
+      '\x10',                          // Weight: 17
+  };
+  Http2FrameHeader header(5, Http2FrameType::PRIORITY, 0, 2);
+  FrameParts expected(header);
+  expected.SetOptPriority(Http2PriorityFields(1, 17, true));
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, RstStream) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x04',          // Length: 4
+      '\x03',                          //   Type: RST_STREAM
+      '\x00',                          //  Flags: none
+      '\x00', '\x00', '\x00', '\x01',  // Stream: 1
+      '\x00', '\x00', '\x00', '\x01',  //  Error: PROTOCOL_ERROR
+  };
+  Http2FrameHeader header(4, Http2FrameType::RST_STREAM, 0, 1);
+  FrameParts expected(header);
+  expected.SetOptRstStreamErrorCode(Http2ErrorCode::PROTOCOL_ERROR);
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, SettingsEmpty) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x00',          // Length: 0
+      '\x04',                          //   Type: SETTINGS
+      '\x00',                          //  Flags: none
+      '\x00', '\x00', '\x00', '\x01',  // Stream: 1 (invalid but unchecked here)
+  };
+  Http2FrameHeader header(0, Http2FrameType::SETTINGS, 0, 1);
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, SettingsAck) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x00',          //   Length: 6
+      '\x04',                          //     Type: SETTINGS
+      '\x01',                          //    Flags: ACK
+      '\x00', '\x00', '\x00', '\x00',  //   Stream: 0
+  };
+  Http2FrameHeader header(0, Http2FrameType::SETTINGS, Http2FrameFlag::ACK, 0);
+  FrameParts expected(header);
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, PushPromiseMinimal) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x04',  // Payload length: 4
+      '\x05',                  // PUSH_PROMISE
+      '\x04',                  // Flags: END_HEADERS
+      '\x00', '\x00', '\x00',
+      '\x02',  //   Stream: 2 (invalid but unchecked here)
+      '\x00', '\x00', '\x00',
+      '\x01',  // Promised: 1 (invalid but unchecked here)
+  };
+  Http2FrameHeader header(4, Http2FrameType::PUSH_PROMISE,
+                          Http2FrameFlag::END_HEADERS, 2);
+  FrameParts expected(header, "");
+  expected.SetOptPushPromise(Http2PushPromiseFields{1});
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, Ping) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x08',          //   Length: 8
+      '\x06',                          //     Type: PING
+      '\xfe',                          //    Flags: no valid flags
+      '\x00', '\x00', '\x00', '\x00',  //   Stream: 0
+      's',    'o',    'm',    'e',     // "some"
+      'd',    'a',    't',    'a',     // "data"
+  };
+  Http2FrameHeader header(8, Http2FrameType::PING, 0, 0);
+  FrameParts expected(header);
+  expected.SetOptPing(
+      Http2PingFields{{'s', 'o', 'm', 'e', 'd', 'a', 't', 'a'}});
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, PingAck) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x08',          //   Length: 8
+      '\x06',                          //     Type: PING
+      '\xff',                          //    Flags: ACK (plus all invalid flags)
+      '\x00', '\x00', '\x00', '\x00',  //   Stream: 0
+      's',    'o',    'm',    'e',     // "some"
+      'd',    'a',    't',    'a',     // "data"
+  };
+  Http2FrameHeader header(8, Http2FrameType::PING, Http2FrameFlag::ACK, 0);
+  FrameParts expected(header);
+  expected.SetOptPing(
+      Http2PingFields{{'s', 'o', 'm', 'e', 'd', 'a', 't', 'a'}});
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, GoAwayMinimal) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x08',          // Length: 8 (no opaque data)
+      '\x07',                          //   Type: GOAWAY
+      '\xff',                          //  Flags: 0xff (no valid flags)
+      '\x00', '\x00', '\x00', '\x01',  // Stream: 1 (invalid but unchecked here)
+      '\x80', '\x00', '\x00', '\xff',  //   Last: 255 (plus R bit)
+      '\x00', '\x00', '\x00', '\x09',  //  Error: COMPRESSION_ERROR
+  };
+  Http2FrameHeader header(8, Http2FrameType::GOAWAY, 0, 1);
+  FrameParts expected(header);
+  expected.SetOptGoaway(
+      Http2GoAwayFields(255, Http2ErrorCode::COMPRESSION_ERROR));
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, WindowUpdate) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x04',          // Length: 4
+      '\x08',                          //   Type: WINDOW_UPDATE
+      '\x0f',                          //  Flags: 0xff (no valid flags)
+      '\x00', '\x00', '\x00', '\x01',  // Stream: 1
+      '\x80', '\x00', '\x04', '\x00',  //   Incr: 1024 (plus R bit)
+  };
+  Http2FrameHeader header(4, Http2FrameType::WINDOW_UPDATE, 0, 1);
+  FrameParts expected(header);
+  expected.SetOptWindowUpdateIncrement(1024);
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, ContinuationEmpty) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x00',  // Payload length: 0
+      '\x09',                  // CONTINUATION
+      '\x00',                  // Flags: none
+      '\x00', '\x00', '\x00',
+      '\x00',  // Stream ID: 0 (invalid but unchecked here)
+  };
+  Http2FrameHeader header(0, Http2FrameType::CONTINUATION, 0, 0);
+  FrameParts expected(header);
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, AltSvcMinimal) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x02',  // Payload length: 2
+      '\x0a',                  // ALTSVC
+      '\xff',                  // Flags: none (plus 0xff)
+      '\x00', '\x00', '\x00',
+      '\x00',          // Stream ID: 0 (invalid but unchecked here)
+      '\x00', '\x00',  // Origin Length: 0
+  };
+  Http2FrameHeader header(2, Http2FrameType::ALTSVC, 0, 0);
+  FrameParts expected(header);
+  expected.SetOptAltsvcOriginLength(0);
+  expected.SetOptAltsvcValueLength(0);
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, UnknownEmpty) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x00',          // Payload length: 0
+      '\x20',                          // 32 (unknown)
+      '\xff',                          // Flags: all
+      '\x00', '\x00', '\x00', '\x00',  // Stream ID: 0
+  };
+  Http2FrameHeader header(0, static_cast<Http2FrameType>(32), 0xff, 0);
+  FrameParts expected(header);
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Tests of longer payloads, for those frame types that allow longer payloads.
+
+TEST_F(Http2FrameDecoderTest, DataPayload) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x03',          // Payload length: 7
+      '\x00',                          // DATA
+      '\x80',                          // Flags: 0x80
+      '\x00', '\x00', '\x02', '\x02',  // Stream ID: 514
+      'a',    'b',    'c',             // Data
+  };
+  Http2FrameHeader header(3, Http2FrameType::DATA, 0, 514);
+  FrameParts expected(header, "abc");
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeadersPayload) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x03',          // Payload length: 3
+      '\x01',                          // HEADERS
+      '\x05',                          // Flags: END_STREAM | END_HEADERS
+      '\x00', '\x00', '\x00', '\x02',  // Stream ID: 0  (REQUIRES ID)
+      'a',    'b',    'c',  // HPACK fragment (doesn't have to be valid)
+  };
+  Http2FrameHeader header(
+      3, Http2FrameType::HEADERS,
+      Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS, 2);
+  FrameParts expected(header, "abc");
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeadersPriority) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x05',          // Payload length: 5
+      '\x01',                          // HEADERS
+      '\x20',                          // Flags: PRIORITY
+      '\x00', '\x00', '\x00', '\x02',  // Stream ID: 0  (REQUIRES ID)
+      '\x00', '\x00', '\x00', '\x01',  // Parent: 1 (Not Exclusive)
+      '\xff',                          // Weight: 256
+  };
+  Http2FrameHeader header(5, Http2FrameType::HEADERS, Http2FrameFlag::PRIORITY,
+                          2);
+  FrameParts expected(header);
+  expected.SetOptPriority(Http2PriorityFields(1, 256, false));
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, Settings) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x0c',          // Length: 12
+      '\x04',                          //   Type: SETTINGS
+      '\x00',                          //  Flags: none
+      '\x00', '\x00', '\x00', '\x00',  // Stream: 0
+      '\x00', '\x04',                  //  Param: INITIAL_WINDOW_SIZE
+      '\x0a', '\x0b', '\x0c', '\x0d',  //  Value: 168496141
+      '\x00', '\x02',                  //  Param: ENABLE_PUSH
+      '\x00', '\x00', '\x00', '\x03',  //  Value: 3 (invalid but unchecked here)
+  };
+  Http2FrameHeader header(12, Http2FrameType::SETTINGS, 0, 0);
+  FrameParts expected(header);
+  expected.AppendSetting(Http2SettingFields(
+      Http2SettingsParameter::INITIAL_WINDOW_SIZE, 168496141));
+  expected.AppendSetting(
+      Http2SettingFields(Http2SettingsParameter::ENABLE_PUSH, 3));
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, PushPromisePayload) {
+  const char kFrameData[] = {
+      '\x00', '\x00', 7,               // Payload length: 7
+      '\x05',                          // PUSH_PROMISE
+      '\x04',                          // Flags: END_HEADERS
+      '\x00', '\x00', '\x00', '\xff',  // Stream ID: 255
+      '\x00', '\x00', '\x01', '\x00',  // Promised: 256
+      'a',    'b',    'c',  // HPACK fragment (doesn't have to be valid)
+  };
+  Http2FrameHeader header(7, Http2FrameType::PUSH_PROMISE,
+                          Http2FrameFlag::END_HEADERS, 255);
+  FrameParts expected(header, "abc");
+  expected.SetOptPushPromise(Http2PushPromiseFields{256});
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, GoAwayOpaqueData) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x0e',          // Length: 14
+      '\x07',                          //   Type: GOAWAY
+      '\xff',                          //  Flags: 0xff (no valid flags)
+      '\x80', '\x00', '\x00', '\x00',  // Stream: 0 (plus R bit)
+      '\x00', '\x00', '\x01', '\x00',  //   Last: 256
+      '\x00', '\x00', '\x00', '\x03',  //  Error: FLOW_CONTROL_ERROR
+      'o',    'p',    'a',    'q',    'u', 'e',
+  };
+  Http2FrameHeader header(14, Http2FrameType::GOAWAY, 0, 0);
+  FrameParts expected(header, "opaque");
+  expected.SetOptGoaway(
+      Http2GoAwayFields(256, Http2ErrorCode::FLOW_CONTROL_ERROR));
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, ContinuationPayload) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x03',          // Payload length: 3
+      '\x09',                          // CONTINUATION
+      '\xff',                          // Flags: END_HEADERS | 0xfb
+      '\x00', '\x00', '\x00', '\x02',  // Stream ID: 2
+      'a',    'b',    'c',             // Data
+  };
+  Http2FrameHeader header(3, Http2FrameType::CONTINUATION,
+                          Http2FrameFlag::END_HEADERS, 2);
+  FrameParts expected(header, "abc");
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, AltSvcPayload) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x08',          // Payload length: 3
+      '\x0a',                          // ALTSVC
+      '\x00',                          // Flags: none
+      '\x00', '\x00', '\x00', '\x02',  // Stream ID: 2
+      '\x00', '\x03',                  // Origin Length: 0
+      'a',    'b',    'c',             // Origin
+      'd',    'e',    'f',             // Value
+  };
+  Http2FrameHeader header(8, Http2FrameType::ALTSVC, 0, 2);
+  FrameParts expected(header);
+  expected.SetAltSvcExpected("abc", "def");
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, UnknownPayload) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x03',          // Payload length: 3
+      '\x30',                          // 48 (unknown)
+      '\x00',                          // Flags: none
+      '\x00', '\x00', '\x00', '\x02',  // Stream ID: 2
+      'a',    'b',    'c',             // Payload
+  };
+  Http2FrameHeader header(3, static_cast<Http2FrameType>(48), 0, 2);
+  FrameParts expected(header, "abc");
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Tests of padded payloads, for those frame types that allow padding.
+
+TEST_F(Http2FrameDecoderTest, DataPayloadAndPadding) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x07',          // Payload length: 7
+      '\x00',                          // DATA
+      '\x09',                          // Flags: END_STREAM | PADDED
+      '\x00', '\x00', '\x00', '\x02',  // Stream ID: 0  (REQUIRES ID)
+      '\x03',                          // Pad Len
+      'a',    'b',    'c',             // Data
+      '\x00', '\x00', '\x00',          // Padding
+  };
+  Http2FrameHeader header(7, Http2FrameType::DATA,
+                          Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED,
+                          2);
+  size_t total_pad_length = 4;  // Including the Pad Length field.
+  FrameParts expected(header, "abc", total_pad_length);
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeadersPayloadAndPadding) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x07',          // Payload length: 7
+      '\x01',                          // HEADERS
+      '\x08',                          // Flags: PADDED
+      '\x00', '\x00', '\x00', '\x02',  // Stream ID: 0  (REQUIRES ID)
+      '\x03',                          // Pad Len
+      'a',    'b',    'c',     // HPACK fragment (doesn't have to be valid)
+      '\x00', '\x00', '\x00',  // Padding
+  };
+  Http2FrameHeader header(7, Http2FrameType::HEADERS, Http2FrameFlag::PADDED,
+                          2);
+  size_t total_pad_length = 4;  // Including the Pad Length field.
+  FrameParts expected(header, "abc", total_pad_length);
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeadersPayloadPriorityAndPadding) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x0c',          // Payload length: 12
+      '\x01',                          // HEADERS
+      '\xff',                          // Flags: all, including undefined
+      '\x00', '\x00', '\x00', '\x02',  // Stream ID: 0  (REQUIRES ID)
+      '\x03',                          // Pad Len
+      '\x80', '\x00', '\x00', '\x01',  // Parent: 1 (Exclusive)
+      '\x10',                          // Weight: 17
+      'a',    'b',    'c',     // HPACK fragment (doesn't have to be valid)
+      '\x00', '\x00', '\x00',  // Padding
+  };
+  Http2FrameHeader header(12, Http2FrameType::HEADERS,
+                          Http2FrameFlag::END_STREAM |
+                              Http2FrameFlag::END_HEADERS |
+                              Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY,
+                          2);
+  size_t total_pad_length = 4;  // Including the Pad Length field.
+  FrameParts expected(header, "abc", total_pad_length);
+  expected.SetOptPriority(Http2PriorityFields(1, 17, true));
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, PushPromisePayloadAndPadding) {
+  const char kFrameData[] = {
+      '\x00', '\x00', 11,              // Payload length: 11
+      '\x05',                          // PUSH_PROMISE
+      '\xff',                          // Flags: END_HEADERS | PADDED | 0xf3
+      '\x00', '\x00', '\x00', '\x01',  // Stream ID: 1
+      '\x03',                          // Pad Len
+      '\x00', '\x00', '\x00', '\x02',  // Promised: 2
+      'a',    'b',    'c',     // HPACK fragment (doesn't have to be valid)
+      '\x00', '\x00', '\x00',  // Padding
+  };
+  Http2FrameHeader header(11, Http2FrameType::PUSH_PROMISE,
+                          Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED,
+                          1);
+  size_t total_pad_length = 4;  // Including the Pad Length field.
+  FrameParts expected(header, "abc", total_pad_length);
+  expected.SetOptPushPromise(Http2PushPromiseFields{2});
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Payload too short errors.
+
+TEST_F(Http2FrameDecoderTest, DataMissingPadLengthField) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x00',          // Payload length: 0
+      '\x00',                          // DATA
+      '\x08',                          // Flags: PADDED
+      '\x00', '\x00', '\x00', '\x01',  // Stream ID: 1
+  };
+  Http2FrameHeader header(0, Http2FrameType::DATA, Http2FrameFlag::PADDED, 1);
+  FrameParts expected(header);
+  expected.SetOptMissingLength(1);
+  EXPECT_TRUE(DecodePayloadExpectingError(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeaderPaddingTooLong) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x02',          // Payload length: 0
+      '\x01',                          // HEADERS
+      '\x08',                          // Flags: PADDED
+      '\x00', '\x01', '\x00', '\x00',  // Stream ID: 65536
+      '\xff',                          // Pad Len: 255
+      '\x00',                          // Only one byte of padding
+  };
+  Http2FrameHeader header(2, Http2FrameType::HEADERS, Http2FrameFlag::PADDED,
+                          65536);
+  FrameParts expected(header);
+  expected.SetOptMissingLength(254);
+  EXPECT_TRUE(DecodePayloadExpectingError(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeaderMissingPriority) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x04',          // Payload length: 0
+      '\x01',                          // HEADERS
+      '\x20',                          // Flags: PRIORITY
+      '\x00', '\x01', '\x00', '\x00',  // Stream ID: 65536
+      '\x00', '\x00', '\x00', '\x00',  // Priority (truncated)
+  };
+  Http2FrameHeader header(4, Http2FrameType::HEADERS, Http2FrameFlag::PRIORITY,
+                          65536);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, PriorityTooShort) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x04',          // Length: 5
+      '\x02',                          //   Type: PRIORITY
+      '\x00',                          //  Flags: none
+      '\x00', '\x00', '\x00', '\x02',  // Stream: 2
+      '\x80', '\x00', '\x00', '\x01',  // Parent: 1 (Exclusive)
+  };
+  Http2FrameHeader header(4, Http2FrameType::PRIORITY, 0, 2);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, RstStreamTooShort) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x03',          // Length: 4
+      '\x03',                          //   Type: RST_STREAM
+      '\x00',                          //  Flags: none
+      '\x00', '\x00', '\x00', '\x01',  // Stream: 1
+      '\x00', '\x00', '\x00',          //  Truncated
+  };
+  Http2FrameHeader header(3, Http2FrameType::RST_STREAM, 0, 1);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+// SETTINGS frames must a multiple of 6 bytes long, so an 9 byte payload is
+// invalid.
+TEST_F(Http2FrameDecoderTest, SettingsWrongSize) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x09',          // Length: 2
+      '\x04',                          //   Type: SETTINGS
+      '\x00',                          //  Flags: none
+      '\x00', '\x00', '\x00', '\x00',  // Stream: 0
+      '\x00', '\x02',                  //  Param: ENABLE_PUSH
+      '\x00', '\x00', '\x00', '\x03',  //  Value: 1
+      '\x00', '\x04',                  //  Param: INITIAL_WINDOW_SIZE
+      '\x00',                          //  Value: Truncated
+  };
+  Http2FrameHeader header(9, Http2FrameType::SETTINGS, 0, 0);
+  FrameParts expected(header);
+  expected.AppendSetting(
+      Http2SettingFields(Http2SettingsParameter::ENABLE_PUSH, 3));
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, PushPromiseTooShort) {
+  const char kFrameData[] = {
+      '\x00', '\x00', 3,               // Payload length: 3
+      '\x05',                          // PUSH_PROMISE
+      '\x00',                          // Flags: none
+      '\x00', '\x00', '\x00', '\x01',  // Stream ID: 1
+      '\x00', '\x00', '\x00',          // Truncated promise id
+  };
+  Http2FrameHeader header(3, Http2FrameType::PUSH_PROMISE, 0, 1);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, PushPromisePaddedTruncatedPromise) {
+  const char kFrameData[] = {
+      '\x00', '\x00', 4,               // Payload length: 4
+      '\x05',                          // PUSH_PROMISE
+      '\x08',                          // Flags: PADDED
+      '\x00', '\x00', '\x00', '\x01',  // Stream ID: 1
+      '\x00',                          // Pad Len
+      '\x00', '\x00', '\x00',          // Truncated promise id
+  };
+  Http2FrameHeader header(4, Http2FrameType::PUSH_PROMISE,
+                          Http2FrameFlag::PADDED, 1);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, PingTooShort) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x07',          //   Length: 8
+      '\x06',                          //     Type: PING
+      '\xfe',                          //    Flags: no valid flags
+      '\x00', '\x00', '\x00', '\x00',  //   Stream: 0
+      's',    'o',    'm',    'e',     // "some"
+      'd',    'a',    't',             // Too little
+  };
+  Http2FrameHeader header(7, Http2FrameType::PING, 0, 0);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, GoAwayTooShort) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x00',          // Length: 0
+      '\x07',                          //   Type: GOAWAY
+      '\xff',                          //  Flags: 0xff (no valid flags)
+      '\x00', '\x00', '\x00', '\x00',  // Stream: 0
+  };
+  Http2FrameHeader header(0, Http2FrameType::GOAWAY, 0, 0);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, WindowUpdateTooShort) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x03',          // Length: 3
+      '\x08',                          //   Type: WINDOW_UPDATE
+      '\x0f',                          //  Flags: 0xff (no valid flags)
+      '\x00', '\x00', '\x00', '\x01',  // Stream: 1
+      '\x80', '\x00', '\x04',          // Truncated
+  };
+  Http2FrameHeader header(3, Http2FrameType::WINDOW_UPDATE, 0, 1);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, AltSvcTruncatedOriginLength) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x01',          // Payload length: 3
+      '\x0a',                          // ALTSVC
+      '\x00',                          // Flags: none
+      '\x00', '\x00', '\x00', '\x02',  // Stream ID: 2
+      '\x00',                          // Origin Length: truncated
+  };
+  Http2FrameHeader header(1, Http2FrameType::ALTSVC, 0, 2);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, AltSvcTruncatedOrigin) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x05',          // Payload length: 3
+      '\x0a',                          // ALTSVC
+      '\x00',                          // Flags: none
+      '\x00', '\x00', '\x00', '\x02',  // Stream ID: 2
+      '\x00', '\x04',                  // Origin Length: 4 (too long)
+      'a',    'b',    'c',             // Origin
+  };
+  Http2FrameHeader header(5, Http2FrameType::ALTSVC, 0, 2);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Payload too long errors.
+
+// The decoder calls the listener's OnFrameSizeError method if the frame's
+// payload is longer than the currently configured maximum payload size.
+TEST_F(Http2FrameDecoderTest, BeyondMaximum) {
+  decoder_.set_maximum_payload_size(2);
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x07',          // Payload length: 7
+      '\x00',                          // DATA
+      '\x09',                          // Flags: END_STREAM | PADDED
+      '\x00', '\x00', '\x00', '\x02',  // Stream ID: 0  (REQUIRES ID)
+      '\x03',                          // Pad Len
+      'a',    'b',    'c',             // Data
+      '\x00', '\x00', '\x00',          // Padding
+  };
+  Http2FrameHeader header(7, Http2FrameType::DATA,
+                          Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED,
+                          2);
+  FrameParts expected(header);
+  expected.SetHasFrameSizeError(true);
+  auto validator = [&expected, this](const DecodeBuffer& input,
+                                     DecodeStatus status) -> AssertionResult {
+    VERIFY_EQ(status, DecodeStatus::kDecodeError);
+    // The decoder detects this error after decoding the header, and without
+    // trying to decode the payload.
+    VERIFY_EQ(input.Offset(), Http2FrameHeader::EncodedSize());
+    VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected));
+  };
+  ResetDecodeSpeedCounters();
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(ToStringPiece(kFrameData),
+                                                  validator));
+  EXPECT_GT(fast_decode_count_, 0u);
+  EXPECT_GT(slow_decode_count_, 0u);
+}
+
+TEST_F(Http2FrameDecoderTest, PriorityTooLong) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x06',          // Length: 5
+      '\x02',                          //   Type: PRIORITY
+      '\x00',                          //  Flags: none
+      '\x00', '\x00', '\x00', '\x02',  // Stream: 2
+      '\x80', '\x00', '\x00', '\x01',  // Parent: 1 (Exclusive)
+      '\x10',                          // Weight: 17
+      '\x00',                          // Too much
+  };
+  Http2FrameHeader header(6, Http2FrameType::PRIORITY, 0, 2);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, RstStreamTooLong) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x05',          // Length: 4
+      '\x03',                          //   Type: RST_STREAM
+      '\x00',                          //  Flags: none
+      '\x00', '\x00', '\x00', '\x01',  // Stream: 1
+      '\x00', '\x00', '\x00', '\x01',  //  Error: PROTOCOL_ERROR
+      '\x00',                          // Too much
+  };
+  Http2FrameHeader header(5, Http2FrameType::RST_STREAM, 0, 1);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, SettingsAckTooLong) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x06',          //   Length: 6
+      '\x04',                          //     Type: SETTINGS
+      '\x01',                          //    Flags: ACK
+      '\x00', '\x00', '\x00', '\x00',  //   Stream: 0
+      '\x00', '\x00',                  //   Extra
+      '\x00', '\x00', '\x00', '\x00',  //   Extra
+  };
+  Http2FrameHeader header(6, Http2FrameType::SETTINGS, Http2FrameFlag::ACK, 0);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, PingAckTooLong) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x09',          //   Length: 8
+      '\x06',                          //     Type: PING
+      '\xff',                          //    Flags: ACK | 0xfe
+      '\x00', '\x00', '\x00', '\x00',  //   Stream: 0
+      's',    'o',    'm',    'e',     // "some"
+      'd',    'a',    't',    'a',     // "data"
+      '\x00',                          // Too much
+  };
+  Http2FrameHeader header(9, Http2FrameType::PING, Http2FrameFlag::ACK, 0);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, WindowUpdateTooLong) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x05',          // Length: 5
+      '\x08',                          //   Type: WINDOW_UPDATE
+      '\x0f',                          //  Flags: 0xff (no valid flags)
+      '\x00', '\x00', '\x00', '\x01',  // Stream: 1
+      '\x80', '\x00', '\x04', '\x00',  //   Incr: 1024 (plus R bit)
+      '\x00',                          // Too much
+  };
+  Http2FrameHeader header(5, Http2FrameType::WINDOW_UPDATE, 0, 1);
+  EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/http2_structure_decoder.cc b/http2/decoder/http2_structure_decoder.cc
new file mode 100644
index 0000000..e4f2c1a
--- /dev/null
+++ b/http2/decoder/http2_structure_decoder.cc
@@ -0,0 +1,90 @@
+// 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 "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h"
+
+#include <algorithm>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+
+namespace http2 {
+
+// Below we have some defensive coding: if we somehow run off the end, don't
+// overwrite lots of memory. Note that most of this decoder is not defensive
+// against bugs in the decoder, only against malicious encoders, but since
+// we're copying memory into a buffer here, let's make sure we don't allow a
+// small mistake to grow larger. The decoder will get stuck if we hit the
+// HTTP2_BUG conditions, but shouldn't corrupt memory.
+
+uint32_t Http2StructureDecoder::IncompleteStart(DecodeBuffer* db,
+                                                uint32_t target_size) {
+  if (target_size > sizeof buffer_) {
+    HTTP2_BUG << "target_size too large for buffer: " << target_size;
+    return 0;
+  }
+  const uint32_t num_to_copy = db->MinLengthRemaining(target_size);
+  memcpy(buffer_, db->cursor(), num_to_copy);
+  offset_ = num_to_copy;
+  db->AdvanceCursor(num_to_copy);
+  return num_to_copy;
+}
+
+DecodeStatus Http2StructureDecoder::IncompleteStart(DecodeBuffer* db,
+                                                    uint32_t* remaining_payload,
+                                                    uint32_t target_size) {
+  DVLOG(1) << "IncompleteStart@" << this
+           << ": *remaining_payload=" << *remaining_payload
+           << "; target_size=" << target_size
+           << "; db->Remaining=" << db->Remaining();
+  *remaining_payload -=
+      IncompleteStart(db, std::min(target_size, *remaining_payload));
+  if (*remaining_payload > 0 && db->Empty()) {
+    return DecodeStatus::kDecodeInProgress;
+  }
+  DVLOG(1) << "IncompleteStart: kDecodeError";
+  return DecodeStatus::kDecodeError;
+}
+
+bool Http2StructureDecoder::ResumeFillingBuffer(DecodeBuffer* db,
+                                                uint32_t target_size) {
+  DVLOG(2) << "ResumeFillingBuffer@" << this << ": target_size=" << target_size
+           << "; offset_=" << offset_ << "; db->Remaining=" << db->Remaining();
+  if (target_size < offset_) {
+    HTTP2_BUG << "Already filled buffer_! target_size=" << target_size
+              << "    offset_=" << offset_;
+    return false;
+  }
+  const uint32_t needed = target_size - offset_;
+  const uint32_t num_to_copy = db->MinLengthRemaining(needed);
+  DVLOG(2) << "ResumeFillingBuffer num_to_copy=" << num_to_copy;
+  memcpy(&buffer_[offset_], db->cursor(), num_to_copy);
+  db->AdvanceCursor(num_to_copy);
+  offset_ += num_to_copy;
+  return needed == num_to_copy;
+}
+
+bool Http2StructureDecoder::ResumeFillingBuffer(DecodeBuffer* db,
+                                                uint32_t* remaining_payload,
+                                                uint32_t target_size) {
+  DVLOG(2) << "ResumeFillingBuffer@" << this << ": target_size=" << target_size
+           << "; offset_=" << offset_
+           << "; *remaining_payload=" << *remaining_payload
+           << "; db->Remaining=" << db->Remaining();
+  if (target_size < offset_) {
+    HTTP2_BUG << "Already filled buffer_! target_size=" << target_size
+              << "    offset_=" << offset_;
+    return false;
+  }
+  const uint32_t needed = target_size - offset_;
+  const uint32_t num_to_copy =
+      db->MinLengthRemaining(std::min(needed, *remaining_payload));
+  DVLOG(2) << "ResumeFillingBuffer num_to_copy=" << num_to_copy;
+  memcpy(&buffer_[offset_], db->cursor(), num_to_copy);
+  db->AdvanceCursor(num_to_copy);
+  offset_ += num_to_copy;
+  *remaining_payload -= num_to_copy;
+  return needed == num_to_copy;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/http2_structure_decoder.h b/http2/decoder/http2_structure_decoder.h
new file mode 100644
index 0000000..46c88b8
--- /dev/null
+++ b/http2/decoder/http2_structure_decoder.h
@@ -0,0 +1,131 @@
+// 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_DECODER_HTTP2_STRUCTURE_DECODER_H_
+#define QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_H_
+
+// Http2StructureDecoder is a class for decoding the fixed size structures in
+// the HTTP/2 spec, defined in net/third_party/quiche/src/http2/http2_structures.h. This class
+// is in aid of deciding whether to keep the SlowDecode methods which I
+// (jamessynge) now think may not be worth their complexity. In particular,
+// if most transport buffers are large, so it is rare that a structure is
+// split across buffer boundaries, than the cost of buffering upon
+// those rare occurrences is small, which then simplifies the callers.
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class Http2StructureDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE Http2StructureDecoder {
+ public:
+  // The caller needs to keep track of whether to call Start or Resume.
+  //
+  // Start has an optimization for the case where the DecodeBuffer holds the
+  // entire encoded structure; in that case it decodes into *out and returns
+  // true, and does NOT touch the data members of the Http2StructureDecoder
+  // instance because the caller won't be calling Resume later.
+  //
+  // However, if the DecodeBuffer is too small to hold the entire encoded
+  // structure, Start copies the available bytes into the Http2StructureDecoder
+  // instance, and returns false to indicate that it has not been able to
+  // complete the decoding.
+  //
+  template <class S>
+  bool Start(S* out, DecodeBuffer* db) {
+    static_assert(S::EncodedSize() <= sizeof buffer_, "buffer_ is too small");
+    DVLOG(2) << __func__ << "@" << this << ": db->Remaining=" << db->Remaining()
+             << "; EncodedSize=" << S::EncodedSize();
+    if (db->Remaining() >= S::EncodedSize()) {
+      DoDecode(out, db);
+      return true;
+    }
+    IncompleteStart(db, S::EncodedSize());
+    return false;
+  }
+
+  template <class S>
+  bool Resume(S* out, DecodeBuffer* db) {
+    DVLOG(2) << __func__ << "@" << this << ": offset_=" << offset_
+             << "; db->Remaining=" << db->Remaining();
+    if (ResumeFillingBuffer(db, S::EncodedSize())) {
+      // We have the whole thing now.
+      DVLOG(2) << __func__ << "@" << this << "    offset_=" << offset_
+               << "    Ready to decode from buffer_.";
+      DecodeBuffer buffer_db(buffer_, S::EncodedSize());
+      DoDecode(out, &buffer_db);
+      return true;
+    }
+    DCHECK_LT(offset_, S::EncodedSize());
+    return false;
+  }
+
+  // A second pair of Start and Resume, where the caller has a variable,
+  // |remaining_payload| that is both tested for sufficiency and updated
+  // during decoding. Note that the decode buffer may extend beyond the
+  // remaining payload because the buffer may include padding.
+  template <class S>
+  DecodeStatus Start(S* out, DecodeBuffer* db, uint32_t* remaining_payload) {
+    static_assert(S::EncodedSize() <= sizeof buffer_, "buffer_ is too small");
+    DVLOG(2) << __func__ << "@" << this
+             << ": *remaining_payload=" << *remaining_payload
+             << "; db->Remaining=" << db->Remaining()
+             << "; EncodedSize=" << S::EncodedSize();
+    if (db->MinLengthRemaining(*remaining_payload) >= S::EncodedSize()) {
+      DoDecode(out, db);
+      *remaining_payload -= S::EncodedSize();
+      return DecodeStatus::kDecodeDone;
+    }
+    return IncompleteStart(db, remaining_payload, S::EncodedSize());
+  }
+
+  template <class S>
+  bool Resume(S* out, DecodeBuffer* db, uint32_t* remaining_payload) {
+    DVLOG(3) << __func__ << "@" << this << ": offset_=" << offset_
+             << "; *remaining_payload=" << *remaining_payload
+             << "; db->Remaining=" << db->Remaining()
+             << "; EncodedSize=" << S::EncodedSize();
+    if (ResumeFillingBuffer(db, remaining_payload, S::EncodedSize())) {
+      // We have the whole thing now.
+      DVLOG(2) << __func__ << "@" << this << ": offset_=" << offset_
+               << "; Ready to decode from buffer_.";
+      DecodeBuffer buffer_db(buffer_, S::EncodedSize());
+      DoDecode(out, &buffer_db);
+      return true;
+    }
+    DCHECK_LT(offset_, S::EncodedSize());
+    return false;
+  }
+
+  uint32_t offset() const { return offset_; }
+
+ private:
+  friend class test::Http2StructureDecoderPeer;
+
+  uint32_t IncompleteStart(DecodeBuffer* db, uint32_t target_size);
+  DecodeStatus IncompleteStart(DecodeBuffer* db,
+                               uint32_t* remaining_payload,
+                               uint32_t target_size);
+
+  bool ResumeFillingBuffer(DecodeBuffer* db, uint32_t target_size);
+  bool ResumeFillingBuffer(DecodeBuffer* db,
+                           uint32_t* remaining_payload,
+                           uint32_t target_size);
+
+  uint32_t offset_;
+  char buffer_[Http2FrameHeader::EncodedSize()];
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_H_
diff --git a/http2/decoder/http2_structure_decoder_test.cc b/http2/decoder/http2_structure_decoder_test.cc
new file mode 100644
index 0000000..8d8da7b
--- /dev/null
+++ b/http2/decoder/http2_structure_decoder_test.cc
@@ -0,0 +1,541 @@
+// 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 "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h"
+
+// Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined in
+// net/third_party/quiche/src/http2/http2_structures.h) using Http2StructureDecoder, which
+// handles buffering of structures split across input buffer boundaries, and in
+// turn uses DoDecode when it has all of a structure in a contiguous buffer.
+
+// NOTE: This tests the first pair of Start and Resume, which don't take
+// a remaining_payload parameter. The other pair are well tested via the
+// payload decoder tests, though...
+// TODO(jamessynge): Create type parameterized tests for Http2StructureDecoder
+// where the type is the type of structure, and with testing of both pairs of
+// Start and Resume methods; note that it appears that the first pair will be
+// used only for Http2FrameHeader, and the other pair only for structures in the
+// frame payload.
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+const bool kMayReturnZeroOnFirst = false;
+
+template <class S>
+class Http2StructureDecoderTest : public RandomDecoderTest {
+ protected:
+  typedef S Structure;
+
+  Http2StructureDecoderTest() {
+    // IF the test adds more data after the encoded structure, stop as
+    // soon as the structure is decoded.
+    stop_decode_on_done_ = true;
+  }
+
+  DecodeStatus StartDecoding(DecodeBuffer* b) override {
+    // Overwrite the current contents of |structure_|, in to which we'll
+    // decode the buffer, so that we can be confident that we really decoded
+    // the structure every time.
+    Http2DefaultReconstructObject(&structure_, RandomPtr());
+    uint32_t old_remaining = b->Remaining();
+    if (structure_decoder_.Start(&structure_, b)) {
+      EXPECT_EQ(old_remaining - S::EncodedSize(), b->Remaining());
+      ++fast_decode_count_;
+      return DecodeStatus::kDecodeDone;
+    } else {
+      EXPECT_LT(structure_decoder_.offset(), S::EncodedSize());
+      EXPECT_EQ(0u, b->Remaining());
+      EXPECT_EQ(old_remaining - structure_decoder_.offset(), b->Remaining());
+      ++incomplete_start_count_;
+      return DecodeStatus::kDecodeInProgress;
+    }
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+    uint32_t old_offset = structure_decoder_.offset();
+    EXPECT_LT(old_offset, S::EncodedSize());
+    uint32_t avail = b->Remaining();
+    if (structure_decoder_.Resume(&structure_, b)) {
+      EXPECT_LE(S::EncodedSize(), old_offset + avail);
+      EXPECT_EQ(b->Remaining(), avail - (S::EncodedSize() - old_offset));
+      ++slow_decode_count_;
+      return DecodeStatus::kDecodeDone;
+    } else {
+      EXPECT_LT(structure_decoder_.offset(), S::EncodedSize());
+      EXPECT_EQ(0u, b->Remaining());
+      EXPECT_GT(S::EncodedSize(), old_offset + avail);
+      ++incomplete_resume_count_;
+      return DecodeStatus::kDecodeInProgress;
+    }
+  }
+
+  // Fully decodes the Structure at the start of data, and confirms it matches
+  // *expected (if provided).
+  AssertionResult DecodeLeadingStructure(const S* expected,
+                                         Http2StringPiece data) {
+    VERIFY_LE(S::EncodedSize(), data.size());
+    DecodeBuffer original(data);
+
+    // The validator is called after each of the several times that the input
+    // DecodeBuffer is decoded, each with a different segmentation of the input.
+    // Validate that structure_ matches the expected value, if provided.
+    Validator validator;
+    if (expected != nullptr) {
+      validator = [expected, this](const DecodeBuffer& db,
+                                   DecodeStatus status) -> AssertionResult {
+        VERIFY_EQ(*expected, structure_);
+        return AssertionSuccess();
+      };
+    }
+
+    // Before that, validate that decoding is done and that we've advanced
+    // the cursor the expected amount.
+    validator = ValidateDoneAndOffset(S::EncodedSize(), validator);
+
+    // Decode several times, with several segmentations of the input buffer.
+    fast_decode_count_ = 0;
+    slow_decode_count_ = 0;
+    incomplete_start_count_ = 0;
+    incomplete_resume_count_ = 0;
+    VERIFY_SUCCESS(DecodeAndValidateSeveralWays(
+        &original, kMayReturnZeroOnFirst, validator));
+    VERIFY_FALSE(HasFailure());
+    VERIFY_EQ(S::EncodedSize(), structure_decoder_.offset());
+    VERIFY_EQ(S::EncodedSize(), original.Offset());
+    VERIFY_LT(0u, fast_decode_count_);
+    VERIFY_LT(0u, slow_decode_count_);
+    VERIFY_LT(0u, incomplete_start_count_);
+
+    // If the structure is large enough so that SelectZeroOrOne will have
+    // caused Resume to return false, check that occurred.
+    if (S::EncodedSize() >= 2) {
+      VERIFY_LE(0u, incomplete_resume_count_);
+    } else {
+      VERIFY_EQ(0u, incomplete_resume_count_);
+    }
+    if (expected != nullptr) {
+      DVLOG(1) << "DecodeLeadingStructure expected: " << *expected;
+      DVLOG(1) << "DecodeLeadingStructure   actual: " << structure_;
+      VERIFY_EQ(*expected, structure_);
+    }
+    return AssertionSuccess();
+  }
+
+  template <size_t N>
+  AssertionResult DecodeLeadingStructure(const char (&data)[N]) {
+    VERIFY_AND_RETURN_SUCCESS(
+        DecodeLeadingStructure(nullptr, Http2StringPiece(data, N)));
+  }
+
+  template <size_t N>
+  AssertionResult DecodeLeadingStructure(const unsigned char (&data)[N]) {
+    VERIFY_AND_RETURN_SUCCESS(
+        DecodeLeadingStructure(nullptr, ToStringPiece(data)));
+  }
+
+  // Encode the structure |in_s| into bytes, then decode the bytes
+  // and validate that the decoder produced the same field values.
+  AssertionResult EncodeThenDecode(const S& in_s) {
+    Http2String bytes = SerializeStructure(in_s);
+    VERIFY_EQ(S::EncodedSize(), bytes.size());
+    VERIFY_AND_RETURN_SUCCESS(DecodeLeadingStructure(&in_s, bytes));
+  }
+
+  // Repeatedly fill a structure with random but valid contents, encode it, then
+  // decode it, and finally validate that the decoded structure matches the
+  // random input. Lather-rinse-and-repeat.
+  AssertionResult TestDecodingRandomizedStructures(size_t count) {
+    for (size_t i = 0; i < count; ++i) {
+      Structure input;
+      Randomize(&input, RandomPtr());
+      VERIFY_SUCCESS(EncodeThenDecode(input));
+    }
+    return AssertionSuccess();
+  }
+
+  AssertionResult TestDecodingRandomizedStructures() {
+    VERIFY_SUCCESS(TestDecodingRandomizedStructures(100));
+    return AssertionSuccess();
+  }
+
+  uint32_t decode_offset_ = 0;
+  S structure_;
+  Http2StructureDecoder structure_decoder_;
+  size_t fast_decode_count_ = 0;
+  size_t slow_decode_count_ = 0;
+  size_t incomplete_start_count_ = 0;
+  size_t incomplete_resume_count_ = 0;
+};
+
+class Http2FrameHeaderDecoderTest
+    : public Http2StructureDecoderTest<Http2FrameHeader> {};
+
+TEST_F(Http2FrameHeaderDecoderTest, DecodesLiteral) {
+  {
+    // Realistic input.
+    // clang-format off
+    const char kData[] = {
+        0x00, 0x00, 0x05,        // Payload length: 5
+        0x01,                    // Frame type: HEADERS
+        0x08,                    // Flags: PADDED
+        0x00, 0x00, 0x00, 0x01,  // Stream ID: 1
+        0x04,                    // Padding length: 4
+        0x00, 0x00, 0x00, 0x00,  // Padding bytes
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(5u, structure_.payload_length);
+    EXPECT_EQ(Http2FrameType::HEADERS, structure_.type);
+    EXPECT_EQ(Http2FrameFlag::PADDED, structure_.flags);
+    EXPECT_EQ(1u, structure_.stream_id);
+  }
+  {
+    // Unlikely input.
+    // clang-format off
+    const unsigned char kData[] = {
+        0xff, 0xff, 0xff,        // Payload length: uint24 max
+        0xff,                    // Frame type: Unknown
+        0xff,                    // Flags: Unknown/All
+        0xff, 0xff, 0xff, 0xff,  // Stream ID: uint31 max, plus R-bit
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ((1u << 24) - 1u, structure_.payload_length);
+    EXPECT_EQ(static_cast<Http2FrameType>(255), structure_.type);
+    EXPECT_EQ(255, structure_.flags);
+    EXPECT_EQ(0x7FFFFFFFu, structure_.stream_id);
+  }
+}
+
+TEST_F(Http2FrameHeaderDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2PriorityFieldsDecoderTest
+    : public Http2StructureDecoderTest<Http2PriorityFields> {};
+
+TEST_F(Http2PriorityFieldsDecoderTest, DecodesLiteral) {
+  {
+    // clang-format off
+    const unsigned char kData[] = {
+        0x80, 0x00, 0x00, 0x05,  // Exclusive (yes) and Dependency (5)
+        0xff,                    // Weight: 256 (after adding 1)
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(5u, structure_.stream_dependency);
+    EXPECT_EQ(256u, structure_.weight);
+    EXPECT_EQ(true, structure_.is_exclusive);
+  }
+  {
+    // clang-format off
+    const unsigned char kData[] = {
+        0x7f, 0xff, 0xff, 0xff,  // Excl. (no) and Dependency (uint31 max)
+        0x00,                    // Weight: 1 (after adding 1)
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(StreamIdMask(), structure_.stream_dependency);
+    EXPECT_EQ(1u, structure_.weight);
+    EXPECT_FALSE(structure_.is_exclusive);
+  }
+}
+
+TEST_F(Http2PriorityFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2RstStreamFieldsDecoderTest
+    : public Http2StructureDecoderTest<Http2RstStreamFields> {};
+
+TEST_F(Http2RstStreamFieldsDecoderTest, DecodesLiteral) {
+  {
+    // clang-format off
+    const char kData[] = {
+        0x00, 0x00, 0x00, 0x01,  // Error: PROTOCOL_ERROR
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_TRUE(structure_.IsSupportedErrorCode());
+    EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code);
+  }
+  {
+    // clang-format off
+    const unsigned char kData[] = {
+        0xff, 0xff, 0xff, 0xff,  // Error: max uint32 (Unknown error code)
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_FALSE(structure_.IsSupportedErrorCode());
+    EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code);
+  }
+}
+
+TEST_F(Http2RstStreamFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2SettingFieldsDecoderTest
+    : public Http2StructureDecoderTest<Http2SettingFields> {};
+
+TEST_F(Http2SettingFieldsDecoderTest, DecodesLiteral) {
+  {
+    // clang-format off
+    const char kData[] = {
+        0x00, 0x01,              // Setting: HEADER_TABLE_SIZE
+        0x00, 0x00, 0x40, 0x00,  // Value: 16K
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_TRUE(structure_.IsSupportedParameter());
+    EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE, structure_.parameter);
+    EXPECT_EQ(1u << 14, structure_.value);
+  }
+  {
+    // clang-format off
+    const unsigned char kData[] = {
+        0x00, 0x00,              // Setting: Unknown (0)
+        0xff, 0xff, 0xff, 0xff,  // Value: max uint32
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_FALSE(structure_.IsSupportedParameter());
+    EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter);
+  }
+}
+
+TEST_F(Http2SettingFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2PushPromiseFieldsDecoderTest
+    : public Http2StructureDecoderTest<Http2PushPromiseFields> {};
+
+TEST_F(Http2PushPromiseFieldsDecoderTest, DecodesLiteral) {
+  {
+    // clang-format off
+    const unsigned char kData[] = {
+        0x00, 0x01, 0x8a, 0x92,  // Promised Stream ID: 101010
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(101010u, structure_.promised_stream_id);
+  }
+  {
+    // Promised stream id has R-bit (reserved for future use) set, which
+    // should be cleared by the decoder.
+    // clang-format off
+    const unsigned char kData[] = {
+        // Promised Stream ID: max uint31 and R-bit
+        0xff, 0xff, 0xff, 0xff,
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id);
+  }
+}
+
+TEST_F(Http2PushPromiseFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2PingFieldsDecoderTest
+    : public Http2StructureDecoderTest<Http2PingFields> {};
+
+TEST_F(Http2PingFieldsDecoderTest, DecodesLiteral) {
+  {
+    // Each byte is different, so can detect if order changed.
+    const char kData[] = {
+        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    };
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(ToStringPiece(kData), ToStringPiece(structure_.opaque_bytes));
+  }
+  {
+    // All zeros, detect problems handling NULs.
+    const char kData[] = {
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    };
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(ToStringPiece(kData), ToStringPiece(structure_.opaque_bytes));
+  }
+  {
+    const unsigned char kData[] = {
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    };
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(ToStringPiece(kData), ToStringPiece(structure_.opaque_bytes));
+  }
+}
+
+TEST_F(Http2PingFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2GoAwayFieldsDecoderTest
+    : public Http2StructureDecoderTest<Http2GoAwayFields> {};
+
+TEST_F(Http2GoAwayFieldsDecoderTest, DecodesLiteral) {
+  {
+    // clang-format off
+    const char kData[] = {
+        0x00, 0x00, 0x00, 0x00,  // Last Stream ID: 0
+        0x00, 0x00, 0x00, 0x00,  // Error: NO_ERROR (0)
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(0u, structure_.last_stream_id);
+    EXPECT_TRUE(structure_.IsSupportedErrorCode());
+    EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, structure_.error_code);
+  }
+  {
+    // clang-format off
+    const char kData[] = {
+        0x00, 0x00, 0x00, 0x01,  // Last Stream ID: 1
+        0x00, 0x00, 0x00, 0x0d,  // Error: HTTP_1_1_REQUIRED
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(1u, structure_.last_stream_id);
+    EXPECT_TRUE(structure_.IsSupportedErrorCode());
+    EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, structure_.error_code);
+  }
+  {
+    // clang-format off
+    const unsigned char kData[] = {
+        0xff, 0xff, 0xff, 0xff,  // Last Stream ID: max uint31 and R-bit
+        0xff, 0xff, 0xff, 0xff,  // Error: max uint32 (Unknown error code)
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(StreamIdMask(), structure_.last_stream_id);  // No high-bit.
+    EXPECT_FALSE(structure_.IsSupportedErrorCode());
+    EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code);
+  }
+}
+
+TEST_F(Http2GoAwayFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2WindowUpdateFieldsDecoderTest
+    : public Http2StructureDecoderTest<Http2WindowUpdateFields> {};
+
+TEST_F(Http2WindowUpdateFieldsDecoderTest, DecodesLiteral) {
+  {
+    // clang-format off
+    const char kData[] = {
+        0x00, 0x01, 0x00, 0x00,  // Window Size Increment: 2 ^ 16
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(1u << 16, structure_.window_size_increment);
+  }
+  {
+    // Increment must be non-zero, but we need to be able to decode the invalid
+    // zero to detect it.
+    // clang-format off
+    const char kData[] = {
+        0x00, 0x00, 0x00, 0x00,  // Window Size Increment: 0
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(0u, structure_.window_size_increment);
+  }
+  {
+    // Increment has R-bit (reserved for future use) set, which
+    // should be cleared by the decoder.
+    // clang-format off
+    const unsigned char kData[] = {
+        // Window Size Increment: max uint31 and R-bit
+        0xff, 0xff, 0xff, 0xff,
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(StreamIdMask(), structure_.window_size_increment);
+  }
+}
+
+TEST_F(Http2WindowUpdateFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2AltSvcFieldsDecoderTest
+    : public Http2StructureDecoderTest<Http2AltSvcFields> {};
+
+TEST_F(Http2AltSvcFieldsDecoderTest, DecodesLiteral) {
+  {
+    // clang-format off
+    const char kData[] = {
+        0x00, 0x00,  // Origin Length: 0
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(0, structure_.origin_length);
+  }
+  {
+    // clang-format off
+    const char kData[] = {
+        0x00, 0x14,  // Origin Length: 20
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(20, structure_.origin_length);
+  }
+  {
+    // clang-format off
+    const unsigned char kData[] = {
+        0xff, 0xff,  // Origin Length: uint16 max
+    };
+    // clang-format on
+    ASSERT_TRUE(DecodeLeadingStructure(kData));
+    EXPECT_EQ(65535, structure_.origin_length);
+  }
+}
+
+TEST_F(Http2AltSvcFieldsDecoderTest, DecodesRandomized) {
+  TestDecodingRandomizedStructures();
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/http2_structure_decoder_test_util.cc b/http2/decoder/http2_structure_decoder_test_util.cc
new file mode 100644
index 0000000..90805ea
--- /dev/null
+++ b/http2/decoder/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 "net/third_party/quiche/src/http2/decoder/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/http2/decoder/http2_structure_decoder_test_util.h b/http2/decoder/http2_structure_decoder_test_util.h
new file mode 100644
index 0000000..4b533c7
--- /dev/null
+++ b/http2/decoder/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_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
+#define QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
+
+#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h"
+
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+class Http2StructureDecoderPeer {
+ public:
+  // Overwrite the Http2StructureDecoder instance with random values.
+  static void Randomize(Http2StructureDecoder* p, Http2Random* rng);
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
diff --git a/http2/decoder/payload_decoders/altsvc_payload_decoder.cc b/http2/decoder/payload_decoders/altsvc_payload_decoder.cc
new file mode 100644
index 0000000..4e4d860
--- /dev/null
+++ b/http2/decoder/payload_decoders/altsvc_payload_decoder.cc
@@ -0,0 +1,148 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+                         AltSvcPayloadDecoder::PayloadState v) {
+  switch (v) {
+    case AltSvcPayloadDecoder::PayloadState::kStartDecodingStruct:
+      return out << "kStartDecodingStruct";
+    case AltSvcPayloadDecoder::PayloadState::kMaybeDecodedStruct:
+      return out << "kMaybeDecodedStruct";
+    case AltSvcPayloadDecoder::PayloadState::kDecodingStrings:
+      return out << "kDecodingStrings";
+    case AltSvcPayloadDecoder::PayloadState::kResumeDecodingStruct:
+      return out << "kResumeDecodingStruct";
+  }
+  // Since the value doesn't come over the wire, only a programming bug should
+  // result in reaching this point.
+  int unknown = static_cast<int>(v);
+  HTTP2_BUG << "Invalid AltSvcPayloadDecoder::PayloadState: " << unknown;
+  return out << "AltSvcPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus AltSvcPayloadDecoder::StartDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "AltSvcPayloadDecoder::StartDecodingPayload: "
+           << state->frame_header();
+  DCHECK_EQ(Http2FrameType::ALTSVC, state->frame_header().type);
+  DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+  DCHECK_EQ(0, state->frame_header().flags);
+
+  state->InitializeRemainders();
+  payload_state_ = PayloadState::kStartDecodingStruct;
+
+  return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus AltSvcPayloadDecoder::ResumeDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  const Http2FrameHeader& frame_header = state->frame_header();
+  DVLOG(2) << "AltSvcPayloadDecoder::ResumeDecodingPayload: " << frame_header;
+  DCHECK_EQ(Http2FrameType::ALTSVC, frame_header.type);
+  DCHECK_LE(state->remaining_payload(), frame_header.payload_length);
+  DCHECK_LE(db->Remaining(), state->remaining_payload());
+  DCHECK_NE(PayloadState::kMaybeDecodedStruct, payload_state_);
+  // |status| has to be initialized to some value to avoid compiler error in
+  // case PayloadState::kMaybeDecodedStruct below, but value does not matter,
+  // see DCHECK_NE above.
+  DecodeStatus status = DecodeStatus::kDecodeError;
+  while (true) {
+    DVLOG(2) << "AltSvcPayloadDecoder::ResumeDecodingPayload payload_state_="
+             << payload_state_;
+    switch (payload_state_) {
+      case PayloadState::kStartDecodingStruct:
+        status = state->StartDecodingStructureInPayload(&altsvc_fields_, db);
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kMaybeDecodedStruct:
+        if (status == DecodeStatus::kDecodeDone &&
+            altsvc_fields_.origin_length <= state->remaining_payload()) {
+          size_t origin_length = altsvc_fields_.origin_length;
+          size_t value_length = state->remaining_payload() - origin_length;
+          state->listener()->OnAltSvcStart(frame_header, origin_length,
+                                           value_length);
+        } else if (status != DecodeStatus::kDecodeDone) {
+          DCHECK(state->remaining_payload() > 0 ||
+                 status == DecodeStatus::kDecodeError)
+              << "\nremaining_payload: " << state->remaining_payload()
+              << "\nstatus: " << status << "\nheader: " << frame_header;
+          // Assume in progress.
+          payload_state_ = PayloadState::kResumeDecodingStruct;
+          return status;
+        } else {
+          // The origin's length is longer than the remaining payload.
+          DCHECK_GT(altsvc_fields_.origin_length, state->remaining_payload());
+          return state->ReportFrameSizeError();
+        }
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kDecodingStrings:
+        return DecodeStrings(state, db);
+
+      case PayloadState::kResumeDecodingStruct:
+        status = state->ResumeDecodingStructureInPayload(&altsvc_fields_, db);
+        payload_state_ = PayloadState::kMaybeDecodedStruct;
+        continue;
+    }
+    HTTP2_BUG << "PayloadState: " << payload_state_;
+  }
+}
+
+DecodeStatus AltSvcPayloadDecoder::DecodeStrings(FrameDecoderState* state,
+                                                 DecodeBuffer* db) {
+  DVLOG(2) << "AltSvcPayloadDecoder::DecodeStrings remaining_payload="
+           << state->remaining_payload()
+           << ", db->Remaining=" << db->Remaining();
+  // Note that we don't explicitly keep track of exactly how far through the
+  // origin; instead we compute it from how much is left of the original
+  // payload length and the decoded total length of the origin.
+  size_t origin_length = altsvc_fields_.origin_length;
+  size_t value_length = state->frame_header().payload_length - origin_length -
+                        Http2AltSvcFields::EncodedSize();
+  if (state->remaining_payload() > value_length) {
+    size_t remaining_origin_length = state->remaining_payload() - value_length;
+    size_t avail = db->MinLengthRemaining(remaining_origin_length);
+    state->listener()->OnAltSvcOriginData(db->cursor(), avail);
+    db->AdvanceCursor(avail);
+    state->ConsumePayload(avail);
+    if (remaining_origin_length > avail) {
+      payload_state_ = PayloadState::kDecodingStrings;
+      return DecodeStatus::kDecodeInProgress;
+    }
+  }
+  // All that is left is the value string.
+  DCHECK_LE(state->remaining_payload(), value_length);
+  DCHECK_LE(db->Remaining(), state->remaining_payload());
+  if (db->HasData()) {
+    size_t avail = db->Remaining();
+    state->listener()->OnAltSvcValueData(db->cursor(), avail);
+    db->AdvanceCursor(avail);
+    state->ConsumePayload(avail);
+  }
+  if (state->remaining_payload() == 0) {
+    state->listener()->OnAltSvcEnd();
+    return DecodeStatus::kDecodeDone;
+  }
+  payload_state_ = PayloadState::kDecodingStrings;
+  return DecodeStatus::kDecodeInProgress;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/altsvc_payload_decoder.h b/http2/decoder/payload_decoders/altsvc_payload_decoder.h
new file mode 100644
index 0000000..e3523f9
--- /dev/null
+++ b/http2/decoder/payload_decoders/altsvc_payload_decoder.h
@@ -0,0 +1,64 @@
+// 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_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a ALTSVC frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class AltSvcPayloadDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE AltSvcPayloadDecoder {
+ public:
+  // States during decoding of a ALTSVC frame.
+  enum class PayloadState {
+    // Start decoding the fixed size structure at the start of an ALTSVC
+    // frame (Http2AltSvcFields).
+    kStartDecodingStruct,
+
+    // Handle the DecodeStatus returned from starting or resuming the
+    // decoding of Http2AltSvcFields. If complete, calls OnAltSvcStart.
+    kMaybeDecodedStruct,
+
+    // Reports the value of the strings (origin and value) of an ALTSVC frame
+    // to the listener.
+    kDecodingStrings,
+
+    // The initial decode buffer wasn't large enough for the Http2AltSvcFields,
+    // so this state resumes the decoding when ResumeDecodingPayload is called
+    // later with a new DecodeBuffer.
+    kResumeDecodingStruct,
+  };
+
+  // Starts the decoding of a ALTSVC frame's payload, and completes it if the
+  // entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a ALTSVC frame's payload that has been split across
+  // decode buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+ private:
+  friend class test::AltSvcPayloadDecoderPeer;
+
+  // Implements state kDecodingStrings.
+  DecodeStatus DecodeStrings(FrameDecoderState* state, DecodeBuffer* db);
+
+  Http2AltSvcFields altsvc_fields_;
+  PayloadState payload_state_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc b/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc
new file mode 100644
index 0000000..bf928d3
--- /dev/null
+++ b/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc
@@ -0,0 +1,121 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class AltSvcPayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() { return Http2FrameType::ALTSVC; }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+  void OnAltSvcStart(const Http2FrameHeader& header,
+                     size_t origin_length,
+                     size_t value_length) override {
+    VLOG(1) << "OnAltSvcStart header: " << header
+            << "; origin_length=" << origin_length
+            << "; value_length=" << value_length;
+    StartFrame(header)->OnAltSvcStart(header, origin_length, value_length);
+  }
+
+  void OnAltSvcOriginData(const char* data, size_t len) override {
+    VLOG(1) << "OnAltSvcOriginData: len=" << len;
+    CurrentFrame()->OnAltSvcOriginData(data, len);
+  }
+
+  void OnAltSvcValueData(const char* data, size_t len) override {
+    VLOG(1) << "OnAltSvcValueData: len=" << len;
+    CurrentFrame()->OnAltSvcValueData(data, len);
+  }
+
+  void OnAltSvcEnd() override {
+    VLOG(1) << "OnAltSvcEnd";
+    EndFrame()->OnAltSvcEnd();
+  }
+
+  void OnFrameSizeError(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnFrameSizeError: " << header;
+    FrameError(header)->OnFrameSizeError(header);
+  }
+};
+
+class AltSvcPayloadDecoderTest
+    : public AbstractPayloadDecoderTest<AltSvcPayloadDecoder,
+                                        AltSvcPayloadDecoderPeer,
+                                        Listener> {};
+
+// Confirm we get an error if the payload is not long enough to hold
+// Http2AltSvcFields and the indicated length of origin.
+TEST_F(AltSvcPayloadDecoderTest, Truncated) {
+  Http2FrameBuilder fb;
+  fb.Append(Http2AltSvcFields{0xffff});  // The longest possible origin length.
+  fb.Append("Too little origin!");
+  EXPECT_TRUE(
+      VerifyDetectsFrameSizeError(0, fb.buffer(), /*approve_size*/ nullptr));
+}
+
+class AltSvcPayloadLengthTests : public AltSvcPayloadDecoderTest,
+                                 public ::testing::WithParamInterface<
+                                     ::testing::tuple<uint16_t, uint32_t>> {
+ protected:
+  AltSvcPayloadLengthTests()
+      : origin_length_(::testing::get<0>(GetParam())),
+        value_length_(::testing::get<1>(GetParam())) {
+    VLOG(1) << "################  origin_length_=" << origin_length_
+            << "   value_length_=" << value_length_ << "  ################";
+  }
+
+  const uint16_t origin_length_;
+  const uint32_t value_length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousOriginAndValueLengths,
+                        AltSvcPayloadLengthTests,
+                        ::testing::Combine(::testing::Values(0, 1, 3, 65535),
+                                           ::testing::Values(0, 1, 3, 65537)));
+
+TEST_P(AltSvcPayloadLengthTests, ValidOriginAndValueLength) {
+  Http2String origin = Random().RandString(origin_length_);
+  Http2String value = Random().RandString(value_length_);
+  Http2FrameBuilder fb;
+  fb.Append(Http2AltSvcFields{origin_length_});
+  fb.Append(origin);
+  fb.Append(value);
+  Http2FrameHeader header(fb.size(), Http2FrameType::ALTSVC, RandFlags(),
+                          RandStreamId());
+  set_frame_header(header);
+  FrameParts expected(header);
+  expected.SetAltSvcExpected(origin, value);
+  ASSERT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/continuation_payload_decoder.cc b/http2/decoder/payload_decoders/continuation_payload_decoder.cc
new file mode 100644
index 0000000..aa9c817
--- /dev/null
+++ b/http2/decoder/payload_decoders/continuation_payload_decoder.cc
@@ -0,0 +1,58 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus ContinuationPayloadDecoder::StartDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  const Http2FrameHeader& frame_header = state->frame_header();
+  const uint32_t total_length = frame_header.payload_length;
+
+  DVLOG(2) << "ContinuationPayloadDecoder::StartDecodingPayload: "
+           << frame_header;
+  DCHECK_EQ(Http2FrameType::CONTINUATION, frame_header.type);
+  DCHECK_LE(db->Remaining(), total_length);
+  DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::END_HEADERS));
+
+  state->InitializeRemainders();
+  state->listener()->OnContinuationStart(frame_header);
+  return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus ContinuationPayloadDecoder::ResumeDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "ContinuationPayloadDecoder::ResumeDecodingPayload"
+           << "  remaining_payload=" << state->remaining_payload()
+           << "  db->Remaining=" << db->Remaining();
+  DCHECK_EQ(Http2FrameType::CONTINUATION, state->frame_header().type);
+  DCHECK_LE(state->remaining_payload(), state->frame_header().payload_length);
+  DCHECK_LE(db->Remaining(), state->remaining_payload());
+
+  size_t avail = db->Remaining();
+  DCHECK_LE(avail, state->remaining_payload());
+  if (avail > 0) {
+    state->listener()->OnHpackFragment(db->cursor(), avail);
+    db->AdvanceCursor(avail);
+    state->ConsumePayload(avail);
+  }
+  if (state->remaining_payload() == 0) {
+    state->listener()->OnContinuationEnd();
+    return DecodeStatus::kDecodeDone;
+  }
+  return DecodeStatus::kDecodeInProgress;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/continuation_payload_decoder.h b/http2/decoder/payload_decoders/continuation_payload_decoder.h
new file mode 100644
index 0000000..63b16ae
--- /dev/null
+++ b/http2/decoder/payload_decoders/continuation_payload_decoder.h
@@ -0,0 +1,31 @@
+// 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_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a CONTINUATION frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE ContinuationPayloadDecoder {
+ public:
+  // Starts the decoding of a CONTINUATION frame's payload, and completes
+  // it if the entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a CONTINUATION frame's payload that has been split across
+  // decode buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc b/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc
new file mode 100644
index 0000000..63888d3
--- /dev/null
+++ b/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc
@@ -0,0 +1,85 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h"
+
+#include <stddef.h>
+
+#include <type_traits>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class ContinuationPayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() {
+    return Http2FrameType::CONTINUATION;
+  }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+  void OnContinuationStart(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnContinuationStart: " << header;
+    StartFrame(header)->OnContinuationStart(header);
+  }
+
+  void OnHpackFragment(const char* data, size_t len) override {
+    VLOG(1) << "OnHpackFragment: len=" << len;
+    CurrentFrame()->OnHpackFragment(data, len);
+  }
+
+  void OnContinuationEnd() override {
+    VLOG(1) << "OnContinuationEnd";
+    EndFrame()->OnContinuationEnd();
+  }
+};
+
+class ContinuationPayloadDecoderTest
+    : public AbstractPayloadDecoderTest<ContinuationPayloadDecoder,
+                                        ContinuationPayloadDecoderPeer,
+                                        Listener>,
+      public ::testing::WithParamInterface<uint32_t> {
+ protected:
+  ContinuationPayloadDecoderTest() : length_(GetParam()) {
+    VLOG(1) << "################  length_=" << length_ << "  ################";
+  }
+
+  const uint32_t length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousLengths,
+                        ContinuationPayloadDecoderTest,
+                        ::testing::Values(0, 1, 2, 3, 4, 5, 6));
+
+TEST_P(ContinuationPayloadDecoderTest, ValidLength) {
+  Http2String hpack_payload = Random().RandString(length_);
+  Http2FrameHeader frame_header(length_, Http2FrameType::CONTINUATION,
+                                RandFlags(), RandStreamId());
+  set_frame_header(frame_header);
+  FrameParts expected(frame_header, hpack_payload);
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(hpack_payload, expected));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/data_payload_decoder.cc b/http2/decoder/payload_decoders/data_payload_decoder.cc
new file mode 100644
index 0000000..b6b8041
--- /dev/null
+++ b/http2/decoder/payload_decoders/data_payload_decoder.cc
@@ -0,0 +1,127 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+                         DataPayloadDecoder::PayloadState v) {
+  switch (v) {
+    case DataPayloadDecoder::PayloadState::kReadPadLength:
+      return out << "kReadPadLength";
+    case DataPayloadDecoder::PayloadState::kReadPayload:
+      return out << "kReadPayload";
+    case DataPayloadDecoder::PayloadState::kSkipPadding:
+      return out << "kSkipPadding";
+  }
+  // Since the value doesn't come over the wire, only a programming bug should
+  // result in reaching this point.
+  int unknown = static_cast<int>(v);
+  HTTP2_BUG << "Invalid DataPayloadDecoder::PayloadState: " << unknown;
+  return out << "DataPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus DataPayloadDecoder::StartDecodingPayload(FrameDecoderState* state,
+                                                      DecodeBuffer* db) {
+  const Http2FrameHeader& frame_header = state->frame_header();
+  const uint32_t total_length = frame_header.payload_length;
+
+  DVLOG(2) << "DataPayloadDecoder::StartDecodingPayload: " << frame_header;
+  DCHECK_EQ(Http2FrameType::DATA, frame_header.type);
+  DCHECK_LE(db->Remaining(), total_length);
+  DCHECK_EQ(0, frame_header.flags &
+                   ~(Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED));
+
+  // Special case for the hoped for common case: unpadded and fits fully into
+  // the decode buffer. TO BE SEEN if that is true. It certainly requires that
+  // the transport buffers be large (e.g. >> 16KB typically).
+  // TODO(jamessynge) Add counters.
+  DVLOG(2) << "StartDecodingPayload total_length=" << total_length;
+  if (!frame_header.IsPadded()) {
+    DVLOG(2) << "StartDecodingPayload !IsPadded";
+    if (db->Remaining() == total_length) {
+      DVLOG(2) << "StartDecodingPayload all present";
+      // Note that we don't cache the listener field so that the callee can
+      // replace it if the frame is bad.
+      // If this case is common enough, consider combining the 3 callbacks
+      // into one.
+      state->listener()->OnDataStart(frame_header);
+      if (total_length > 0) {
+        state->listener()->OnDataPayload(db->cursor(), total_length);
+        db->AdvanceCursor(total_length);
+      }
+      state->listener()->OnDataEnd();
+      return DecodeStatus::kDecodeDone;
+    }
+    payload_state_ = PayloadState::kReadPayload;
+  } else {
+    payload_state_ = PayloadState::kReadPadLength;
+  }
+  state->InitializeRemainders();
+  state->listener()->OnDataStart(frame_header);
+  return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus DataPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state,
+                                                       DecodeBuffer* db) {
+  DVLOG(2) << "DataPayloadDecoder::ResumeDecodingPayload payload_state_="
+           << payload_state_;
+  const Http2FrameHeader& frame_header = state->frame_header();
+  DCHECK_EQ(Http2FrameType::DATA, frame_header.type);
+  DCHECK_LE(state->remaining_payload_and_padding(),
+            frame_header.payload_length);
+  DCHECK_LE(db->Remaining(), state->remaining_payload_and_padding());
+  DecodeStatus status;
+  size_t avail;
+  switch (payload_state_) {
+    case PayloadState::kReadPadLength:
+      // ReadPadLength handles the OnPadLength callback, and updating the
+      // remaining_payload and remaining_padding fields. If the amount of
+      // padding is too large to fit in the frame's payload, ReadPadLength
+      // instead calls OnPaddingTooLong and returns kDecodeError.
+      status = state->ReadPadLength(db, /*report_pad_length*/ true);
+      if (status != DecodeStatus::kDecodeDone) {
+        return status;
+      }
+      HTTP2_FALLTHROUGH;
+
+    case PayloadState::kReadPayload:
+      avail = state->AvailablePayload(db);
+      if (avail > 0) {
+        state->listener()->OnDataPayload(db->cursor(), avail);
+        db->AdvanceCursor(avail);
+        state->ConsumePayload(avail);
+      }
+      if (state->remaining_payload() > 0) {
+        payload_state_ = PayloadState::kReadPayload;
+        return DecodeStatus::kDecodeInProgress;
+      }
+      HTTP2_FALLTHROUGH;
+
+    case PayloadState::kSkipPadding:
+      // SkipPadding handles the OnPadding callback.
+      if (state->SkipPadding(db)) {
+        state->listener()->OnDataEnd();
+        return DecodeStatus::kDecodeDone;
+      }
+      payload_state_ = PayloadState::kSkipPadding;
+      return DecodeStatus::kDecodeInProgress;
+  }
+  HTTP2_BUG << "PayloadState: " << payload_state_;
+  return DecodeStatus::kDecodeError;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/data_payload_decoder.h b/http2/decoder/payload_decoders/data_payload_decoder.h
new file mode 100644
index 0000000..1e083a9
--- /dev/null
+++ b/http2/decoder/payload_decoders/data_payload_decoder.h
@@ -0,0 +1,54 @@
+// 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_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a DATA frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class DataPayloadDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE DataPayloadDecoder {
+ public:
+  // States during decoding of a DATA frame.
+  enum class PayloadState {
+    // The frame is padded and we need to read the PAD_LENGTH field (1 byte),
+    // and then call OnPadLength
+    kReadPadLength,
+
+    // Report the non-padding portion of the payload to the listener's
+    // OnDataPayload method.
+    kReadPayload,
+
+    // The decoder has finished with the non-padding portion of the payload,
+    // and is now ready to skip the trailing padding, if the frame has any.
+    kSkipPadding,
+  };
+
+  // Starts decoding a DATA frame's payload, and completes it if
+  // the entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a DATA frame's payload that has been split across
+  // decode buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+ private:
+  friend class test::DataPayloadDecoderPeer;
+
+  PayloadState payload_state_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/data_payload_decoder_test.cc b/http2/decoder/payload_decoders/data_payload_decoder_test.cc
new file mode 100644
index 0000000..e5253b9
--- /dev/null
+++ b/http2/decoder/payload_decoders/data_payload_decoder_test.cc
@@ -0,0 +1,113 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class DataPayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() { return Http2FrameType::DATA; }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() {
+    return Http2FrameFlag::PADDED;
+  }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+  void OnDataStart(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnDataStart: " << header;
+    StartFrame(header)->OnDataStart(header);
+  }
+
+  void OnDataPayload(const char* data, size_t len) override {
+    VLOG(1) << "OnDataPayload: len=" << len;
+    CurrentFrame()->OnDataPayload(data, len);
+  }
+
+  void OnDataEnd() override {
+    VLOG(1) << "OnDataEnd";
+    EndFrame()->OnDataEnd();
+  }
+
+  void OnPadLength(size_t pad_length) override {
+    VLOG(1) << "OnPadLength: " << pad_length;
+    CurrentFrame()->OnPadLength(pad_length);
+  }
+
+  void OnPadding(const char* padding, size_t skipped_length) override {
+    VLOG(1) << "OnPadding: " << skipped_length;
+    CurrentFrame()->OnPadding(padding, skipped_length);
+  }
+
+  void OnPaddingTooLong(const Http2FrameHeader& header,
+                        size_t missing_length) override {
+    VLOG(1) << "OnPaddingTooLong: " << header
+            << "    missing_length: " << missing_length;
+    EndFrame()->OnPaddingTooLong(header, missing_length);
+  }
+};
+
+class DataPayloadDecoderTest
+    : public AbstractPaddablePayloadDecoderTest<DataPayloadDecoder,
+                                                DataPayloadDecoderPeer,
+                                                Listener> {
+ protected:
+  AssertionResult CreateAndDecodeDataOfSize(size_t data_size) {
+    Reset();
+    uint8_t flags = RandFlags();
+
+    Http2String data_payload = Random().RandString(data_size);
+    frame_builder_.Append(data_payload);
+    MaybeAppendTrailingPadding();
+
+    Http2FrameHeader frame_header(frame_builder_.size(), Http2FrameType::DATA,
+                                  flags, RandStreamId());
+    set_frame_header(frame_header);
+    ScrubFlagsOfHeader(&frame_header);
+    FrameParts expected(frame_header, data_payload, total_pad_length_);
+    VERIFY_AND_RETURN_SUCCESS(
+        DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), expected));
+  }
+};
+
+INSTANTIATE_TEST_CASE_P(VariousPadLengths,
+                        DataPayloadDecoderTest,
+                        ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256));
+
+TEST_P(DataPayloadDecoderTest, VariousDataPayloadSizes) {
+  for (size_t data_size : {0, 1, 2, 3, 255, 256, 1024}) {
+    EXPECT_TRUE(CreateAndDecodeDataOfSize(data_size));
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/goaway_payload_decoder.cc b/http2/decoder/payload_decoders/goaway_payload_decoder.cc
new file mode 100644
index 0000000..cf7b673
--- /dev/null
+++ b/http2/decoder/payload_decoders/goaway_payload_decoder.cc
@@ -0,0 +1,121 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+                         GoAwayPayloadDecoder::PayloadState v) {
+  switch (v) {
+    case GoAwayPayloadDecoder::PayloadState::kStartDecodingFixedFields:
+      return out << "kStartDecodingFixedFields";
+    case GoAwayPayloadDecoder::PayloadState::kHandleFixedFieldsStatus:
+      return out << "kHandleFixedFieldsStatus";
+    case GoAwayPayloadDecoder::PayloadState::kReadOpaqueData:
+      return out << "kReadOpaqueData";
+    case GoAwayPayloadDecoder::PayloadState::kResumeDecodingFixedFields:
+      return out << "kResumeDecodingFixedFields";
+  }
+  // Since the value doesn't come over the wire, only a programming bug should
+  // result in reaching this point.
+  int unknown = static_cast<int>(v);
+  HTTP2_BUG << "Invalid GoAwayPayloadDecoder::PayloadState: " << unknown;
+  return out << "GoAwayPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus GoAwayPayloadDecoder::StartDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "GoAwayPayloadDecoder::StartDecodingPayload: "
+           << state->frame_header();
+  DCHECK_EQ(Http2FrameType::GOAWAY, state->frame_header().type);
+  DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+  DCHECK_EQ(0, state->frame_header().flags);
+
+  state->InitializeRemainders();
+  payload_state_ = PayloadState::kStartDecodingFixedFields;
+  return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus GoAwayPayloadDecoder::ResumeDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "GoAwayPayloadDecoder::ResumeDecodingPayload: remaining_payload="
+           << state->remaining_payload()
+           << ", db->Remaining=" << db->Remaining();
+
+  const Http2FrameHeader& frame_header = state->frame_header();
+  DCHECK_EQ(Http2FrameType::GOAWAY, frame_header.type);
+  DCHECK_LE(db->Remaining(), frame_header.payload_length);
+  DCHECK_NE(PayloadState::kHandleFixedFieldsStatus, payload_state_);
+
+  // |status| has to be initialized to some value to avoid compiler error in
+  // case PayloadState::kHandleFixedFieldsStatus below, but value does not
+  // matter, see DCHECK_NE above.
+  DecodeStatus status = DecodeStatus::kDecodeError;
+  size_t avail;
+  while (true) {
+    DVLOG(2) << "GoAwayPayloadDecoder::ResumeDecodingPayload payload_state_="
+             << payload_state_;
+    switch (payload_state_) {
+      case PayloadState::kStartDecodingFixedFields:
+        status = state->StartDecodingStructureInPayload(&goaway_fields_, db);
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kHandleFixedFieldsStatus:
+        if (status == DecodeStatus::kDecodeDone) {
+          state->listener()->OnGoAwayStart(frame_header, goaway_fields_);
+        } else {
+          // Not done decoding the structure. Either we've got more payload
+          // to decode, or we've run out because the payload is too short,
+          // in which case OnFrameSizeError will have already been called.
+          DCHECK((status == DecodeStatus::kDecodeInProgress &&
+                  state->remaining_payload() > 0) ||
+                 (status == DecodeStatus::kDecodeError &&
+                  state->remaining_payload() == 0))
+              << "\n status=" << status
+              << "; remaining_payload=" << state->remaining_payload();
+          payload_state_ = PayloadState::kResumeDecodingFixedFields;
+          return status;
+        }
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kReadOpaqueData:
+        // The opaque data is all the remains to be decoded, so anything left
+        // in the decode buffer is opaque data.
+        avail = db->Remaining();
+        if (avail > 0) {
+          state->listener()->OnGoAwayOpaqueData(db->cursor(), avail);
+          db->AdvanceCursor(avail);
+          state->ConsumePayload(avail);
+        }
+        if (state->remaining_payload() > 0) {
+          payload_state_ = PayloadState::kReadOpaqueData;
+          return DecodeStatus::kDecodeInProgress;
+        }
+        state->listener()->OnGoAwayEnd();
+        return DecodeStatus::kDecodeDone;
+
+      case PayloadState::kResumeDecodingFixedFields:
+        status = state->ResumeDecodingStructureInPayload(&goaway_fields_, db);
+        payload_state_ = PayloadState::kHandleFixedFieldsStatus;
+        continue;
+    }
+    HTTP2_BUG << "PayloadState: " << payload_state_;
+  }
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/goaway_payload_decoder.h b/http2/decoder/payload_decoders/goaway_payload_decoder.h
new file mode 100644
index 0000000..7a50873
--- /dev/null
+++ b/http2/decoder/payload_decoders/goaway_payload_decoder.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_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a GOAWAY frame.
+
+// TODO(jamessynge): Sweep through all payload decoders, changing the names of
+// the PayloadState enums so that they are really states, and not actions.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class GoAwayPayloadDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE GoAwayPayloadDecoder {
+ public:
+  // States during decoding of a GOAWAY frame.
+  enum class PayloadState {
+    // At the start of the GOAWAY frame payload, ready to start decoding the
+    // fixed size fields into goaway_fields_.
+    kStartDecodingFixedFields,
+
+    // Handle the DecodeStatus returned from starting or resuming the
+    // decoding of Http2GoAwayFields into goaway_fields_. If complete,
+    // calls OnGoAwayStart.
+    kHandleFixedFieldsStatus,
+
+    // Report the Opaque Data portion of the payload to the listener's
+    // OnGoAwayOpaqueData method, and call OnGoAwayEnd when the end of the
+    // payload is reached.
+    kReadOpaqueData,
+
+    // The fixed size fields weren't all available when the decoder first
+    // tried to decode them (state kStartDecodingFixedFields); this state
+    // resumes the decoding when ResumeDecodingPayload is called later.
+    kResumeDecodingFixedFields,
+  };
+
+  // Starts the decoding of a GOAWAY frame's payload, and completes it if
+  // the entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a GOAWAY frame's payload that has been split across
+  // decode buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+ private:
+  friend class test::GoAwayPayloadDecoderPeer;
+
+  Http2GoAwayFields goaway_fields_;
+  PayloadState payload_state_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc b/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc
new file mode 100644
index 0000000..1df5214
--- /dev/null
+++ b/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc
@@ -0,0 +1,107 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class GoAwayPayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() { return Http2FrameType::GOAWAY; }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+  void OnGoAwayStart(const Http2FrameHeader& header,
+                     const Http2GoAwayFields& goaway) override {
+    VLOG(1) << "OnGoAwayStart header: " << header << "; goaway: " << goaway;
+    StartFrame(header)->OnGoAwayStart(header, goaway);
+  }
+
+  void OnGoAwayOpaqueData(const char* data, size_t len) override {
+    VLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+    CurrentFrame()->OnGoAwayOpaqueData(data, len);
+  }
+
+  void OnGoAwayEnd() override {
+    VLOG(1) << "OnGoAwayEnd";
+    EndFrame()->OnGoAwayEnd();
+  }
+
+  void OnFrameSizeError(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnFrameSizeError: " << header;
+    FrameError(header)->OnFrameSizeError(header);
+  }
+};
+
+class GoAwayPayloadDecoderTest
+    : public AbstractPayloadDecoderTest<GoAwayPayloadDecoder,
+                                        GoAwayPayloadDecoderPeer,
+                                        Listener> {};
+
+// Confirm we get an error if the payload is not long enough to hold
+// Http2GoAwayFields.
+TEST_F(GoAwayPayloadDecoderTest, Truncated) {
+  auto approve_size = [](size_t size) {
+    return size != Http2GoAwayFields::EncodedSize();
+  };
+  Http2FrameBuilder fb;
+  fb.Append(Http2GoAwayFields(123, Http2ErrorCode::ENHANCE_YOUR_CALM));
+  EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+class GoAwayOpaqueDataLengthTests
+    : public GoAwayPayloadDecoderTest,
+      public ::testing::WithParamInterface<uint32_t> {
+ protected:
+  GoAwayOpaqueDataLengthTests() : length_(GetParam()) {
+    VLOG(1) << "################  length_=" << length_ << "  ################";
+  }
+
+  const uint32_t length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousLengths,
+                        GoAwayOpaqueDataLengthTests,
+                        ::testing::Values(0, 1, 2, 3, 4, 5, 6));
+
+TEST_P(GoAwayOpaqueDataLengthTests, ValidLength) {
+  Http2GoAwayFields goaway;
+  Randomize(&goaway, RandomPtr());
+  Http2String opaque_data = Random().RandString(length_);
+  Http2FrameBuilder fb;
+  fb.Append(goaway);
+  fb.Append(opaque_data);
+  Http2FrameHeader header(fb.size(), Http2FrameType::GOAWAY, RandFlags(),
+                          RandStreamId());
+  set_frame_header(header);
+  FrameParts expected(header, opaque_data);
+  expected.SetOptGoaway(goaway);
+  ASSERT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/headers_payload_decoder.cc b/http2/decoder/payload_decoders/headers_payload_decoder.cc
new file mode 100644
index 0000000..ecaf0fa
--- /dev/null
+++ b/http2/decoder/payload_decoders/headers_payload_decoder.cc
@@ -0,0 +1,175 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+                         HeadersPayloadDecoder::PayloadState v) {
+  switch (v) {
+    case HeadersPayloadDecoder::PayloadState::kReadPadLength:
+      return out << "kReadPadLength";
+    case HeadersPayloadDecoder::PayloadState::kStartDecodingPriorityFields:
+      return out << "kStartDecodingPriorityFields";
+    case HeadersPayloadDecoder::PayloadState::kResumeDecodingPriorityFields:
+      return out << "kResumeDecodingPriorityFields";
+    case HeadersPayloadDecoder::PayloadState::kReadPayload:
+      return out << "kReadPayload";
+    case HeadersPayloadDecoder::PayloadState::kSkipPadding:
+      return out << "kSkipPadding";
+  }
+  // Since the value doesn't come over the wire, only a programming bug should
+  // result in reaching this point.
+  int unknown = static_cast<int>(v);
+  HTTP2_BUG << "Invalid HeadersPayloadDecoder::PayloadState: " << unknown;
+  return out << "HeadersPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus HeadersPayloadDecoder::StartDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  const Http2FrameHeader& frame_header = state->frame_header();
+  const uint32_t total_length = frame_header.payload_length;
+
+  DVLOG(2) << "HeadersPayloadDecoder::StartDecodingPayload: " << frame_header;
+
+  DCHECK_EQ(Http2FrameType::HEADERS, frame_header.type);
+  DCHECK_LE(db->Remaining(), total_length);
+  DCHECK_EQ(0, frame_header.flags &
+                   ~(Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS |
+                     Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY));
+
+  // Special case for HEADERS frames that contain only the HPACK block
+  // (fragment or whole) and that fit fully into the decode buffer.
+  // Why? Unencoded browser GET requests are typically under 1K and HPACK
+  // commonly shrinks request headers by 80%, so we can expect this to
+  // be common.
+  // TODO(jamessynge) Add counters here and to Spdy for determining how
+  // common this situation is. A possible approach is to create a
+  // Http2FrameDecoderListener that counts the callbacks and then forwards
+  // them on to another listener, which makes it easy to add and remove
+  // counting on a connection or even frame basis.
+
+  // PADDED and PRIORITY both extra steps to decode, but if neither flag is
+  // set then we can decode faster.
+  const auto payload_flags = Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY;
+  if (!frame_header.HasAnyFlags(payload_flags)) {
+    DVLOG(2) << "StartDecodingPayload !IsPadded && !HasPriority";
+    if (db->Remaining() == total_length) {
+      DVLOG(2) << "StartDecodingPayload all present";
+      // Note that we don't cache the listener field so that the callee can
+      // replace it if the frame is bad.
+      // If this case is common enough, consider combining the 3 callbacks
+      // into one, especially if END_HEADERS is also set.
+      state->listener()->OnHeadersStart(frame_header);
+      if (total_length > 0) {
+        state->listener()->OnHpackFragment(db->cursor(), total_length);
+        db->AdvanceCursor(total_length);
+      }
+      state->listener()->OnHeadersEnd();
+      return DecodeStatus::kDecodeDone;
+    }
+    payload_state_ = PayloadState::kReadPayload;
+  } else if (frame_header.IsPadded()) {
+    payload_state_ = PayloadState::kReadPadLength;
+  } else {
+    DCHECK(frame_header.HasPriority()) << frame_header;
+    payload_state_ = PayloadState::kStartDecodingPriorityFields;
+  }
+  state->InitializeRemainders();
+  state->listener()->OnHeadersStart(frame_header);
+  return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus HeadersPayloadDecoder::ResumeDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "HeadersPayloadDecoder::ResumeDecodingPayload "
+           << "remaining_payload=" << state->remaining_payload()
+           << "; db->Remaining=" << db->Remaining();
+
+  const Http2FrameHeader& frame_header = state->frame_header();
+
+  DCHECK_EQ(Http2FrameType::HEADERS, frame_header.type);
+  DCHECK_LE(state->remaining_payload_and_padding(),
+            frame_header.payload_length);
+  DCHECK_LE(db->Remaining(), state->remaining_payload_and_padding());
+  DecodeStatus status;
+  size_t avail;
+  while (true) {
+    DVLOG(2) << "HeadersPayloadDecoder::ResumeDecodingPayload payload_state_="
+             << payload_state_;
+    switch (payload_state_) {
+      case PayloadState::kReadPadLength:
+        // ReadPadLength handles the OnPadLength callback, and updating the
+        // remaining_payload and remaining_padding fields. If the amount of
+        // padding is too large to fit in the frame's payload, ReadPadLength
+        // instead calls OnPaddingTooLong and returns kDecodeError.
+        status = state->ReadPadLength(db, /*report_pad_length*/ true);
+        if (status != DecodeStatus::kDecodeDone) {
+          return status;
+        }
+        if (!frame_header.HasPriority()) {
+          payload_state_ = PayloadState::kReadPayload;
+          continue;
+        }
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kStartDecodingPriorityFields:
+        status = state->StartDecodingStructureInPayload(&priority_fields_, db);
+        if (status != DecodeStatus::kDecodeDone) {
+          payload_state_ = PayloadState::kResumeDecodingPriorityFields;
+          return status;
+        }
+        state->listener()->OnHeadersPriority(priority_fields_);
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kReadPayload:
+        avail = state->AvailablePayload(db);
+        if (avail > 0) {
+          state->listener()->OnHpackFragment(db->cursor(), avail);
+          db->AdvanceCursor(avail);
+          state->ConsumePayload(avail);
+        }
+        if (state->remaining_payload() > 0) {
+          payload_state_ = PayloadState::kReadPayload;
+          return DecodeStatus::kDecodeInProgress;
+        }
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kSkipPadding:
+        // SkipPadding handles the OnPadding callback.
+        if (state->SkipPadding(db)) {
+          state->listener()->OnHeadersEnd();
+          return DecodeStatus::kDecodeDone;
+        }
+        payload_state_ = PayloadState::kSkipPadding;
+        return DecodeStatus::kDecodeInProgress;
+
+      case PayloadState::kResumeDecodingPriorityFields:
+        status = state->ResumeDecodingStructureInPayload(&priority_fields_, db);
+        if (status != DecodeStatus::kDecodeDone) {
+          return status;
+        }
+        state->listener()->OnHeadersPriority(priority_fields_);
+        payload_state_ = PayloadState::kReadPayload;
+        continue;
+    }
+    HTTP2_BUG << "PayloadState: " << payload_state_;
+  }
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/headers_payload_decoder.h b/http2/decoder/payload_decoders/headers_payload_decoder.h
new file mode 100644
index 0000000..d3cdfe5
--- /dev/null
+++ b/http2/decoder/payload_decoders/headers_payload_decoder.h
@@ -0,0 +1,67 @@
+// 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_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a HEADERS frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class HeadersPayloadDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE HeadersPayloadDecoder {
+ public:
+  // States during decoding of a HEADERS frame, unless the fast path kicks
+  // in, in which case the state machine will be bypassed.
+  enum class PayloadState {
+    // The PADDED flag is set, and we now need to read the Pad Length field
+    // (the first byte of the payload, after the common frame header).
+    kReadPadLength,
+
+    // The PRIORITY flag is set, and we now need to read the fixed size priority
+    // fields (E, Stream Dependency, Weight) into priority_fields_.  Calls on
+    // OnHeadersPriority if completely decodes those fields.
+    kStartDecodingPriorityFields,
+
+    // The decoder passes the non-padding portion of the remaining payload
+    // (i.e. the HPACK block fragment) to the listener's OnHpackFragment method.
+    kReadPayload,
+
+    // The decoder has finished with the HPACK block fragment, and is now
+    // ready to skip the trailing padding, if the frame has any.
+    kSkipPadding,
+
+    // The fixed size fields weren't all available when the decoder first tried
+    // to decode them (state kStartDecodingPriorityFields); this state resumes
+    // the decoding when ResumeDecodingPayload is called later.
+    kResumeDecodingPriorityFields,
+  };
+
+  // Starts the decoding of a HEADERS frame's payload, and completes it if
+  // the entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a HEADERS frame's payload that has been split across
+  // decode buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+ private:
+  friend class test::HeadersPayloadDecoderPeer;
+
+  PayloadState payload_state_;
+  Http2PriorityFields priority_fields_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/headers_payload_decoder_test.cc b/http2/decoder/payload_decoders/headers_payload_decoder_test.cc
new file mode 100644
index 0000000..a6fcb65
--- /dev/null
+++ b/http2/decoder/payload_decoders/headers_payload_decoder_test.cc
@@ -0,0 +1,158 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class HeadersPayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() {
+    return Http2FrameType::HEADERS;
+  }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() {
+    return Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY;
+  }
+};
+
+namespace {
+
+// Listener handles all On* methods that are expected to be called. If any other
+// On* methods of Http2FrameDecoderListener is called then the test fails; this
+// is achieved by way of FailingHttp2FrameDecoderListener, the base class of
+// FramePartsCollector.
+// These On* methods make use of StartFrame, EndFrame, etc. of the base class
+// to create and access to FrameParts instance(s) that will record the details.
+// After decoding, the test validation code can access the FramePart instance(s)
+// via the public methods of FramePartsCollector.
+struct Listener : public FramePartsCollector {
+  void OnHeadersStart(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnHeadersStart: " << header;
+    StartFrame(header)->OnHeadersStart(header);
+  }
+
+  void OnHeadersPriority(const Http2PriorityFields& priority) override {
+    VLOG(1) << "OnHeadersPriority: " << priority;
+    CurrentFrame()->OnHeadersPriority(priority);
+  }
+
+  void OnHpackFragment(const char* data, size_t len) override {
+    VLOG(1) << "OnHpackFragment: len=" << len;
+    CurrentFrame()->OnHpackFragment(data, len);
+  }
+
+  void OnHeadersEnd() override {
+    VLOG(1) << "OnHeadersEnd";
+    EndFrame()->OnHeadersEnd();
+  }
+
+  void OnPadLength(size_t pad_length) override {
+    VLOG(1) << "OnPadLength: " << pad_length;
+    CurrentFrame()->OnPadLength(pad_length);
+  }
+
+  void OnPadding(const char* padding, size_t skipped_length) override {
+    VLOG(1) << "OnPadding: " << skipped_length;
+    CurrentFrame()->OnPadding(padding, skipped_length);
+  }
+
+  void OnPaddingTooLong(const Http2FrameHeader& header,
+                        size_t missing_length) override {
+    VLOG(1) << "OnPaddingTooLong: " << header
+            << "; missing_length: " << missing_length;
+    FrameError(header)->OnPaddingTooLong(header, missing_length);
+  }
+
+  void OnFrameSizeError(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnFrameSizeError: " << header;
+    FrameError(header)->OnFrameSizeError(header);
+  }
+};
+
+class HeadersPayloadDecoderTest
+    : public AbstractPaddablePayloadDecoderTest<HeadersPayloadDecoder,
+                                                HeadersPayloadDecoderPeer,
+                                                Listener> {};
+
+INSTANTIATE_TEST_CASE_P(VariousPadLengths,
+                        HeadersPayloadDecoderTest,
+                        ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256));
+
+// Decode various sizes of (fake) HPACK payload, both with and without the
+// PRIORITY flag set.
+TEST_P(HeadersPayloadDecoderTest, VariousHpackPayloadSizes) {
+  for (size_t hpack_size : {0, 1, 2, 3, 255, 256, 1024}) {
+    LOG(INFO) << "###########   hpack_size = " << hpack_size << "  ###########";
+    Http2PriorityFields priority(RandStreamId(), 1 + Random().Rand8(),
+                                 Random().OneIn(2));
+
+    for (bool has_priority : {false, true}) {
+      Reset();
+      ASSERT_EQ(IsPadded() ? 1u : 0u, frame_builder_.size());
+      uint8_t flags = RandFlags();
+      if (has_priority) {
+        flags |= Http2FrameFlag::PRIORITY;
+        frame_builder_.Append(priority);
+      }
+
+      Http2String hpack_payload = Random().RandString(hpack_size);
+      frame_builder_.Append(hpack_payload);
+
+      MaybeAppendTrailingPadding();
+      Http2FrameHeader frame_header(frame_builder_.size(),
+                                    Http2FrameType::HEADERS, flags,
+                                    RandStreamId());
+      set_frame_header(frame_header);
+      ScrubFlagsOfHeader(&frame_header);
+      FrameParts expected(frame_header, hpack_payload, total_pad_length_);
+      if (has_priority) {
+        expected.SetOptPriority(priority);
+      }
+      EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(),
+                                                      expected));
+    }
+  }
+}
+
+// Confirm we get an error if the PRIORITY flag is set but the payload is
+// not long enough, regardless of the amount of (valid) padding.
+TEST_P(HeadersPayloadDecoderTest, Truncated) {
+  auto approve_size = [](size_t size) {
+    return size != Http2PriorityFields::EncodedSize();
+  };
+  Http2FrameBuilder fb;
+  fb.Append(Http2PriorityFields(RandStreamId(), 1 + Random().Rand8(),
+                                Random().OneIn(2)));
+  EXPECT_TRUE(VerifyDetectsMultipleFrameSizeErrors(
+      Http2FrameFlag::PRIORITY, fb.buffer(), approve_size, total_pad_length_));
+}
+
+// Confirm we get an error if the PADDED flag is set but the payload is not
+// long enough to hold even the Pad Length amount of padding.
+TEST_P(HeadersPayloadDecoderTest, PaddingTooLong) {
+  EXPECT_TRUE(VerifyDetectsPaddingTooLong());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc b/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc
new file mode 100644
index 0000000..88e1b98
--- /dev/null
+++ b/http2/decoder/payload_decoders/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 "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.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) {
+  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.
+  Http2DefaultReconstructObject(&frame_decoder_state_, RandomPtr());
+  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_);
+  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) {
+  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(
+    Http2StringPiece 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 = Http2StringPiece(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/http2/decoder/payload_decoders/payload_decoder_base_test_util.h b/http2/decoder/payload_decoders/payload_decoder_base_test_util.h
new file mode 100644
index 0000000..8297e70
--- /dev/null
+++ b/http2/decoder/payload_decoders/payload_decoder_base_test_util.h
@@ -0,0 +1,455 @@
+// 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_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
+
+// Base class for testing concrete payload decoder classes.
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.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 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) {
+      VLOG(2) << "set_frame_header: " << frame_header_;
+    }
+    frame_header_ = header;
+    frame_header_is_set_ = true;
+  }
+
+  FrameDecoderState* mutable_state() { return &frame_decoder_state_; }
+
+  // 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(
+      Http2StringPiece 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_;
+  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 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 {
+    Http2DefaultReconstructObject(&payload_decoder_, RandomPtr());
+  }
+
+  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 {
+    DVLOG(2) << "StartDecodingPayload, db->Remaining=" << db->Remaining();
+    return payload_decoder_.StartDecodingPayload(mutable_state(), db);
+  }
+
+  // Resume decoding the payload.
+  DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) override {
+    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(
+      Http2StringPiece payload,
+      const FrameParts& expected) {
+    auto validator = [&expected, this]() -> AssertionResult {
+      VERIFY_FALSE(listener_.IsInProgress());
+      VERIFY_EQ(1u, listener_.size());
+      VERIFY_AND_RETURN_SUCCESS(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(
+      Http2StringPiece 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 {
+      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);
+    };
+    VERIFY_AND_RETURN_SUCCESS(
+        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,
+      Http2StringPiece 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;
+      }
+      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,
+      Http2StringPiece 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);
+    VERIFY_AND_RETURN_SUCCESS(VerifyDetectsMultipleFrameSizeErrors(
+        required_flags, unpadded_payload, approve_size, 0));
+  }
+
+  Listener listener_;
+  union {
+    // Confirm at compile time that Decoder can be in an anonymous union,
+    // i.e. complain loudly if Decoder has members that prevent this, as it
+    // becomes annoying and possibly difficult to deal with non-anonymous
+    // unions and such union members.
+    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 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()) {
+    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(
+      Http2StringPiece 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& input,
+            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();
+    };
+    VERIFY_AND_RETURN_SUCCESS(
+        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());
+      VLOG(1) << "fb.size=" << fb.size();
+      // Pick a random length for the payload that is shorter than neccesary.
+      payload_length = Random().Uniform(fb.size());
+    }
+
+    VLOG(1) << "payload_length=" << payload_length;
+    Http2String 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;
+    VLOG(1) << "missing_length=" << missing_length;
+
+    const Http2FrameHeader header(payload_length, DecoderPeer::FrameType(),
+                                  flags, RandStreamId());
+    VERIFY_AND_RETURN_SUCCESS(
+        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_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
diff --git a/http2/decoder/payload_decoders/ping_payload_decoder.cc b/http2/decoder/payload_decoders/ping_payload_decoder.cc
new file mode 100644
index 0000000..8d98046
--- /dev/null
+++ b/http2/decoder/payload_decoders/ping_payload_decoder.cc
@@ -0,0 +1,89 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+
+namespace http2 {
+namespace {
+constexpr auto kOpaqueSize = Http2PingFields::EncodedSize();
+}
+
+DecodeStatus PingPayloadDecoder::StartDecodingPayload(FrameDecoderState* state,
+                                                      DecodeBuffer* db) {
+  const Http2FrameHeader& frame_header = state->frame_header();
+  const uint32_t total_length = frame_header.payload_length;
+
+  DVLOG(2) << "PingPayloadDecoder::StartDecodingPayload: " << frame_header;
+  DCHECK_EQ(Http2FrameType::PING, frame_header.type);
+  DCHECK_LE(db->Remaining(), total_length);
+  DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::ACK));
+
+  // Is the payload entirely in the decode buffer and is it the correct size?
+  // Given the size of the header and payload (17 bytes total), this is most
+  // likely the case the vast majority of the time.
+  if (db->Remaining() == kOpaqueSize && total_length == kOpaqueSize) {
+    // Special case this situation as it allows us to avoid any copying;
+    // the other path makes two copies, first into the buffer in
+    // Http2StructureDecoder as it accumulates the 8 bytes of opaque data,
+    // and a second copy into the Http2PingFields member of in this class.
+    // This supports the claim that this decoder is (mostly) non-buffering.
+    static_assert(sizeof(Http2PingFields) == kOpaqueSize,
+                  "If not, then can't enter this block!");
+    auto* ping = reinterpret_cast<const Http2PingFields*>(db->cursor());
+    if (frame_header.IsAck()) {
+      state->listener()->OnPingAck(frame_header, *ping);
+    } else {
+      state->listener()->OnPing(frame_header, *ping);
+    }
+    db->AdvanceCursor(kOpaqueSize);
+    return DecodeStatus::kDecodeDone;
+  }
+  state->InitializeRemainders();
+  return HandleStatus(
+      state, state->StartDecodingStructureInPayload(&ping_fields_, db));
+}
+
+DecodeStatus PingPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state,
+                                                       DecodeBuffer* db) {
+  DVLOG(2) << "ResumeDecodingPayload: remaining_payload="
+           << state->remaining_payload();
+  DCHECK_EQ(Http2FrameType::PING, state->frame_header().type);
+  DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+  return HandleStatus(
+      state, state->ResumeDecodingStructureInPayload(&ping_fields_, db));
+}
+
+DecodeStatus PingPayloadDecoder::HandleStatus(FrameDecoderState* state,
+                                              DecodeStatus status) {
+  DVLOG(2) << "HandleStatus: status=" << status
+           << "; remaining_payload=" << state->remaining_payload();
+  if (status == DecodeStatus::kDecodeDone) {
+    if (state->remaining_payload() == 0) {
+      const Http2FrameHeader& frame_header = state->frame_header();
+      if (frame_header.IsAck()) {
+        state->listener()->OnPingAck(frame_header, ping_fields_);
+      } else {
+        state->listener()->OnPing(frame_header, ping_fields_);
+      }
+      return DecodeStatus::kDecodeDone;
+    }
+    // Payload is too long.
+    return state->ReportFrameSizeError();
+  }
+  // Not done decoding the structure. Either we've got more payload to decode,
+  // or we've run out because the payload is too short.
+  DCHECK(
+      (status == DecodeStatus::kDecodeInProgress &&
+       state->remaining_payload() > 0) ||
+      (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+      << "\n status=" << status
+      << "; remaining_payload=" << state->remaining_payload();
+  return status;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/ping_payload_decoder.h b/http2/decoder/payload_decoders/ping_payload_decoder.h
new file mode 100644
index 0000000..84704fb
--- /dev/null
+++ b/http2/decoder/payload_decoders/ping_payload_decoder.h
@@ -0,0 +1,43 @@
+// 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_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a PING frame; for the RFC, see:
+//     http://httpwg.org/specs/rfc7540.html#PING
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class PingPayloadDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE PingPayloadDecoder {
+ public:
+  // Starts the decoding of a PING frame's payload, and completes it if the
+  // entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a PING frame's payload that has been split across
+  // decode buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+ private:
+  friend class test::PingPayloadDecoderPeer;
+
+  DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+  Http2PingFields ping_fields_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/ping_payload_decoder_test.cc b/http2/decoder/payload_decoders/ping_payload_decoder_test.cc
new file mode 100644
index 0000000..34833b2
--- /dev/null
+++ b/http2/decoder/payload_decoders/ping_payload_decoder_test.cc
@@ -0,0 +1,110 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class PingPayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() { return Http2FrameType::PING; }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+  void OnPing(const Http2FrameHeader& header,
+              const Http2PingFields& ping) override {
+    VLOG(1) << "OnPing: " << header << "; " << ping;
+    StartAndEndFrame(header)->OnPing(header, ping);
+  }
+
+  void OnPingAck(const Http2FrameHeader& header,
+                 const Http2PingFields& ping) override {
+    VLOG(1) << "OnPingAck: " << header << "; " << ping;
+    StartAndEndFrame(header)->OnPingAck(header, ping);
+  }
+
+  void OnFrameSizeError(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnFrameSizeError: " << header;
+    FrameError(header)->OnFrameSizeError(header);
+  }
+};
+
+class PingPayloadDecoderTest
+    : public AbstractPayloadDecoderTest<PingPayloadDecoder,
+                                        PingPayloadDecoderPeer,
+                                        Listener> {
+ protected:
+  Http2PingFields RandPingFields() {
+    Http2PingFields fields;
+    test::Randomize(&fields, RandomPtr());
+    return fields;
+  }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2PingFields.
+TEST_F(PingPayloadDecoderTest, WrongSize) {
+  auto approve_size = [](size_t size) {
+    return size != Http2PingFields::EncodedSize();
+  };
+  Http2FrameBuilder fb;
+  fb.Append(RandPingFields());
+  fb.Append(RandPingFields());
+  fb.Append(RandPingFields());
+  EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(PingPayloadDecoderTest, Ping) {
+  for (int n = 0; n < 100; ++n) {
+    Http2PingFields fields = RandPingFields();
+    Http2FrameBuilder fb;
+    fb.Append(fields);
+    Http2FrameHeader header(fb.size(), Http2FrameType::PING,
+                            RandFlags() & ~Http2FrameFlag::ACK, RandStreamId());
+    set_frame_header(header);
+    FrameParts expected(header);
+    expected.SetOptPing(fields);
+    EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+  }
+}
+
+TEST_F(PingPayloadDecoderTest, PingAck) {
+  for (int n = 0; n < 100; ++n) {
+    Http2PingFields fields;
+    Randomize(&fields, RandomPtr());
+    Http2FrameBuilder fb;
+    fb.Append(fields);
+    Http2FrameHeader header(fb.size(), Http2FrameType::PING,
+                            RandFlags() | Http2FrameFlag::ACK, RandStreamId());
+    set_frame_header(header);
+    FrameParts expected(header);
+    expected.SetOptPing(fields);
+    EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/priority_payload_decoder.cc b/http2/decoder/payload_decoders/priority_payload_decoder.cc
new file mode 100644
index 0000000..7be1c95
--- /dev/null
+++ b/http2/decoder/payload_decoders/priority_payload_decoder.cc
@@ -0,0 +1,64 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus PriorityPayloadDecoder::StartDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "PriorityPayloadDecoder::StartDecodingPayload: "
+           << state->frame_header();
+  DCHECK_EQ(Http2FrameType::PRIORITY, state->frame_header().type);
+  DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+  // PRIORITY frames have no flags.
+  DCHECK_EQ(0, state->frame_header().flags);
+  state->InitializeRemainders();
+  return HandleStatus(
+      state, state->StartDecodingStructureInPayload(&priority_fields_, db));
+}
+
+DecodeStatus PriorityPayloadDecoder::ResumeDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "PriorityPayloadDecoder::ResumeDecodingPayload"
+           << "  remaining_payload=" << state->remaining_payload()
+           << "  db->Remaining=" << db->Remaining();
+  DCHECK_EQ(Http2FrameType::PRIORITY, state->frame_header().type);
+  DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+  return HandleStatus(
+      state, state->ResumeDecodingStructureInPayload(&priority_fields_, db));
+}
+
+DecodeStatus PriorityPayloadDecoder::HandleStatus(FrameDecoderState* state,
+                                                  DecodeStatus status) {
+  if (status == DecodeStatus::kDecodeDone) {
+    if (state->remaining_payload() == 0) {
+      state->listener()->OnPriorityFrame(state->frame_header(),
+                                         priority_fields_);
+      return DecodeStatus::kDecodeDone;
+    }
+    // Payload is too long.
+    return state->ReportFrameSizeError();
+  }
+  // Not done decoding the structure. Either we've got more payload to decode,
+  // or we've run out because the payload is too short, in which case
+  // OnFrameSizeError will have already been called.
+  DCHECK(
+      (status == DecodeStatus::kDecodeInProgress &&
+       state->remaining_payload() > 0) ||
+      (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+      << "\n status=" << status
+      << "; remaining_payload=" << state->remaining_payload();
+  return status;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/priority_payload_decoder.h b/http2/decoder/payload_decoders/priority_payload_decoder.h
new file mode 100644
index 0000000..921eefe
--- /dev/null
+++ b/http2/decoder/payload_decoders/priority_payload_decoder.h
@@ -0,0 +1,44 @@
+// 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_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a PRIORITY frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class PriorityPayloadDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE PriorityPayloadDecoder {
+ public:
+  // Starts the decoding of a PRIORITY frame's payload, and completes it if
+  // the entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a PRIORITY frame that has been split across decode
+  // buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+ private:
+  friend class test::PriorityPayloadDecoderPeer;
+
+  // Determines whether to report the PRIORITY to the listener, wait for more
+  // input, or to report a Frame Size Error.
+  DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+  Http2PriorityFields priority_fields_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/priority_payload_decoder_test.cc b/http2/decoder/payload_decoders/priority_payload_decoder_test.cc
new file mode 100644
index 0000000..4e44eba
--- /dev/null
+++ b/http2/decoder/payload_decoders/priority_payload_decoder_test.cc
@@ -0,0 +1,90 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class PriorityPayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() {
+    return Http2FrameType::PRIORITY;
+  }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+  void OnPriorityFrame(const Http2FrameHeader& header,
+                       const Http2PriorityFields& priority_fields) override {
+    VLOG(1) << "OnPriority: " << header << "; " << priority_fields;
+    StartAndEndFrame(header)->OnPriorityFrame(header, priority_fields);
+  }
+
+  void OnFrameSizeError(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnFrameSizeError: " << header;
+    FrameError(header)->OnFrameSizeError(header);
+  }
+};
+
+class PriorityPayloadDecoderTest
+    : public AbstractPayloadDecoderTest<PriorityPayloadDecoder,
+                                        PriorityPayloadDecoderPeer,
+                                        Listener> {
+ protected:
+  Http2PriorityFields RandPriorityFields() {
+    Http2PriorityFields fields;
+    test::Randomize(&fields, RandomPtr());
+    return fields;
+  }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2PriorityFields.
+TEST_F(PriorityPayloadDecoderTest, WrongSize) {
+  auto approve_size = [](size_t size) {
+    return size != Http2PriorityFields::EncodedSize();
+  };
+  Http2FrameBuilder fb;
+  fb.Append(RandPriorityFields());
+  fb.Append(RandPriorityFields());
+  EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(PriorityPayloadDecoderTest, VariousPayloads) {
+  for (int n = 0; n < 100; ++n) {
+    Http2PriorityFields fields = RandPriorityFields();
+    Http2FrameBuilder fb;
+    fb.Append(fields);
+    Http2FrameHeader header(fb.size(), Http2FrameType::PRIORITY, RandFlags(),
+                            RandStreamId());
+    set_frame_header(header);
+    FrameParts expected(header);
+    expected.SetOptPriority(fields);
+    EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/push_promise_payload_decoder.cc b/http2/decoder/payload_decoders/push_promise_payload_decoder.cc
new file mode 100644
index 0000000..cec1c07
--- /dev/null
+++ b/http2/decoder/payload_decoders/push_promise_payload_decoder.cc
@@ -0,0 +1,172 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+                         PushPromisePayloadDecoder::PayloadState v) {
+  switch (v) {
+    case PushPromisePayloadDecoder::PayloadState::kReadPadLength:
+      return out << "kReadPadLength";
+    case PushPromisePayloadDecoder::PayloadState::
+        kStartDecodingPushPromiseFields:
+      return out << "kStartDecodingPushPromiseFields";
+    case PushPromisePayloadDecoder::PayloadState::kReadPayload:
+      return out << "kReadPayload";
+    case PushPromisePayloadDecoder::PayloadState::kSkipPadding:
+      return out << "kSkipPadding";
+    case PushPromisePayloadDecoder::PayloadState::
+        kResumeDecodingPushPromiseFields:
+      return out << "kResumeDecodingPushPromiseFields";
+  }
+  return out << static_cast<int>(v);
+}
+
+DecodeStatus PushPromisePayloadDecoder::StartDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  const Http2FrameHeader& frame_header = state->frame_header();
+  const uint32_t total_length = frame_header.payload_length;
+
+  DVLOG(2) << "PushPromisePayloadDecoder::StartDecodingPayload: "
+           << frame_header;
+
+  DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type);
+  DCHECK_LE(db->Remaining(), total_length);
+  DCHECK_EQ(0, frame_header.flags &
+                   ~(Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED));
+
+  if (!frame_header.IsPadded()) {
+    // If it turns out that PUSH_PROMISE frames without padding are sufficiently
+    // common, and that they are usually short enough that they fit entirely
+    // into one DecodeBuffer, we can detect that here and implement a special
+    // case, avoiding the state machine in ResumeDecodingPayload.
+    payload_state_ = PayloadState::kStartDecodingPushPromiseFields;
+  } else {
+    payload_state_ = PayloadState::kReadPadLength;
+  }
+  state->InitializeRemainders();
+  return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus PushPromisePayloadDecoder::ResumeDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "UnknownPayloadDecoder::ResumeDecodingPayload"
+           << "  remaining_payload=" << state->remaining_payload()
+           << "  db->Remaining=" << db->Remaining();
+
+  const Http2FrameHeader& frame_header = state->frame_header();
+  DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type);
+  DCHECK_LE(state->remaining_payload(), frame_header.payload_length);
+  DCHECK_LE(db->Remaining(), frame_header.payload_length);
+
+  DecodeStatus status;
+  while (true) {
+    DVLOG(2)
+        << "PushPromisePayloadDecoder::ResumeDecodingPayload payload_state_="
+        << payload_state_;
+    switch (payload_state_) {
+      case PayloadState::kReadPadLength:
+        DCHECK_EQ(state->remaining_payload(), frame_header.payload_length);
+        // ReadPadLength handles the OnPadLength callback, and updating the
+        // remaining_payload and remaining_padding fields. If the amount of
+        // padding is too large to fit in the frame's payload, ReadPadLength
+        // instead calls OnPaddingTooLong and returns kDecodeError.
+        // Suppress the call to OnPadLength because we haven't yet called
+        // OnPushPromiseStart, which needs to wait until we've decoded the
+        // Promised Stream ID.
+        status = state->ReadPadLength(db, /*report_pad_length*/ false);
+        if (status != DecodeStatus::kDecodeDone) {
+          payload_state_ = PayloadState::kReadPadLength;
+          return status;
+        }
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kStartDecodingPushPromiseFields:
+        status =
+            state->StartDecodingStructureInPayload(&push_promise_fields_, db);
+        if (status != DecodeStatus::kDecodeDone) {
+          payload_state_ = PayloadState::kResumeDecodingPushPromiseFields;
+          return status;
+        }
+        // Finished decoding the Promised Stream ID. Can now tell the listener
+        // that we're starting to decode a PUSH_PROMISE frame.
+        ReportPushPromise(state);
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kReadPayload:
+        DCHECK_LT(state->remaining_payload(), frame_header.payload_length);
+        DCHECK_LE(state->remaining_payload(),
+                  frame_header.payload_length -
+                      Http2PushPromiseFields::EncodedSize());
+        DCHECK_LE(
+            state->remaining_payload(),
+            frame_header.payload_length -
+                Http2PushPromiseFields::EncodedSize() -
+                (frame_header.IsPadded() ? (1 + state->remaining_padding())
+                                         : 0));
+        {
+          size_t avail = state->AvailablePayload(db);
+          state->listener()->OnHpackFragment(db->cursor(), avail);
+          db->AdvanceCursor(avail);
+          state->ConsumePayload(avail);
+        }
+        if (state->remaining_payload() > 0) {
+          payload_state_ = PayloadState::kReadPayload;
+          return DecodeStatus::kDecodeInProgress;
+        }
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kSkipPadding:
+        // SkipPadding handles the OnPadding callback.
+        if (state->SkipPadding(db)) {
+          state->listener()->OnPushPromiseEnd();
+          return DecodeStatus::kDecodeDone;
+        }
+        payload_state_ = PayloadState::kSkipPadding;
+        return DecodeStatus::kDecodeInProgress;
+
+      case PayloadState::kResumeDecodingPushPromiseFields:
+        status =
+            state->ResumeDecodingStructureInPayload(&push_promise_fields_, db);
+        if (status == DecodeStatus::kDecodeDone) {
+          // Finished decoding the Promised Stream ID. Can now tell the listener
+          // that we're starting to decode a PUSH_PROMISE frame.
+          ReportPushPromise(state);
+          payload_state_ = PayloadState::kReadPayload;
+          continue;
+        }
+        payload_state_ = PayloadState::kResumeDecodingPushPromiseFields;
+        return status;
+    }
+    HTTP2_BUG << "PayloadState: " << payload_state_;
+  }
+}
+
+void PushPromisePayloadDecoder::ReportPushPromise(FrameDecoderState* state) {
+  const Http2FrameHeader& frame_header = state->frame_header();
+  if (frame_header.IsPadded()) {
+    state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_,
+                                          1 + state->remaining_padding());
+  } else {
+    state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_,
+                                          0);
+  }
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/push_promise_payload_decoder.h b/http2/decoder/payload_decoders/push_promise_payload_decoder.h
new file mode 100644
index 0000000..2db9cb3
--- /dev/null
+++ b/http2/decoder/payload_decoders/push_promise_payload_decoder.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_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a PUSH_PROMISE frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class PushPromisePayloadDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE PushPromisePayloadDecoder {
+ public:
+  // States during decoding of a PUSH_PROMISE frame.
+  enum class PayloadState {
+    // The frame is padded and we need to read the PAD_LENGTH field (1 byte).
+    kReadPadLength,
+
+    // Ready to start decoding the fixed size fields of the PUSH_PROMISE
+    // frame into push_promise_fields_.
+    kStartDecodingPushPromiseFields,
+
+    // The decoder has already called OnPushPromiseStart, and is now reporting
+    // the HPACK block fragment to the listener's OnHpackFragment method.
+    kReadPayload,
+
+    // The decoder has finished with the HPACK block fragment, and is now
+    // ready to skip the trailing padding, if the frame has any.
+    kSkipPadding,
+
+    // The fixed size fields weren't all available when the decoder first tried
+    // to decode them (state kStartDecodingPushPromiseFields); this state
+    // resumes the decoding when ResumeDecodingPayload is called later.
+    kResumeDecodingPushPromiseFields,
+  };
+
+  // Starts the decoding of a PUSH_PROMISE frame's payload, and completes it if
+  // the entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a PUSH_PROMISE frame's payload that has been split across
+  // decode buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+ private:
+  friend class test::PushPromisePayloadDecoderPeer;
+
+  void ReportPushPromise(FrameDecoderState* state);
+
+  PayloadState payload_state_;
+  Http2PushPromiseFields push_promise_fields_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc b/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc
new file mode 100644
index 0000000..9d55a80
--- /dev/null
+++ b/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc
@@ -0,0 +1,137 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class PushPromisePayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() {
+    return Http2FrameType::PUSH_PROMISE;
+  }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() {
+    return Http2FrameFlag::PADDED;
+  }
+};
+
+namespace {
+
+// Listener listens for only those methods expected by the payload decoder
+// under test, and forwards them onto the FrameParts instance for the current
+// frame.
+struct Listener : public FramePartsCollector {
+  void OnPushPromiseStart(const Http2FrameHeader& header,
+                          const Http2PushPromiseFields& promise,
+                          size_t total_padding_length) override {
+    VLOG(1) << "OnPushPromiseStart header: " << header
+            << "  promise: " << promise
+            << "  total_padding_length: " << total_padding_length;
+    EXPECT_EQ(Http2FrameType::PUSH_PROMISE, header.type);
+    StartFrame(header)->OnPushPromiseStart(header, promise,
+                                           total_padding_length);
+  }
+
+  void OnHpackFragment(const char* data, size_t len) override {
+    VLOG(1) << "OnHpackFragment: len=" << len;
+    CurrentFrame()->OnHpackFragment(data, len);
+  }
+
+  void OnPushPromiseEnd() override {
+    VLOG(1) << "OnPushPromiseEnd";
+    EndFrame()->OnPushPromiseEnd();
+  }
+
+  void OnPadding(const char* padding, size_t skipped_length) override {
+    VLOG(1) << "OnPadding: " << skipped_length;
+    CurrentFrame()->OnPadding(padding, skipped_length);
+  }
+
+  void OnPaddingTooLong(const Http2FrameHeader& header,
+                        size_t missing_length) override {
+    VLOG(1) << "OnPaddingTooLong: " << header
+            << "; missing_length: " << missing_length;
+    FrameError(header)->OnPaddingTooLong(header, missing_length);
+  }
+
+  void OnFrameSizeError(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnFrameSizeError: " << header;
+    FrameError(header)->OnFrameSizeError(header);
+  }
+};
+
+class PushPromisePayloadDecoderTest
+    : public AbstractPaddablePayloadDecoderTest<PushPromisePayloadDecoder,
+                                                PushPromisePayloadDecoderPeer,
+                                                Listener> {};
+
+INSTANTIATE_TEST_CASE_P(VariousPadLengths,
+                        PushPromisePayloadDecoderTest,
+                        ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256));
+
+// Payload contains the required Http2PushPromiseFields, followed by some
+// (fake) HPACK payload.
+TEST_P(PushPromisePayloadDecoderTest, VariousHpackPayloadSizes) {
+  for (size_t hpack_size : {0, 1, 2, 3, 255, 256, 1024}) {
+    LOG(INFO) << "###########   hpack_size = " << hpack_size << "  ###########";
+    Reset();
+    Http2String hpack_payload = Random().RandString(hpack_size);
+    Http2PushPromiseFields push_promise{RandStreamId()};
+    frame_builder_.Append(push_promise);
+    frame_builder_.Append(hpack_payload);
+    MaybeAppendTrailingPadding();
+    Http2FrameHeader frame_header(frame_builder_.size(),
+                                  Http2FrameType::PUSH_PROMISE, RandFlags(),
+                                  RandStreamId());
+    set_frame_header(frame_header);
+    FrameParts expected(frame_header, hpack_payload, total_pad_length_);
+    expected.SetOptPushPromise(push_promise);
+    EXPECT_TRUE(
+        DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), expected));
+  }
+}
+
+// Confirm we get an error if the payload is not long enough for the required
+// portion of the payload, regardless of the amount of (valid) padding.
+TEST_P(PushPromisePayloadDecoderTest, Truncated) {
+  auto approve_size = [](size_t size) {
+    return size != Http2PushPromiseFields::EncodedSize();
+  };
+  Http2PushPromiseFields push_promise{RandStreamId()};
+  Http2FrameBuilder fb;
+  fb.Append(push_promise);
+  EXPECT_TRUE(VerifyDetectsMultipleFrameSizeErrors(0, fb.buffer(), approve_size,
+                                                   total_pad_length_));
+}
+
+// Confirm we get an error if the PADDED flag is set but the payload is not
+// long enough to hold even the Pad Length amount of padding.
+TEST_P(PushPromisePayloadDecoderTest, PaddingTooLong) {
+  EXPECT_TRUE(VerifyDetectsPaddingTooLong());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc b/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc
new file mode 100644
index 0000000..c39a16a
--- /dev/null
+++ b/http2/decoder/payload_decoders/rst_stream_payload_decoder.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 "net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus RstStreamPayloadDecoder::StartDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "RstStreamPayloadDecoder::StartDecodingPayload: "
+           << state->frame_header();
+  DCHECK_EQ(Http2FrameType::RST_STREAM, state->frame_header().type);
+  DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+  // RST_STREAM has no flags.
+  DCHECK_EQ(0, state->frame_header().flags);
+  state->InitializeRemainders();
+  return HandleStatus(
+      state, state->StartDecodingStructureInPayload(&rst_stream_fields_, db));
+}
+
+DecodeStatus RstStreamPayloadDecoder::ResumeDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "RstStreamPayloadDecoder::ResumeDecodingPayload"
+           << "  remaining_payload=" << state->remaining_payload()
+           << "  db->Remaining=" << db->Remaining();
+  DCHECK_EQ(Http2FrameType::RST_STREAM, state->frame_header().type);
+  DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+  return HandleStatus(
+      state, state->ResumeDecodingStructureInPayload(&rst_stream_fields_, db));
+}
+
+DecodeStatus RstStreamPayloadDecoder::HandleStatus(FrameDecoderState* state,
+                                                   DecodeStatus status) {
+  DVLOG(2) << "HandleStatus: status=" << status
+           << "; remaining_payload=" << state->remaining_payload();
+  if (status == DecodeStatus::kDecodeDone) {
+    if (state->remaining_payload() == 0) {
+      state->listener()->OnRstStream(state->frame_header(),
+                                     rst_stream_fields_.error_code);
+      return DecodeStatus::kDecodeDone;
+    }
+    // Payload is too long.
+    return state->ReportFrameSizeError();
+  }
+  // Not done decoding the structure. Either we've got more payload to decode,
+  // or we've run out because the payload is too short, in which case
+  // OnFrameSizeError will have already been called by the FrameDecoderState.
+  DCHECK(
+      (status == DecodeStatus::kDecodeInProgress &&
+       state->remaining_payload() > 0) ||
+      (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+      << "\n status=" << status
+      << "; remaining_payload=" << state->remaining_payload();
+  return status;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/rst_stream_payload_decoder.h b/http2/decoder/payload_decoders/rst_stream_payload_decoder.h
new file mode 100644
index 0000000..947fc06
--- /dev/null
+++ b/http2/decoder/payload_decoders/rst_stream_payload_decoder.h
@@ -0,0 +1,42 @@
+// 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_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a RST_STREAM frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class RstStreamPayloadDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE RstStreamPayloadDecoder {
+ public:
+  // Starts the decoding of a RST_STREAM frame's payload, and completes it if
+  // the entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a RST_STREAM frame's payload that has been split across
+  // decode buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+ private:
+  friend class test::RstStreamPayloadDecoderPeer;
+
+  DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+  Http2RstStreamFields rst_stream_fields_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc b/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc
new file mode 100644
index 0000000..08f6621
--- /dev/null
+++ b/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc
@@ -0,0 +1,92 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class RstStreamPayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() {
+    return Http2FrameType::RST_STREAM;
+  }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+  void OnRstStream(const Http2FrameHeader& header,
+                   Http2ErrorCode error_code) override {
+    VLOG(1) << "OnRstStream: " << header << "; error_code=" << error_code;
+    StartAndEndFrame(header)->OnRstStream(header, error_code);
+  }
+
+  void OnFrameSizeError(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnFrameSizeError: " << header;
+    FrameError(header)->OnFrameSizeError(header);
+  }
+};
+
+class RstStreamPayloadDecoderTest
+    : public AbstractPayloadDecoderTest<RstStreamPayloadDecoder,
+                                        RstStreamPayloadDecoderPeer,
+                                        Listener> {
+ protected:
+  Http2RstStreamFields RandRstStreamFields() {
+    Http2RstStreamFields fields;
+    test::Randomize(&fields, RandomPtr());
+    return fields;
+  }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2RstStreamFields.
+TEST_F(RstStreamPayloadDecoderTest, WrongSize) {
+  auto approve_size = [](size_t size) {
+    return size != Http2RstStreamFields::EncodedSize();
+  };
+  Http2FrameBuilder fb;
+  fb.Append(RandRstStreamFields());
+  fb.Append(RandRstStreamFields());
+  fb.Append(RandRstStreamFields());
+  EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(RstStreamPayloadDecoderTest, AllErrors) {
+  for (auto error_code : AllHttp2ErrorCodes()) {
+    Http2RstStreamFields fields{error_code};
+    Http2FrameBuilder fb;
+    fb.Append(fields);
+    Http2FrameHeader header(fb.size(), Http2FrameType::RST_STREAM, RandFlags(),
+                            RandStreamId());
+    set_frame_header(header);
+    FrameParts expected(header);
+    expected.SetOptRstStreamErrorCode(error_code);
+    EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/settings_payload_decoder.cc b/http2/decoder/payload_decoders/settings_payload_decoder.cc
new file mode 100644
index 0000000..bf29c4d
--- /dev/null
+++ b/http2/decoder/payload_decoders/settings_payload_decoder.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 "net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus SettingsPayloadDecoder::StartDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  const Http2FrameHeader& frame_header = state->frame_header();
+  const uint32_t total_length = frame_header.payload_length;
+
+  DVLOG(2) << "SettingsPayloadDecoder::StartDecodingPayload: " << frame_header;
+  DCHECK_EQ(Http2FrameType::SETTINGS, frame_header.type);
+  DCHECK_LE(db->Remaining(), total_length);
+  DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::ACK));
+
+  if (frame_header.IsAck()) {
+    if (total_length == 0) {
+      state->listener()->OnSettingsAck(frame_header);
+      return DecodeStatus::kDecodeDone;
+    } else {
+      state->InitializeRemainders();
+      return state->ReportFrameSizeError();
+    }
+  } else {
+    state->InitializeRemainders();
+    state->listener()->OnSettingsStart(frame_header);
+    return StartDecodingSettings(state, db);
+  }
+}
+
+DecodeStatus SettingsPayloadDecoder::ResumeDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "SettingsPayloadDecoder::ResumeDecodingPayload"
+           << "  remaining_payload=" << state->remaining_payload()
+           << "  db->Remaining=" << db->Remaining();
+  DCHECK_EQ(Http2FrameType::SETTINGS, state->frame_header().type);
+  DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+
+  DecodeStatus status =
+      state->ResumeDecodingStructureInPayload(&setting_fields_, db);
+  if (status == DecodeStatus::kDecodeDone) {
+    state->listener()->OnSetting(setting_fields_);
+    return StartDecodingSettings(state, db);
+  }
+  return HandleNotDone(state, db, status);
+}
+
+DecodeStatus SettingsPayloadDecoder::StartDecodingSettings(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "SettingsPayloadDecoder::StartDecodingSettings"
+           << "  remaining_payload=" << state->remaining_payload()
+           << "  db->Remaining=" << db->Remaining();
+  while (state->remaining_payload() > 0) {
+    DecodeStatus status =
+        state->StartDecodingStructureInPayload(&setting_fields_, db);
+    if (status == DecodeStatus::kDecodeDone) {
+      state->listener()->OnSetting(setting_fields_);
+      continue;
+    }
+    return HandleNotDone(state, db, status);
+  }
+  DVLOG(2) << "LEAVING SettingsPayloadDecoder::StartDecodingSettings"
+           << "\n\tdb->Remaining=" << db->Remaining()
+           << "\n\t remaining_payload=" << state->remaining_payload();
+  state->listener()->OnSettingsEnd();
+  return DecodeStatus::kDecodeDone;
+}
+
+DecodeStatus SettingsPayloadDecoder::HandleNotDone(FrameDecoderState* state,
+                                                   DecodeBuffer* db,
+                                                   DecodeStatus status) {
+  // Not done decoding the structure. Either we've got more payload to decode,
+  // or we've run out because the payload is too short, in which case
+  // OnFrameSizeError will have already been called.
+  DCHECK(
+      (status == DecodeStatus::kDecodeInProgress &&
+       state->remaining_payload() > 0) ||
+      (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+      << "\n status=" << status
+      << "; remaining_payload=" << state->remaining_payload()
+      << "; db->Remaining=" << db->Remaining();
+  return status;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/settings_payload_decoder.h b/http2/decoder/payload_decoders/settings_payload_decoder.h
new file mode 100644
index 0000000..7e3c313
--- /dev/null
+++ b/http2/decoder/payload_decoders/settings_payload_decoder.h
@@ -0,0 +1,54 @@
+// 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_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a SETTINGS frame; for the RFC, see:
+//     http://httpwg.org/specs/rfc7540.html#SETTINGS
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class SettingsPayloadDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE SettingsPayloadDecoder {
+ public:
+  // Starts the decoding of a SETTINGS frame's payload, and completes it if
+  // the entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a SETTINGS frame that has been split across decode
+  // buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+ private:
+  friend class test::SettingsPayloadDecoderPeer;
+
+  // Decodes as many settings as are available in the decode buffer, starting at
+  // the first byte of one setting; if a single setting is split across buffers,
+  // ResumeDecodingPayload will handle starting from where the previous call
+  // left off, and then will call StartDecodingSettings.
+  DecodeStatus StartDecodingSettings(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+  // Decoding a single SETTING returned a status other than kDecodeDone; this
+  // method just brings together the DCHECKs to reduce duplication.
+  DecodeStatus HandleNotDone(FrameDecoderState* state,
+                             DecodeBuffer* db,
+                             DecodeStatus status);
+
+  Http2SettingFields setting_fields_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/settings_payload_decoder_test.cc b/http2/decoder/payload_decoders/settings_payload_decoder_test.cc
new file mode 100644
index 0000000..3429779
--- /dev/null
+++ b/http2/decoder/payload_decoders/settings_payload_decoder_test.cc
@@ -0,0 +1,160 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h"
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class SettingsPayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() {
+    return Http2FrameType::SETTINGS;
+  }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() {
+    return Http2FrameFlag::ACK;
+  }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+  void OnSettingsStart(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnSettingsStart: " << header;
+    EXPECT_EQ(Http2FrameType::SETTINGS, header.type) << header;
+    EXPECT_EQ(Http2FrameFlag(), header.flags) << header;
+    StartFrame(header)->OnSettingsStart(header);
+  }
+
+  void OnSetting(const Http2SettingFields& setting_fields) override {
+    VLOG(1) << "Http2SettingFields: setting_fields=" << setting_fields;
+    CurrentFrame()->OnSetting(setting_fields);
+  }
+
+  void OnSettingsEnd() override {
+    VLOG(1) << "OnSettingsEnd";
+    EndFrame()->OnSettingsEnd();
+  }
+
+  void OnSettingsAck(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnSettingsAck: " << header;
+    StartAndEndFrame(header)->OnSettingsAck(header);
+  }
+
+  void OnFrameSizeError(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnFrameSizeError: " << header;
+    FrameError(header)->OnFrameSizeError(header);
+  }
+};
+
+class SettingsPayloadDecoderTest
+    : public AbstractPayloadDecoderTest<SettingsPayloadDecoder,
+                                        SettingsPayloadDecoderPeer,
+                                        Listener> {
+ protected:
+  Http2SettingFields RandSettingsFields() {
+    Http2SettingFields fields;
+    test::Randomize(&fields, RandomPtr());
+    return fields;
+  }
+};
+
+// Confirm we get an error if the SETTINGS payload is not the correct size
+// to hold exactly zero or more whole Http2SettingFields.
+TEST_F(SettingsPayloadDecoderTest, SettingsWrongSize) {
+  auto approve_size = [](size_t size) {
+    // Should get an error if size is not an integral multiple of the size
+    // of one setting.
+    return 0 != (size % Http2SettingFields::EncodedSize());
+  };
+  Http2FrameBuilder fb;
+  fb.Append(RandSettingsFields());
+  fb.Append(RandSettingsFields());
+  fb.Append(RandSettingsFields());
+  EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+// Confirm we get an error if the SETTINGS ACK payload is not empty.
+TEST_F(SettingsPayloadDecoderTest, SettingsAkcWrongSize) {
+  auto approve_size = [](size_t size) { return size != 0; };
+  Http2FrameBuilder fb;
+  fb.Append(RandSettingsFields());
+  fb.Append(RandSettingsFields());
+  fb.Append(RandSettingsFields());
+  EXPECT_TRUE(VerifyDetectsFrameSizeError(Http2FrameFlag::ACK, fb.buffer(),
+                                          approve_size));
+}
+
+// SETTINGS must have stream_id==0, but the payload decoder doesn't check that.
+TEST_F(SettingsPayloadDecoderTest, SettingsAck) {
+  for (int stream_id = 0; stream_id < 3; ++stream_id) {
+    Http2FrameHeader header(0, Http2FrameType::SETTINGS,
+                            RandFlags() | Http2FrameFlag::ACK, stream_id);
+    set_frame_header(header);
+    FrameParts expected(header);
+    EXPECT_TRUE(DecodePayloadAndValidateSeveralWays("", expected));
+  }
+}
+
+// Try several values of each known SETTINGS parameter.
+TEST_F(SettingsPayloadDecoderTest, OneRealSetting) {
+  std::vector<uint32_t> values = {0, 1, 0xffffffff, Random().Rand32()};
+  for (auto param : AllHttp2SettingsParameters()) {
+    for (uint32_t value : values) {
+      Http2SettingFields fields(param, value);
+      Http2FrameBuilder fb;
+      fb.Append(fields);
+      Http2FrameHeader header(fb.size(), Http2FrameType::SETTINGS, RandFlags(),
+                              RandStreamId());
+      set_frame_header(header);
+      FrameParts expected(header);
+      expected.AppendSetting(fields);
+      EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+    }
+  }
+}
+
+// Decode a SETTINGS frame with lots of fields.
+TEST_F(SettingsPayloadDecoderTest, ManySettings) {
+  const size_t num_settings = 100;
+  const size_t size = Http2SettingFields::EncodedSize() * num_settings;
+  Http2FrameHeader header(size, Http2FrameType::SETTINGS,
+                          RandFlags(),  // & ~Http2FrameFlag::ACK,
+                          RandStreamId());
+  set_frame_header(header);
+  FrameParts expected(header);
+  Http2FrameBuilder fb;
+  for (size_t n = 0; n < num_settings; ++n) {
+    Http2SettingFields fields(static_cast<Http2SettingsParameter>(n),
+                              Random().Rand32());
+    fb.Append(fields);
+    expected.AppendSetting(fields);
+  }
+  ASSERT_EQ(size, fb.size());
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/unknown_payload_decoder.cc b/http2/decoder/payload_decoders/unknown_payload_decoder.cc
new file mode 100644
index 0000000..10eaf24
--- /dev/null
+++ b/http2/decoder/payload_decoders/unknown_payload_decoder.cc
@@ -0,0 +1,55 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus UnknownPayloadDecoder::StartDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  const Http2FrameHeader& frame_header = state->frame_header();
+
+  DVLOG(2) << "UnknownPayloadDecoder::StartDecodingPayload: " << frame_header;
+  DCHECK(!IsSupportedHttp2FrameType(frame_header.type)) << frame_header;
+  DCHECK_LE(db->Remaining(), frame_header.payload_length);
+
+  state->InitializeRemainders();
+  state->listener()->OnUnknownStart(frame_header);
+  return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus UnknownPayloadDecoder::ResumeDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "UnknownPayloadDecoder::ResumeDecodingPayload "
+           << "remaining_payload=" << state->remaining_payload()
+           << "; db->Remaining=" << db->Remaining();
+  DCHECK(!IsSupportedHttp2FrameType(state->frame_header().type))
+      << state->frame_header();
+  DCHECK_LE(state->remaining_payload(), state->frame_header().payload_length);
+  DCHECK_LE(db->Remaining(), state->remaining_payload());
+
+  size_t avail = db->Remaining();
+  if (avail > 0) {
+    state->listener()->OnUnknownPayload(db->cursor(), avail);
+    db->AdvanceCursor(avail);
+    state->ConsumePayload(avail);
+  }
+  if (state->remaining_payload() == 0) {
+    state->listener()->OnUnknownEnd();
+    return DecodeStatus::kDecodeDone;
+  }
+  return DecodeStatus::kDecodeInProgress;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/unknown_payload_decoder.h b/http2/decoder/payload_decoders/unknown_payload_decoder.h
new file mode 100644
index 0000000..7bf4103
--- /dev/null
+++ b/http2/decoder/payload_decoders/unknown_payload_decoder.h
@@ -0,0 +1,33 @@
+// 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_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a frame whose type unknown.  According to the HTTP/2
+// specification (http://httpwg.org/specs/rfc7540.html#FrameHeader):
+//     Implementations MUST ignore and discard any frame that has
+//     a type that is unknown.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE UnknownPayloadDecoder {
+ public:
+  // Starts decoding a payload of unknown type; just passes it to the listener.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a payload of unknown type that has been split across
+  // decode buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc b/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc
new file mode 100644
index 0000000..7ba95f7
--- /dev/null
+++ b/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc
@@ -0,0 +1,100 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h"
+
+#include <stddef.h>
+
+#include <type_traits>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+namespace {
+Http2FrameType g_unknown_frame_type;
+}  // namespace
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class UnknownPayloadDecoderPeer {
+ public:
+  static Http2FrameType FrameType() { return g_unknown_frame_type; }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+  void OnUnknownStart(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnUnknownStart: " << header;
+    StartFrame(header)->OnUnknownStart(header);
+  }
+
+  void OnUnknownPayload(const char* data, size_t len) override {
+    VLOG(1) << "OnUnknownPayload: len=" << len;
+    CurrentFrame()->OnUnknownPayload(data, len);
+  }
+
+  void OnUnknownEnd() override {
+    VLOG(1) << "OnUnknownEnd";
+    EndFrame()->OnUnknownEnd();
+  }
+};
+
+constexpr bool SupportedFrameType = false;
+
+class UnknownPayloadDecoderTest
+    : public AbstractPayloadDecoderTest<UnknownPayloadDecoder,
+                                        UnknownPayloadDecoderPeer,
+                                        Listener,
+                                        SupportedFrameType>,
+      public ::testing::WithParamInterface<uint32_t> {
+ protected:
+  UnknownPayloadDecoderTest() : length_(GetParam()) {
+    VLOG(1) << "################  length_=" << length_ << "  ################";
+
+    // Each test case will choose a random frame type that isn't supported.
+    do {
+      g_unknown_frame_type = static_cast<Http2FrameType>(Random().Rand8());
+    } while (IsSupportedHttp2FrameType(g_unknown_frame_type));
+  }
+
+  const uint32_t length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousLengths,
+                        UnknownPayloadDecoderTest,
+                        ::testing::Values(0, 1, 2, 3, 255, 256));
+
+TEST_P(UnknownPayloadDecoderTest, ValidLength) {
+  Http2String unknown_payload = Random().RandString(length_);
+  Http2FrameHeader frame_header(length_, g_unknown_frame_type, Random().Rand8(),
+                                RandStreamId());
+  set_frame_header(frame_header);
+  FrameParts expected(frame_header, unknown_payload);
+  EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(unknown_payload, expected));
+  // TODO(jamessynge): Check here (and in other such tests) that the fast
+  // and slow decode counts are both non-zero. Perhaps also add some kind of
+  // test for the listener having been called. That could simply be a test
+  // that there is a single collected FrameParts instance, and that it matches
+  // expected.
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/window_update_payload_decoder.cc b/http2/decoder/payload_decoders/window_update_payload_decoder.cc
new file mode 100644
index 0000000..c0bb028
--- /dev/null
+++ b/http2/decoder/payload_decoders/window_update_payload_decoder.cc
@@ -0,0 +1,82 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus WindowUpdatePayloadDecoder::StartDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  const Http2FrameHeader& frame_header = state->frame_header();
+  const uint32_t total_length = frame_header.payload_length;
+
+  DVLOG(2) << "WindowUpdatePayloadDecoder::StartDecodingPayload: "
+           << frame_header;
+
+  DCHECK_EQ(Http2FrameType::WINDOW_UPDATE, frame_header.type);
+  DCHECK_LE(db->Remaining(), total_length);
+
+  // WINDOW_UPDATE frames have no flags.
+  DCHECK_EQ(0, frame_header.flags);
+
+  // Special case for when the payload is the correct size and entirely in
+  // the buffer.
+  if (db->Remaining() == Http2WindowUpdateFields::EncodedSize() &&
+      total_length == Http2WindowUpdateFields::EncodedSize()) {
+    DoDecode(&window_update_fields_, db);
+    state->listener()->OnWindowUpdate(
+        frame_header, window_update_fields_.window_size_increment);
+    return DecodeStatus::kDecodeDone;
+  }
+  state->InitializeRemainders();
+  return HandleStatus(state, state->StartDecodingStructureInPayload(
+                                 &window_update_fields_, db));
+}
+
+DecodeStatus WindowUpdatePayloadDecoder::ResumeDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  DVLOG(2) << "ResumeDecodingPayload: remaining_payload="
+           << state->remaining_payload()
+           << "; db->Remaining=" << db->Remaining();
+  DCHECK_EQ(Http2FrameType::WINDOW_UPDATE, state->frame_header().type);
+  DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+  return HandleStatus(state, state->ResumeDecodingStructureInPayload(
+                                 &window_update_fields_, db));
+}
+
+DecodeStatus WindowUpdatePayloadDecoder::HandleStatus(FrameDecoderState* state,
+                                                      DecodeStatus status) {
+  DVLOG(2) << "HandleStatus: status=" << status
+           << "; remaining_payload=" << state->remaining_payload();
+  if (status == DecodeStatus::kDecodeDone) {
+    if (state->remaining_payload() == 0) {
+      state->listener()->OnWindowUpdate(
+          state->frame_header(), window_update_fields_.window_size_increment);
+      return DecodeStatus::kDecodeDone;
+    }
+    // Payload is too long.
+    return state->ReportFrameSizeError();
+  }
+  // Not done decoding the structure. Either we've got more payload to decode,
+  // or we've run out because the payload is too short, in which case
+  // OnFrameSizeError will have already been called.
+  DCHECK(
+      (status == DecodeStatus::kDecodeInProgress &&
+       state->remaining_payload() > 0) ||
+      (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+      << "\n status=" << status
+      << "; remaining_payload=" << state->remaining_payload();
+  return status;
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/window_update_payload_decoder.h b/http2/decoder/payload_decoders/window_update_payload_decoder.h
new file mode 100644
index 0000000..158c165
--- /dev/null
+++ b/http2/decoder/payload_decoders/window_update_payload_decoder.h
@@ -0,0 +1,42 @@
+// 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_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a WINDOW_UPDATE frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class WindowUpdatePayloadDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE WindowUpdatePayloadDecoder {
+ public:
+  // Starts decoding a WINDOW_UPDATE frame's payload, and completes it if
+  // the entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a WINDOW_UPDATE frame's payload that has been split across
+  // decode buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+ private:
+  friend class test::WindowUpdatePayloadDecoderPeer;
+
+  DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+  Http2WindowUpdateFields window_update_fields_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc b/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc
new file mode 100644
index 0000000..77a2b90
--- /dev/null
+++ b/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc
@@ -0,0 +1,95 @@
+// 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class WindowUpdatePayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() {
+    return Http2FrameType::WINDOW_UPDATE;
+  }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+  void OnWindowUpdate(const Http2FrameHeader& header,
+                      uint32_t window_size_increment) override {
+    VLOG(1) << "OnWindowUpdate: " << header
+            << "; window_size_increment=" << window_size_increment;
+    EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, header.type);
+    StartAndEndFrame(header)->OnWindowUpdate(header, window_size_increment);
+  }
+
+  void OnFrameSizeError(const Http2FrameHeader& header) override {
+    VLOG(1) << "OnFrameSizeError: " << header;
+    FrameError(header)->OnFrameSizeError(header);
+  }
+};
+
+class WindowUpdatePayloadDecoderTest
+    : public AbstractPayloadDecoderTest<WindowUpdatePayloadDecoder,
+                                        WindowUpdatePayloadDecoderPeer,
+                                        Listener> {
+ protected:
+  Http2WindowUpdateFields RandWindowUpdateFields() {
+    Http2WindowUpdateFields fields;
+    test::Randomize(&fields, RandomPtr());
+    VLOG(3) << "RandWindowUpdateFields: " << fields;
+    return fields;
+  }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2WindowUpdateFields.
+TEST_F(WindowUpdatePayloadDecoderTest, WrongSize) {
+  auto approve_size = [](size_t size) {
+    return size != Http2WindowUpdateFields::EncodedSize();
+  };
+  Http2FrameBuilder fb;
+  fb.Append(RandWindowUpdateFields());
+  fb.Append(RandWindowUpdateFields());
+  fb.Append(RandWindowUpdateFields());
+  EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(WindowUpdatePayloadDecoderTest, VariousPayloads) {
+  for (int n = 0; n < 100; ++n) {
+    uint32_t stream_id = n == 0 ? 0 : RandStreamId();
+    Http2WindowUpdateFields fields = RandWindowUpdateFields();
+    Http2FrameBuilder fb;
+    fb.Append(fields);
+    Http2FrameHeader header(fb.size(), Http2FrameType::WINDOW_UPDATE,
+                            RandFlags(), stream_id);
+    set_frame_header(header);
+    FrameParts expected(header);
+    expected.SetOptWindowUpdateIncrement(fields.window_size_increment);
+    EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_block_collector.cc b/http2/hpack/decoder/hpack_block_collector.cc
new file mode 100644
index 0000000..5b2c6b7
--- /dev/null
+++ b/http2/hpack/decoder/hpack_block_collector.cc
@@ -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.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_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());
+  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 Http2String& value) {
+  entries_.push_back(HpackEntryCollector(type, index, value_huffman, value));
+}
+void HpackBlockCollector::ExpectLiteralNameAndValue(HpackEntryType type,
+                                                    bool name_huffman,
+                                                    const Http2String& name,
+                                                    bool value_huffman,
+                                                    const Http2String& 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 {
+  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,
+    Http2StringPiece 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,
+    Http2StringPiece expected_name,
+    bool expected_value_huffman,
+    Http2StringPiece 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/http2/hpack/decoder/hpack_block_collector.h b/http2/hpack/decoder/hpack_block_collector.h
new file mode 100644
index 0000000..6ad1405
--- /dev/null
+++ b/http2/hpack/decoder/hpack_block_collector.h
@@ -0,0 +1,129 @@
+// 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_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_
+#define QUICHE_HTTP2_HPACK_DECODER_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 <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+class 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 Http2String& value);
+
+  // Add an HPACK entry for a header entry with a literal name and value.
+  void ExpectLiteralNameAndValue(HpackEntryType type,
+                                 bool name_huffman,
+                                 const Http2String& name,
+                                 bool value_huffman,
+                                 const Http2String& 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,
+      Http2StringPiece 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,
+      Http2StringPiece expected_name,
+      bool expected_value_huffman,
+      Http2StringPiece 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_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_
diff --git a/http2/hpack/decoder/hpack_block_decoder.cc b/http2/hpack/decoder/hpack_block_decoder.cc
new file mode 100644
index 0000000..c6882d2
--- /dev/null
+++ b/http2/hpack/decoder/hpack_block_decoder.cc
@@ -0,0 +1,62 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h"
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+DecodeStatus HpackBlockDecoder::Decode(DecodeBuffer* db) {
+  if (!before_entry_) {
+    DVLOG(2) << "HpackBlockDecoder::Decode resume entry, db->Remaining="
+             << db->Remaining();
+    DecodeStatus status = entry_decoder_.Resume(db, listener_);
+    switch (status) {
+      case DecodeStatus::kDecodeDone:
+        before_entry_ = true;
+        break;
+      case DecodeStatus::kDecodeInProgress:
+        DCHECK_EQ(0u, db->Remaining());
+        return DecodeStatus::kDecodeInProgress;
+      case DecodeStatus::kDecodeError:
+        return DecodeStatus::kDecodeError;
+    }
+  }
+  DCHECK(before_entry_);
+  while (db->HasData()) {
+    DVLOG(2) << "HpackBlockDecoder::Decode start entry, db->Remaining="
+             << db->Remaining();
+    DecodeStatus status = entry_decoder_.Start(db, listener_);
+    switch (status) {
+      case DecodeStatus::kDecodeDone:
+        continue;
+      case DecodeStatus::kDecodeInProgress:
+        DCHECK_EQ(0u, db->Remaining());
+        before_entry_ = false;
+        return DecodeStatus::kDecodeInProgress;
+      case DecodeStatus::kDecodeError:
+        return DecodeStatus::kDecodeError;
+    }
+    DCHECK(false);
+  }
+  DCHECK(before_entry_);
+  return DecodeStatus::kDecodeDone;
+}
+
+Http2String HpackBlockDecoder::DebugString() const {
+  return Http2StrCat("HpackBlockDecoder(", entry_decoder_.DebugString(),
+                     ", listener@",
+                     Http2Hex(reinterpret_cast<intptr_t>(listener_)),
+                     (before_entry_ ? ", between entries)" : ", in an entry)"));
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackBlockDecoder& v) {
+  return out << v.DebugString();
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_block_decoder.h b/http2/hpack/decoder/hpack_block_decoder.h
new file mode 100644
index 0000000..e8b23e1
--- /dev/null
+++ b/http2/hpack/decoder/hpack_block_decoder.h
@@ -0,0 +1,64 @@
+// 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_HPACK_DECODER_HPACK_BLOCK_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_
+
+// HpackBlockDecoder decodes an entire HPACK block (or the available portion
+// thereof in the DecodeBuffer) into entries, but doesn't include HPACK static
+// or dynamic table support, so table indices remain indices at this level.
+// Reports the entries to an HpackEntryDecoderListener.
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackBlockDecoder {
+ public:
+  explicit HpackBlockDecoder(HpackEntryDecoderListener* listener)
+      : listener_(listener) {
+    DCHECK_NE(listener_, nullptr);
+  }
+  ~HpackBlockDecoder() {}
+
+  HpackBlockDecoder(const HpackBlockDecoder&) = delete;
+  HpackBlockDecoder& operator=(const HpackBlockDecoder&) = delete;
+
+  // Prepares the decoder to start decoding a new HPACK block. Expected
+  // to be called from an implementation of Http2FrameDecoderListener's
+  // OnHeadersStart or OnPushPromiseStart methods.
+  void Reset() {
+    DVLOG(2) << "HpackBlockDecoder::Reset";
+    before_entry_ = true;
+  }
+
+  // Decode the fragment of the HPACK block contained in the decode buffer.
+  // Expected to be called from an implementation of Http2FrameDecoderListener's
+  // OnHpackFragment method.
+  DecodeStatus Decode(DecodeBuffer* db);
+
+  // Is the decoding process between entries (i.e. would the next byte be the
+  // first byte of a new HPACK entry)?
+  bool before_entry() const { return before_entry_; }
+
+  Http2String DebugString() const;
+
+ private:
+  HpackEntryDecoder entry_decoder_;
+  HpackEntryDecoderListener* const listener_;
+  bool before_entry_ = true;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const HpackBlockDecoder& v);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_
diff --git a/http2/hpack/decoder/hpack_block_decoder_test.cc b/http2/hpack/decoder/hpack_block_decoder_test.cc
new file mode 100644
index 0000000..fce45b2
--- /dev/null
+++ b/http2/hpack/decoder/hpack_block_decoder_test.cc
@@ -0,0 +1,294 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h"
+
+// Tests of HpackBlockDecoder.
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+
+class HpackBlockDecoderTest : public RandomDecoderTest {
+ protected:
+  HpackBlockDecoderTest() : listener_(&collector_), decoder_(&listener_) {
+    stop_decode_on_done_ = false;
+    decoder_.Reset();
+    // Make sure logging doesn't crash. Not examining the result.
+    std::ostringstream strm;
+    strm << decoder_;
+  }
+
+  DecodeStatus StartDecoding(DecodeBuffer* db) override {
+    collector_.Clear();
+    decoder_.Reset();
+    return ResumeDecoding(db);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* db) override {
+    DecodeStatus status = decoder_.Decode(db);
+
+    // Make sure logging doesn't crash. Not examining the result.
+    std::ostringstream strm;
+    strm << decoder_;
+
+    return status;
+  }
+
+  AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db,
+                                               const Validator& validator) {
+    bool return_non_zero_on_first = false;
+    return RandomDecoderTest::DecodeAndValidateSeveralWays(
+        db, return_non_zero_on_first, validator);
+  }
+
+  AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb,
+                                               const Validator& validator) {
+    DecodeBuffer db(hbb.buffer());
+    return DecodeAndValidateSeveralWays(&db, validator);
+  }
+
+  AssertionResult DecodeHpackExampleAndValidateSeveralWays(
+      Http2StringPiece hpack_example,
+      Validator validator) {
+    Http2String input = HpackExampleToStringOrDie(hpack_example);
+    DecodeBuffer db(input);
+    return DecodeAndValidateSeveralWays(&db, validator);
+  }
+
+  uint8_t Rand8() { return Random().Rand8(); }
+
+  Http2String Rand8String() { return Random().RandString(Rand8()); }
+
+  HpackBlockCollector collector_;
+  HpackEntryDecoderVLoggingListener listener_;
+  HpackBlockDecoder decoder_;
+};
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.1
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_1) {
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralNameValueHeader(
+        HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false,
+        "custom-header"));
+  };
+  const char hpack_example[] = R"(
+      40                                      | == Literal indexed ==
+      0a                                      |   Literal name (len = 10)
+      6375 7374 6f6d 2d6b 6579                | custom-key
+      0d                                      |   Literal value (len = 13)
+      6375 7374 6f6d 2d68 6561 6465 72        | custom-header
+                                              | -> custom-key:
+                                              |   custom-header
+      )";
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      hpack_example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.2
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_2) {
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralValueHeader(
+        HpackEntryType::kUnindexedLiteralHeader, 4, false, "/sample/path"));
+  };
+  const char hpack_example[] = R"(
+      04                                      | == Literal not indexed ==
+                                              |   Indexed name (idx = 4)
+                                              |     :path
+      0c                                      |   Literal value (len = 12)
+      2f73 616d 706c 652f 7061 7468           | /sample/path
+                                              | -> :path: /sample/path
+      )";
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      hpack_example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.3
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_3) {
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralNameValueHeader(
+        HpackEntryType::kNeverIndexedLiteralHeader, false, "password", false,
+        "secret"));
+  };
+  const char hpack_example[] = R"(
+      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
+      )";
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      hpack_example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.4
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_4) {
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleIndexedHeader(2));
+  };
+  const char hpack_example[] = R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      )";
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      hpack_example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3.1
+TEST_F(HpackBlockDecoderTest, SpecExample_C_3_1) {
+  Http2String example = R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      86                                      | == Indexed - Add ==
+                                              |   idx = 6
+                                              | -> :scheme: http
+      84                                      | == Indexed - Add ==
+                                              |   idx = 4
+                                              | -> :path: /
+      41                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 1)
+                                              |     :authority
+      0f                                      |   Literal value (len = 15)
+      7777 772e 6578 616d 706c 652e 636f 6d   | www.example.com
+                                              | -> :authority:
+                                              |   www.example.com
+      )";
+  HpackBlockCollector expected;
+  expected.ExpectIndexedHeader(2);
+  expected.ExpectIndexedHeader(6);
+  expected.ExpectIndexedHeader(4);
+  expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+                                          1, false, "www.example.com");
+  NoArgValidator do_check = [expected, this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected));
+  };
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.5.1
+TEST_F(HpackBlockDecoderTest, SpecExample_C_5_1) {
+  Http2String example = R"(
+      48                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 8)
+                                              |     :status
+      03                                      |   Literal value (len = 3)
+      3330 32                                 | 302
+                                              | -> :status: 302
+      58                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 24)
+                                              |     cache-control
+      07                                      |   Literal value (len = 7)
+      7072 6976 6174 65                       | private
+                                              | -> cache-control: private
+      61                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 33)
+                                              |     date
+      1d                                      |   Literal value (len = 29)
+      4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+      2032 303a 3133 3a32 3120 474d 54        |  20:13:21 GMT
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:21 GMT
+      6e                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 46)
+                                              |     location
+      17                                      |   Literal value (len = 23)
+      6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam
+      706c 652e 636f 6d                       | ple.com
+                                              | -> location:
+                                              |   https://www.example.com
+      )";
+  HpackBlockCollector expected;
+  expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+                                          8, false, "302");
+  expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+                                          24, false, "private");
+  expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+                                          33, false,
+                                          "Mon, 21 Oct 2013 20:13:21 GMT");
+  expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+                                          46, false, "https://www.example.com");
+  NoArgValidator do_check = [expected, this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected));
+  };
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+// Generate a bunch of HPACK block entries to expect, use those expectations
+// to generate an HPACK block, then decode it and confirm it matches those
+// expectations. Some of these are invalid (such as Indexed, with index=0),
+// but well-formed, and the decoder doesn't check for validity, just
+// well-formedness. That includes the validity of the strings not being checked,
+// such as lower-case ascii for the names, and valid Huffman encodings.
+TEST_F(HpackBlockDecoderTest, Computed) {
+  HpackBlockCollector expected;
+  expected.ExpectIndexedHeader(0);
+  expected.ExpectIndexedHeader(1);
+  expected.ExpectIndexedHeader(126);
+  expected.ExpectIndexedHeader(127);
+  expected.ExpectIndexedHeader(128);
+  expected.ExpectDynamicTableSizeUpdate(0);
+  expected.ExpectDynamicTableSizeUpdate(1);
+  expected.ExpectDynamicTableSizeUpdate(14);
+  expected.ExpectDynamicTableSizeUpdate(15);
+  expected.ExpectDynamicTableSizeUpdate(30);
+  expected.ExpectDynamicTableSizeUpdate(31);
+  expected.ExpectDynamicTableSizeUpdate(4095);
+  expected.ExpectDynamicTableSizeUpdate(4096);
+  expected.ExpectDynamicTableSizeUpdate(8192);
+  for (auto type : {HpackEntryType::kIndexedLiteralHeader,
+                    HpackEntryType::kUnindexedLiteralHeader,
+                    HpackEntryType::kNeverIndexedLiteralHeader}) {
+    for (bool value_huffman : {false, true}) {
+      // An entry with an index for the name. Ensure the name index
+      // is not zero by adding one to the Rand8() result.
+      expected.ExpectNameIndexAndLiteralValue(type, Rand8() + 1, value_huffman,
+                                              Rand8String());
+      // And two entries with literal names, one plain, one huffman encoded.
+      expected.ExpectLiteralNameAndValue(type, false, Rand8String(),
+                                         value_huffman, Rand8String());
+      expected.ExpectLiteralNameAndValue(type, true, Rand8String(),
+                                         value_huffman, Rand8String());
+    }
+  }
+  // Shuffle the entries and serialize them to produce an HPACK block.
+  expected.ShuffleEntries(RandomPtr());
+  HpackBlockBuilder hbb;
+  expected.AppendToHpackBlockBuilder(&hbb);
+
+  NoArgValidator do_check = [expected, this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected));
+  };
+  EXPECT_TRUE(
+      DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder.cc b/http2/hpack/decoder/hpack_decoder.cc
new file mode 100644
index 0000000..d6897cb
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder.cc
@@ -0,0 +1,122 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h"
+
+namespace http2 {
+
+HpackDecoder::HpackDecoder(HpackDecoderListener* listener,
+                           size_t max_string_size)
+    : decoder_state_(listener),
+      entry_buffer_(&decoder_state_, max_string_size),
+      block_decoder_(&entry_buffer_),
+      error_detected_(false) {}
+
+HpackDecoder::~HpackDecoder() = default;
+
+void HpackDecoder::set_tables_debug_listener(
+    HpackDecoderTablesDebugListener* debug_listener) {
+  decoder_state_.set_tables_debug_listener(debug_listener);
+}
+
+void HpackDecoder::set_max_string_size_bytes(size_t max_string_size_bytes) {
+  entry_buffer_.set_max_string_size_bytes(max_string_size_bytes);
+}
+
+void HpackDecoder::ApplyHeaderTableSizeSetting(uint32_t max_header_table_size) {
+  decoder_state_.ApplyHeaderTableSizeSetting(max_header_table_size);
+}
+
+bool HpackDecoder::StartDecodingBlock() {
+  DVLOG(3) << "HpackDecoder::StartDecodingBlock, error_detected="
+           << (error_detected() ? "true" : "false");
+  if (error_detected()) {
+    return false;
+  }
+  // TODO(jamessynge): Eliminate Reset(), which shouldn't be necessary
+  // if there are no errors, and shouldn't be necessary with errors if
+  // we never resume decoding after an error has been detected.
+  block_decoder_.Reset();
+  decoder_state_.OnHeaderBlockStart();
+  return true;
+}
+
+bool HpackDecoder::DecodeFragment(DecodeBuffer* db) {
+  DVLOG(3) << "HpackDecoder::DecodeFragment, error_detected="
+           << (error_detected() ? "true" : "false")
+           << ", size=" << db->Remaining();
+  if (error_detected()) {
+    return false;
+  }
+  // Decode contents of db as an HPACK block fragment, forwards the decoded
+  // entries to entry_buffer_, which in turn forwards them to decode_state_,
+  // which finally forwards them to the HpackDecoderListener.
+  DecodeStatus status = block_decoder_.Decode(db);
+  if (status == DecodeStatus::kDecodeError) {
+    // This has probably already been reported, but just in case...
+    ReportError("HPACK block malformed.");
+    return false;
+  } else if (error_detected()) {
+    return false;
+  }
+  // Should be positioned between entries iff decoding is complete.
+  DCHECK_EQ(block_decoder_.before_entry(), status == DecodeStatus::kDecodeDone)
+      << status;
+  if (!block_decoder_.before_entry()) {
+    entry_buffer_.BufferStringsIfUnbuffered();
+  }
+  return true;
+}
+
+bool HpackDecoder::EndDecodingBlock() {
+  DVLOG(3) << "HpackDecoder::EndDecodingBlock, error_detected="
+           << (error_detected() ? "true" : "false");
+  if (error_detected()) {
+    return false;
+  }
+  if (!block_decoder_.before_entry()) {
+    // The HPACK block ended in the middle of an entry.
+    ReportError("HPACK block truncated.");
+    return false;
+  }
+  decoder_state_.OnHeaderBlockEnd();
+  if (error_detected()) {
+    // HpackDecoderState will have reported the error.
+    return false;
+  }
+  return true;
+}
+
+bool HpackDecoder::error_detected() {
+  if (!error_detected_) {
+    if (entry_buffer_.error_detected()) {
+      DVLOG(2) << "HpackDecoder::error_detected in entry_buffer_";
+      error_detected_ = true;
+    } else if (decoder_state_.error_detected()) {
+      DVLOG(2) << "HpackDecoder::error_detected in decoder_state_";
+      error_detected_ = true;
+    }
+  }
+  return error_detected_;
+}
+
+size_t HpackDecoder::EstimateMemoryUsage() const {
+  return Http2EstimateMemoryUsage(entry_buffer_);
+}
+
+void HpackDecoder::ReportError(Http2StringPiece error_message) {
+  DVLOG(3) << "HpackDecoder::ReportError is new="
+           << (!error_detected_ ? "true" : "false")
+           << ", error_message: " << error_message;
+  if (!error_detected_) {
+    error_detected_ = true;
+    decoder_state_.listener()->OnHeaderErrorDetected(error_message);
+  }
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder.h b/http2/hpack/decoder/hpack_decoder.h
new file mode 100644
index 0000000..e173bc6
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder.h
@@ -0,0 +1,123 @@
+// 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.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_
+
+// Decodes HPACK blocks, calls an HpackDecoderListener with the decoded header
+// entries. Also notifies the listener of errors and of the boundaries of the
+// HPACK blocks.
+
+// TODO(jamessynge): Add feature allowing an HpackEntryDecoderListener
+// sub-class (and possibly others) to be passed in for counting events,
+// so that deciding whether to count is not done by having lots of if
+// statements, but instead by inserting an indirection only when needed.
+
+// TODO(jamessynge): Consider whether to return false from methods below
+// when an error has been previously detected. It protects calling code
+// from its failure to pay attention to previous errors, but should we
+// spend time to do that?
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+class HpackDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE HpackDecoder {
+ public:
+  HpackDecoder(HpackDecoderListener* listener, size_t max_string_size);
+  virtual ~HpackDecoder();
+
+  HpackDecoder(const HpackDecoder&) = delete;
+  HpackDecoder& operator=(const HpackDecoder&) = delete;
+
+  // Set listener to be notified of insertions into the HPACK dynamic table,
+  // and uses of those entries.
+  void set_tables_debug_listener(
+      HpackDecoderTablesDebugListener* debug_listener);
+
+  // max_string_size specifies the maximum size of an on-the-wire string (name
+  // or value, plain or Huffman encoded) that will be accepted. See sections
+  // 5.1 and 5.2 of RFC 7541. This is a defense against OOM attacks; HTTP/2
+  // allows a decoder to enforce any limit of the size of the header lists
+  // that it is willing to decode, including less than the MAX_HEADER_LIST_SIZE
+  // setting, a setting that is initially unlimited. For example, we might
+  // choose to send a MAX_HEADER_LIST_SIZE of 64KB, and to use that same value
+  // as the upper bound for individual strings.
+  void set_max_string_size_bytes(size_t max_string_size_bytes);
+
+  // ApplyHeaderTableSizeSetting notifies this object that this endpoint has
+  // received a SETTINGS ACK frame acknowledging an earlier SETTINGS frame from
+  // this endpoint specifying a new value for SETTINGS_HEADER_TABLE_SIZE (the
+  // maximum size of the dynamic table that this endpoint will use to decode
+  // HPACK blocks).
+  // Because a SETTINGS frame can contain SETTINGS_HEADER_TABLE_SIZE values,
+  // the caller must keep track of those multiple changes, and make
+  // corresponding calls to this method. In particular, a call must be made
+  // with the lowest value acknowledged by the peer, and a call must be made
+  // with the final value acknowledged, in that order; additional calls may
+  // be made if additional values were sent. These calls must be made between
+  // decoding the SETTINGS ACK, and before the next HPACK block is decoded.
+  void ApplyHeaderTableSizeSetting(uint32_t max_header_table_size);
+
+  // Prepares the decoder for decoding a new HPACK block, and announces this to
+  // its listener. Returns true if OK to continue with decoding, false if an
+  // error has been detected, which for StartDecodingBlock means the error was
+  // detected while decoding a previous HPACK block.
+  bool StartDecodingBlock();
+
+  // Decodes a fragment (some or all of the remainder) of an HPACK block,
+  // reporting header entries (name & value pairs) that it completely decodes
+  // in the process to the listener. Returns true successfully decoded, false if
+  // an error has been detected, either during decoding of the fragment, or
+  // prior to this call.
+  bool DecodeFragment(DecodeBuffer* db);
+
+  // Completes the process of decoding an HPACK block: if the HPACK block was
+  // properly terminated, announces the end of the header list to the listener
+  // and returns true; else returns false.
+  bool EndDecodingBlock();
+
+  // Was an error detected?
+  bool error_detected();
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+ private:
+  friend class test::HpackDecoderPeer;
+
+  // Reports an error to the listener IF this is the first error detected.
+  void ReportError(Http2StringPiece error_message);
+
+  // The decompressor state, as defined by HPACK (i.e. the static and dynamic
+  // tables).
+  HpackDecoderState decoder_state_;
+
+  // Assembles the various parts of a header entry into whole entries.
+  HpackWholeEntryBuffer entry_buffer_;
+
+  // The decoder of HPACK blocks into entry parts, passed to entry_buffer_.
+  HpackBlockDecoder block_decoder_;
+
+  // Has an error been detected?
+  bool error_detected_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_
diff --git a/http2/hpack/decoder/hpack_decoder_listener.cc b/http2/hpack/decoder/hpack_decoder_listener.cc
new file mode 100644
index 0000000..8afa8aa
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_listener.cc
@@ -0,0 +1,30 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+
+namespace http2 {
+
+HpackDecoderListener::HpackDecoderListener() = default;
+HpackDecoderListener::~HpackDecoderListener() = default;
+
+HpackDecoderNoOpListener::HpackDecoderNoOpListener() = default;
+HpackDecoderNoOpListener::~HpackDecoderNoOpListener() = default;
+
+void HpackDecoderNoOpListener::OnHeaderListStart() {}
+void HpackDecoderNoOpListener::OnHeader(HpackEntryType entry_type,
+                                        const HpackString& name,
+                                        const HpackString& value) {}
+void HpackDecoderNoOpListener::OnHeaderListEnd() {}
+void HpackDecoderNoOpListener::OnHeaderErrorDetected(
+    Http2StringPiece error_message) {}
+
+// static
+HpackDecoderNoOpListener* HpackDecoderNoOpListener::NoOpListener() {
+  static HpackDecoderNoOpListener* static_instance =
+      new HpackDecoderNoOpListener();
+  return static_instance;
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_listener.h b/http2/hpack/decoder/hpack_decoder_listener.h
new file mode 100644
index 0000000..fa68591
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_listener.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.
+
+// Defines HpackDecoderListener, the base class of listeners for HTTP header
+// lists decoded from an HPACK block.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderListener {
+ public:
+  HpackDecoderListener();
+  virtual ~HpackDecoderListener();
+
+  // OnHeaderListStart is called at the start of decoding an HPACK block into
+  // an HTTP/2 header list. Will only be called once per block, even if it
+  // extends into CONTINUATION frames.
+  virtual void OnHeaderListStart() = 0;
+
+  // Called for each header name-value pair that is decoded, in the order they
+  // appear in the HPACK block. Multiple values for a given key will be emitted
+  // as multiple calls to OnHeader.
+  virtual void OnHeader(HpackEntryType entry_type,
+                        const HpackString& name,
+                        const HpackString& value) = 0;
+
+  // OnHeaderListEnd is called after successfully decoding an HPACK block into
+  // an HTTP/2 header list. Will only be called once per block, even if it
+  // extends into CONTINUATION frames.
+  virtual void OnHeaderListEnd() = 0;
+
+  // OnHeaderErrorDetected is called if an error is detected while decoding.
+  // error_message may be used in a GOAWAY frame as the Opaque Data.
+  virtual void OnHeaderErrorDetected(Http2StringPiece error_message) = 0;
+};
+
+// A no-op implementation of HpackDecoderListener, useful for ignoring
+// callbacks once an error is detected.
+class HTTP2_EXPORT_PRIVATE HpackDecoderNoOpListener
+    : public HpackDecoderListener {
+ public:
+  HpackDecoderNoOpListener();
+  ~HpackDecoderNoOpListener() override;
+
+  void OnHeaderListStart() override;
+  void OnHeader(HpackEntryType entry_type,
+                const HpackString& name,
+                const HpackString& value) override;
+  void OnHeaderListEnd() override;
+  void OnHeaderErrorDetected(Http2StringPiece error_message) override;
+
+  // Returns a listener that ignores all the calls.
+  static HpackDecoderNoOpListener* NoOpListener();
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_
diff --git a/http2/hpack/decoder/hpack_decoder_state.cc b/http2/hpack/decoder/hpack_decoder_state.cc
new file mode 100644
index 0000000..2ddf421
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_state.cc
@@ -0,0 +1,218 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+namespace {
+
+HpackString ExtractHpackString(HpackDecoderStringBuffer* string_buffer) {
+  if (string_buffer->IsBuffered()) {
+    return HpackString(string_buffer->ReleaseString());
+  } else {
+    auto result = HpackString(string_buffer->str());
+    string_buffer->Reset();
+    return result;
+  }
+}
+
+}  // namespace
+
+HpackDecoderState::HpackDecoderState(HpackDecoderListener* listener)
+    : listener_(HTTP2_DIE_IF_NULL(listener)),
+      final_header_table_size_(Http2SettingsInfo::DefaultHeaderTableSize()),
+      lowest_header_table_size_(final_header_table_size_),
+      require_dynamic_table_size_update_(false),
+      allow_dynamic_table_size_update_(true),
+      saw_dynamic_table_size_update_(false),
+      error_detected_(false) {}
+HpackDecoderState::~HpackDecoderState() = default;
+
+void HpackDecoderState::set_tables_debug_listener(
+    HpackDecoderTablesDebugListener* debug_listener) {
+  decoder_tables_.set_debug_listener(debug_listener);
+}
+
+void HpackDecoderState::ApplyHeaderTableSizeSetting(
+    uint32_t header_table_size) {
+  DVLOG(2) << "HpackDecoderState::ApplyHeaderTableSizeSetting("
+           << header_table_size << ")";
+  DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
+  if (header_table_size < lowest_header_table_size_) {
+    lowest_header_table_size_ = header_table_size;
+  }
+  final_header_table_size_ = header_table_size;
+  DVLOG(2) << "low water mark: " << lowest_header_table_size_;
+  DVLOG(2) << "final limit: " << final_header_table_size_;
+}
+
+// Called to notify this object that we're starting to decode an HPACK block
+// (e.g. a HEADERS or PUSH_PROMISE frame's header has been decoded).
+void HpackDecoderState::OnHeaderBlockStart() {
+  DVLOG(2) << "HpackDecoderState::OnHeaderBlockStart";
+  // This instance can't be reused after an error has been detected, as we must
+  // assume that the encoder and decoder compression states are no longer
+  // synchronized.
+  DCHECK(!error_detected_);
+  DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
+  allow_dynamic_table_size_update_ = true;
+  saw_dynamic_table_size_update_ = false;
+  // If the peer has acknowledged a HEADER_TABLE_SIZE smaller than that which
+  // its HPACK encoder has been using, then the next HPACK block it sends MUST
+  // start with a Dynamic Table Size Update entry that is at least as low as
+  // lowest_header_table_size_. That may be followed by another as great as
+  // final_header_table_size_, if those are different.
+  require_dynamic_table_size_update_ =
+      (lowest_header_table_size_ <
+           decoder_tables_.current_header_table_size() ||
+       final_header_table_size_ < decoder_tables_.header_table_size_limit());
+  DVLOG(2) << "HpackDecoderState::OnHeaderListStart "
+           << "require_dynamic_table_size_update_="
+           << require_dynamic_table_size_update_;
+  listener_->OnHeaderListStart();
+}
+
+void HpackDecoderState::OnIndexedHeader(size_t index) {
+  DVLOG(2) << "HpackDecoderState::OnIndexedHeader: " << index;
+  if (error_detected_) {
+    return;
+  }
+  if (require_dynamic_table_size_update_) {
+    ReportError("Missing dynamic table size update.");
+    return;
+  }
+  allow_dynamic_table_size_update_ = false;
+  const HpackStringPair* entry = decoder_tables_.Lookup(index);
+  if (entry != nullptr) {
+    listener_->OnHeader(HpackEntryType::kIndexedHeader, entry->name,
+                        entry->value);
+  } else {
+    ReportError("Invalid index.");
+  }
+}
+
+void HpackDecoderState::OnNameIndexAndLiteralValue(
+    HpackEntryType entry_type,
+    size_t name_index,
+    HpackDecoderStringBuffer* value_buffer) {
+  DVLOG(2) << "HpackDecoderState::OnNameIndexAndLiteralValue " << entry_type
+           << ", " << name_index << ", " << value_buffer->str();
+  if (error_detected_) {
+    return;
+  }
+  if (require_dynamic_table_size_update_) {
+    ReportError("Missing dynamic table size update.");
+    return;
+  }
+  allow_dynamic_table_size_update_ = false;
+  const HpackStringPair* entry = decoder_tables_.Lookup(name_index);
+  if (entry != nullptr) {
+    HpackString value(ExtractHpackString(value_buffer));
+    listener_->OnHeader(entry_type, entry->name, value);
+    if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
+      decoder_tables_.Insert(entry->name, value);
+    }
+  } else {
+    ReportError("Invalid name index.");
+  }
+}
+
+void HpackDecoderState::OnLiteralNameAndValue(
+    HpackEntryType entry_type,
+    HpackDecoderStringBuffer* name_buffer,
+    HpackDecoderStringBuffer* value_buffer) {
+  DVLOG(2) << "HpackDecoderState::OnLiteralNameAndValue " << entry_type << ", "
+           << name_buffer->str() << ", " << value_buffer->str();
+  if (error_detected_) {
+    return;
+  }
+  if (require_dynamic_table_size_update_) {
+    ReportError("Missing dynamic table size update.");
+    return;
+  }
+  allow_dynamic_table_size_update_ = false;
+  HpackString name(ExtractHpackString(name_buffer));
+  HpackString value(ExtractHpackString(value_buffer));
+  listener_->OnHeader(entry_type, name, value);
+  if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
+    decoder_tables_.Insert(name, value);
+  }
+}
+
+void HpackDecoderState::OnDynamicTableSizeUpdate(size_t size_limit) {
+  DVLOG(2) << "HpackDecoderState::OnDynamicTableSizeUpdate " << size_limit
+           << ", required="
+           << (require_dynamic_table_size_update_ ? "true" : "false")
+           << ", allowed="
+           << (allow_dynamic_table_size_update_ ? "true" : "false");
+  if (error_detected_) {
+    return;
+  }
+  DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
+  if (!allow_dynamic_table_size_update_) {
+    // At most two dynamic table size updates allowed at the start, and not
+    // after a header.
+    ReportError("Dynamic table size update not allowed.");
+    return;
+  }
+  if (require_dynamic_table_size_update_) {
+    // The new size must not be greater than the low water mark.
+    if (size_limit > lowest_header_table_size_) {
+      ReportError("Initial dynamic table size update is above low water mark.");
+      return;
+    }
+    require_dynamic_table_size_update_ = false;
+  } else if (size_limit > final_header_table_size_) {
+    // The new size must not be greater than the final max header table size
+    // that the peer acknowledged.
+    ReportError("Dynamic table size update is above acknowledged setting.");
+    return;
+  }
+  decoder_tables_.DynamicTableSizeUpdate(size_limit);
+  if (saw_dynamic_table_size_update_) {
+    allow_dynamic_table_size_update_ = false;
+  } else {
+    saw_dynamic_table_size_update_ = true;
+  }
+  // We no longer need to keep an eye out for a lower header table size.
+  lowest_header_table_size_ = final_header_table_size_;
+}
+
+void HpackDecoderState::OnHpackDecodeError(Http2StringPiece error_message) {
+  DVLOG(2) << "HpackDecoderState::OnHpackDecodeError " << error_message;
+  if (!error_detected_) {
+    ReportError(error_message);
+  }
+}
+
+void HpackDecoderState::OnHeaderBlockEnd() {
+  DVLOG(2) << "HpackDecoderState::OnHeaderBlockEnd";
+  if (error_detected_) {
+    return;
+  }
+  if (require_dynamic_table_size_update_) {
+    // Apparently the HPACK block was empty, but we needed it to contain at
+    // least 1 dynamic table size update.
+    ReportError("Missing dynamic table size update.");
+  } else {
+    listener_->OnHeaderListEnd();
+  }
+}
+
+void HpackDecoderState::ReportError(Http2StringPiece error_message) {
+  DVLOG(2) << "HpackDecoderState::ReportError is new="
+           << (!error_detected_ ? "true" : "false")
+           << ", error_message: " << error_message;
+  if (!error_detected_) {
+    listener_->OnHeaderErrorDetected(error_message);
+    error_detected_ = true;
+  }
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_state.h b/http2/hpack/decoder/hpack_decoder_state.h
new file mode 100644
index 0000000..fd34179
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_state.h
@@ -0,0 +1,129 @@
+// 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.
+
+// HpackDecoderState maintains the HPACK decompressor state; i.e. updates the
+// HPACK dynamic table according to RFC 7541 as the entries in an HPACK block
+// are decoded, and reads from the static and dynamic tables in order to build
+// complete header entries. Calls an HpackDecoderListener with the completely
+// decoded headers (i.e. after resolving table indices into names or values),
+// thus translating the decoded HPACK entries into HTTP/2 headers.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+class HpackDecoderStatePeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderState : public HpackWholeEntryListener {
+ public:
+  explicit HpackDecoderState(HpackDecoderListener* listener);
+  ~HpackDecoderState() override;
+
+  HpackDecoderState(const HpackDecoderState&) = delete;
+  HpackDecoderState& operator=(const HpackDecoderState&) = delete;
+
+  // Set the listener to be notified when a whole entry has been decoded,
+  // including resolving name or name and value references.
+  // The listener may be changed at any time.
+  HpackDecoderListener* listener() const { return listener_; }
+
+  // Set listener to be notified of insertions into the HPACK dynamic table,
+  // and uses of those entries.
+  void set_tables_debug_listener(
+      HpackDecoderTablesDebugListener* debug_listener);
+
+  // ApplyHeaderTableSizeSetting notifies this object that this endpoint has
+  // received a SETTINGS ACK frame acknowledging an earlier SETTINGS frame from
+  // this endpoint specifying a new value for SETTINGS_HEADER_TABLE_SIZE (the
+  // maximum size of the dynamic table that this endpoint will use to decode
+  // HPACK blocks).
+  // Because a SETTINGS frame can contain SETTINGS_HEADER_TABLE_SIZE values,
+  // the caller must keep track of those multiple changes, and make
+  // corresponding calls to this method. In particular, a call must be made
+  // with the lowest value acknowledged by the peer, and a call must be made
+  // with the final value acknowledged, in that order; additional calls may
+  // be made if additional values were sent. These calls must be made between
+  // decoding the SETTINGS ACK, and before the next HPACK block is decoded.
+  void ApplyHeaderTableSizeSetting(uint32_t max_header_table_size);
+
+  // OnHeaderBlockStart notifies this object that we're starting to decode the
+  // HPACK payload of a HEADERS or PUSH_PROMISE frame.
+  void OnHeaderBlockStart();
+
+  // Implement the HpackWholeEntryListener methods, each of which notifies this
+  // object when an entire entry has been decoded.
+  void OnIndexedHeader(size_t index) override;
+  void OnNameIndexAndLiteralValue(
+      HpackEntryType entry_type,
+      size_t name_index,
+      HpackDecoderStringBuffer* value_buffer) override;
+  void OnLiteralNameAndValue(HpackEntryType entry_type,
+                             HpackDecoderStringBuffer* name_buffer,
+                             HpackDecoderStringBuffer* value_buffer) override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+  void OnHpackDecodeError(Http2StringPiece error_message) override;
+
+  // OnHeaderBlockEnd notifies this object that an entire HPACK block has been
+  // decoded, which might have extended into CONTINUATION blocks.
+  void OnHeaderBlockEnd();
+
+  // Was an error detected? After an error has been detected and reported,
+  // no further callbacks will be made to the listener.
+  bool error_detected() const { return error_detected_; }
+
+  const HpackDecoderTables& decoder_tables_for_test() const {
+    return decoder_tables_;
+  }
+
+ private:
+  friend class test::HpackDecoderStatePeer;
+
+  // Reports an error to the listener IF this is the first error detected.
+  void ReportError(Http2StringPiece error_message);
+
+  // The static and dynamic HPACK tables.
+  HpackDecoderTables decoder_tables_;
+
+  // The listener to be notified of headers, the start and end of header
+  // lists, and of errors.
+  HpackDecoderListener* listener_;
+
+  // The most recent HEADER_TABLE_SIZE setting acknowledged by the peer.
+  uint32_t final_header_table_size_;
+
+  // The lowest HEADER_TABLE_SIZE setting acknowledged by the peer; valid until
+  // the next HPACK block is decoded.
+  // TODO(jamessynge): Test raising the HEADER_TABLE_SIZE.
+  uint32_t lowest_header_table_size_;
+
+  // Must the next (first) HPACK entry be a dynamic table size update?
+  bool require_dynamic_table_size_update_;
+
+  // May the next (first or second) HPACK entry be a dynamic table size update?
+  bool allow_dynamic_table_size_update_;
+
+  // Have we already seen a dynamic table size update in this HPACK block?
+  bool saw_dynamic_table_size_update_;
+
+  // Has an error already been detected and reported to the listener?
+  bool error_detected_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
diff --git a/http2/hpack/decoder/hpack_decoder_state_test.cc b/http2/hpack/decoder/hpack_decoder_state_test.cc
new file mode 100644
index 0000000..115827e
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_state_test.cc
@@ -0,0 +1,539 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+
+// Tests of HpackDecoderState.
+
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Mock;
+using ::testing::StrictMock;
+
+namespace http2 {
+namespace test {
+class HpackDecoderStatePeer {
+ public:
+  static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) {
+    return &state->decoder_tables_;
+  }
+};
+
+namespace {
+
+class MockHpackDecoderListener : public HpackDecoderListener {
+ public:
+  MOCK_METHOD0(OnHeaderListStart, void());
+  MOCK_METHOD3(OnHeader,
+               void(HpackEntryType entry_type,
+                    const HpackString& name,
+                    const HpackString& value));
+  MOCK_METHOD0(OnHeaderListEnd, void());
+  MOCK_METHOD1(OnHeaderErrorDetected, void(Http2StringPiece error_message));
+};
+
+enum StringBacking { STATIC, UNBUFFERED, BUFFERED };
+
+class HpackDecoderStateTest : public ::testing::Test {
+ protected:
+  HpackDecoderStateTest() : decoder_state_(&listener_) {}
+
+  HpackDecoderTables* GetDecoderTables() {
+    return HpackDecoderStatePeer::GetDecoderTables(&decoder_state_);
+  }
+
+  const HpackStringPair* Lookup(size_t index) {
+    return GetDecoderTables()->Lookup(index);
+  }
+
+  size_t current_header_table_size() {
+    return GetDecoderTables()->current_header_table_size();
+  }
+
+  size_t header_table_size_limit() {
+    return GetDecoderTables()->header_table_size_limit();
+  }
+
+  void set_header_table_size_limit(size_t size) {
+    GetDecoderTables()->DynamicTableSizeUpdate(size);
+  }
+
+  void SetStringBuffer(const char* s,
+                       StringBacking backing,
+                       HpackDecoderStringBuffer* string_buffer) {
+    switch (backing) {
+      case STATIC:
+        string_buffer->Set(s, true);
+        break;
+      case UNBUFFERED:
+        string_buffer->Set(s, false);
+        break;
+      case BUFFERED:
+        string_buffer->Set(s, false);
+        string_buffer->BufferStringIfUnbuffered();
+        break;
+    }
+  }
+
+  void SetName(const char* s, StringBacking backing) {
+    SetStringBuffer(s, backing, &name_buffer_);
+  }
+
+  void SetValue(const char* s, StringBacking backing) {
+    SetStringBuffer(s, backing, &value_buffer_);
+  }
+
+  void SendStartAndVerifyCallback() {
+    EXPECT_CALL(listener_, OnHeaderListStart());
+    decoder_state_.OnHeaderBlockStart();
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  void SendSizeUpdate(size_t size) {
+    decoder_state_.OnDynamicTableSizeUpdate(size);
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  void SendIndexAndVerifyCallback(size_t index,
+                                  HpackEntryType expected_type,
+                                  const char* expected_name,
+                                  const char* expected_value) {
+    EXPECT_CALL(listener_,
+                OnHeader(expected_type, Eq(expected_name), Eq(expected_value)));
+    decoder_state_.OnIndexedHeader(index);
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  void SendValueAndVerifyCallback(size_t name_index,
+                                  HpackEntryType entry_type,
+                                  const char* name,
+                                  const char* value,
+                                  StringBacking value_backing) {
+    SetValue(value, value_backing);
+    EXPECT_CALL(listener_, OnHeader(entry_type, Eq(name), Eq(value)));
+    decoder_state_.OnNameIndexAndLiteralValue(entry_type, name_index,
+                                              &value_buffer_);
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  void SendNameAndValueAndVerifyCallback(HpackEntryType entry_type,
+                                         const char* name,
+                                         StringBacking name_backing,
+                                         const char* value,
+                                         StringBacking value_backing) {
+    SetName(name, name_backing);
+    SetValue(value, value_backing);
+    EXPECT_CALL(listener_, OnHeader(entry_type, Eq(name), Eq(value)));
+    decoder_state_.OnLiteralNameAndValue(entry_type, &name_buffer_,
+                                         &value_buffer_);
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  void SendEndAndVerifyCallback() {
+    EXPECT_CALL(listener_, OnHeaderListEnd());
+    decoder_state_.OnHeaderBlockEnd();
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  // dynamic_index is one-based, because that is the way RFC 7541 shows it.
+  AssertionResult VerifyEntry(size_t dynamic_index,
+                              const char* name,
+                              const char* value) {
+    const HpackStringPair* entry =
+        Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+    VERIFY_NE(entry, nullptr);
+    VERIFY_EQ(entry->name.ToStringPiece(), name);
+    VERIFY_EQ(entry->value.ToStringPiece(), value);
+    return AssertionSuccess();
+  }
+  AssertionResult VerifyNoEntry(size_t dynamic_index) {
+    const HpackStringPair* entry =
+        Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+    VERIFY_EQ(entry, nullptr);
+    return AssertionSuccess();
+  }
+  AssertionResult VerifyDynamicTableContents(
+      const std::vector<std::pair<const char*, const char*>>& entries) {
+    size_t index = 1;
+    for (const auto& entry : entries) {
+      VERIFY_SUCCESS(VerifyEntry(index, entry.first, entry.second));
+      ++index;
+    }
+    VERIFY_SUCCESS(VerifyNoEntry(index));
+    return AssertionSuccess();
+  }
+
+  StrictMock<MockHpackDecoderListener> listener_;
+  HpackDecoderState decoder_state_;
+  HpackDecoderStringBuffer name_buffer_, value_buffer_;
+};
+
+// Test based on RFC 7541, section C.3: Request Examples without Huffman Coding.
+// This section shows several consecutive header lists, corresponding to HTTP
+// requests, on the same connection.
+TEST_F(HpackDecoderStateTest, C3_RequestExamples) {
+  // C.3.1 First Request
+  //
+  // Header list to encode:
+  //
+  //   :method: GET
+  //   :scheme: http
+  //   :path: /
+  //   :authority: www.example.com
+
+  SendStartAndVerifyCallback();
+  SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method",
+                             "GET");
+  SendIndexAndVerifyCallback(6, HpackEntryType::kIndexedHeader, ":scheme",
+                             "http");
+  SendIndexAndVerifyCallback(4, HpackEntryType::kIndexedHeader, ":path", "/");
+  SendValueAndVerifyCallback(1, HpackEntryType::kIndexedLiteralHeader,
+                             ":authority", "www.example.com", UNBUFFERED);
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  57) :authority: www.example.com
+  //         Table size:  57
+
+  ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}}));
+  ASSERT_EQ(57u, current_header_table_size());
+
+  // C.3.2 Second Request
+  //
+  // Header list to encode:
+  //
+  //   :method: GET
+  //   :scheme: http
+  //   :path: /
+  //   :authority: www.example.com
+  //   cache-control: no-cache
+
+  SendStartAndVerifyCallback();
+  SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method",
+                             "GET");
+  SendIndexAndVerifyCallback(6, HpackEntryType::kIndexedHeader, ":scheme",
+                             "http");
+  SendIndexAndVerifyCallback(4, HpackEntryType::kIndexedHeader, ":path", "/");
+  SendIndexAndVerifyCallback(62, HpackEntryType::kIndexedHeader, ":authority",
+                             "www.example.com");
+  SendValueAndVerifyCallback(24, HpackEntryType::kIndexedLiteralHeader,
+                             "cache-control", "no-cache", UNBUFFERED);
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  53) cache-control: no-cache
+  //   [  2] (s =  57) :authority: www.example.com
+  //         Table size: 110
+
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"cache-control", "no-cache"}, {":authority", "www.example.com"}}));
+  ASSERT_EQ(110u, current_header_table_size());
+
+  // C.3.3 Third Request
+  //
+  // Header list to encode:
+  //
+  //   :method: GET
+  //   :scheme: https
+  //   :path: /index.html
+  //   :authority: www.example.com
+  //   custom-key: custom-value
+
+  SendStartAndVerifyCallback();
+  SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method",
+                             "GET");
+  SendIndexAndVerifyCallback(7, HpackEntryType::kIndexedHeader, ":scheme",
+                             "https");
+  SendIndexAndVerifyCallback(5, HpackEntryType::kIndexedHeader, ":path",
+                             "/index.html");
+  SendIndexAndVerifyCallback(63, HpackEntryType::kIndexedHeader, ":authority",
+                             "www.example.com");
+  SendNameAndValueAndVerifyCallback(HpackEntryType::kIndexedLiteralHeader,
+                                    "custom-key", UNBUFFERED, "custom-value",
+                                    UNBUFFERED);
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  54) custom-key: custom-value
+  //   [  2] (s =  53) cache-control: no-cache
+  //   [  3] (s =  57) :authority: www.example.com
+  //         Table size: 164
+
+  ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"},
+                                          {"cache-control", "no-cache"},
+                                          {":authority", "www.example.com"}}));
+  ASSERT_EQ(164u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.5: Response Examples without Huffman
+// Coding. This section shows several consecutive header lists, corresponding
+// to HTTP responses, on the same connection. The HTTP/2 setting parameter
+// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing
+// some evictions to occur.
+TEST_F(HpackDecoderStateTest, C5_ResponseExamples) {
+  set_header_table_size_limit(256);
+
+  // C.5.1 First Response
+  //
+  // Header list to encode:
+  //
+  //   :status: 302
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+
+  SendStartAndVerifyCallback();
+  SendValueAndVerifyCallback(8, HpackEntryType::kIndexedLiteralHeader,
+                             ":status", "302", BUFFERED);
+  SendValueAndVerifyCallback(24, HpackEntryType::kIndexedLiteralHeader,
+                             "cache-control", "private", UNBUFFERED);
+  SendValueAndVerifyCallback(33, HpackEntryType::kIndexedLiteralHeader, "date",
+                             "Mon, 21 Oct 2013 20:13:21 GMT", UNBUFFERED);
+  SendValueAndVerifyCallback(46, HpackEntryType::kIndexedLiteralHeader,
+                             "location", "https://www.example.com", UNBUFFERED);
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  63) location: https://www.example.com
+  //   [  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  3] (s =  52) cache-control: private
+  //   [  4] (s =  42) :status: 302
+  //         Table size: 222
+
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"},
+                                  {":status", "302"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.2 Second Response
+  //
+  // The (":status", "302") header field is evicted from the dynamic table to
+  // free space to allow adding the (":status", "307") header field.
+  //
+  // Header list to encode:
+  //
+  //   :status: 307
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+
+  SendStartAndVerifyCallback();
+  SendValueAndVerifyCallback(8, HpackEntryType::kIndexedLiteralHeader,
+                             ":status", "307", BUFFERED);
+  SendIndexAndVerifyCallback(65, HpackEntryType::kIndexedHeader,
+                             "cache-control", "private");
+  SendIndexAndVerifyCallback(64, HpackEntryType::kIndexedHeader, "date",
+                             "Mon, 21 Oct 2013 20:13:21 GMT");
+  SendIndexAndVerifyCallback(63, HpackEntryType::kIndexedHeader, "location",
+                             "https://www.example.com");
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  42) :status: 307
+  //   [  2] (s =  63) location: https://www.example.com
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  4] (s =  52) cache-control: private
+  //         Table size: 222
+
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{":status", "307"},
+                                  {"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.3 Third Response
+  //
+  // Several header fields are evicted from the dynamic table during the
+  // processing of this header list.
+  //
+  // Header list to encode:
+  //
+  //   :status: 200
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:22 GMT
+  //   location: https://www.example.com
+  //   content-encoding: gzip
+  //   set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
+
+  SendStartAndVerifyCallback();
+  SendIndexAndVerifyCallback(8, HpackEntryType::kIndexedHeader, ":status",
+                             "200");
+  SendIndexAndVerifyCallback(65, HpackEntryType::kIndexedHeader,
+                             "cache-control", "private");
+  SendValueAndVerifyCallback(33, HpackEntryType::kIndexedLiteralHeader, "date",
+                             "Mon, 21 Oct 2013 20:13:22 GMT", BUFFERED);
+  SendIndexAndVerifyCallback(64, HpackEntryType::kIndexedHeader, "location",
+                             "https://www.example.com");
+  SendValueAndVerifyCallback(26, HpackEntryType::kIndexedLiteralHeader,
+                             "content-encoding", "gzip", UNBUFFERED);
+  SendValueAndVerifyCallback(
+      55, HpackEntryType::kIndexedLiteralHeader, "set-cookie",
+      "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", BUFFERED);
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
+  //                    max-age=3600; version=1
+  //   [  2] (s =  52) content-encoding: gzip
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT
+  //         Table size: 215
+
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"set-cookie",
+        "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+       {"content-encoding", "gzip"},
+       {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}}));
+  ASSERT_EQ(215u, current_header_table_size());
+}
+
+// Confirm that the table size can be changed, but at most twice.
+TEST_F(HpackDecoderStateTest, OptionalTableSizeChanges) {
+  SendStartAndVerifyCallback();
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  SendSizeUpdate(1024);
+  EXPECT_EQ(1024u, header_table_size_limit());
+  SendSizeUpdate(0);
+  EXPECT_EQ(0u, header_table_size_limit());
+
+  // Three updates aren't allowed.
+  EXPECT_CALL(listener_,
+              OnHeaderErrorDetected(HasSubstr("size update not allowed")));
+  SendSizeUpdate(0);
+}
+
+// Confirm that required size updates are indeed required before headers.
+TEST_F(HpackDecoderStateTest, RequiredTableSizeChangeBeforeHeader) {
+  decoder_state_.ApplyHeaderTableSizeSetting(1024);
+  decoder_state_.ApplyHeaderTableSizeSetting(2048);
+
+  // First provide the required update, and an allowed second update.
+  SendStartAndVerifyCallback();
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  SendSizeUpdate(1024);
+  EXPECT_EQ(1024u, header_table_size_limit());
+  SendSizeUpdate(1500);
+  EXPECT_EQ(1500u, header_table_size_limit());
+  SendEndAndVerifyCallback();
+
+  // Another HPACK block, but this time missing the required size update.
+  decoder_state_.ApplyHeaderTableSizeSetting(1024);
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_, OnHeaderErrorDetected(
+                             HasSubstr("Missing dynamic table size update")));
+  decoder_state_.OnIndexedHeader(1);
+
+  // Further decoded entries are ignored.
+  decoder_state_.OnIndexedHeader(1);
+  decoder_state_.OnDynamicTableSizeUpdate(1);
+  SetValue("value", UNBUFFERED);
+  decoder_state_.OnNameIndexAndLiteralValue(
+      HpackEntryType::kIndexedLiteralHeader, 4, &value_buffer_);
+  SetName("name", UNBUFFERED);
+  decoder_state_.OnLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader,
+                                       &name_buffer_, &value_buffer_);
+  decoder_state_.OnHeaderBlockEnd();
+  decoder_state_.OnHpackDecodeError("NOT FORWARDED");
+}
+
+// Confirm that required size updates are validated.
+TEST_F(HpackDecoderStateTest, InvalidRequiredSizeUpdate) {
+  // Require a size update, but provide one that isn't small enough.
+  decoder_state_.ApplyHeaderTableSizeSetting(1024);
+  SendStartAndVerifyCallback();
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  EXPECT_CALL(listener_,
+              OnHeaderErrorDetected(HasSubstr("above low water mark")));
+  SendSizeUpdate(2048);
+}
+
+// Confirm that required size updates are indeed required before the end.
+TEST_F(HpackDecoderStateTest, RequiredTableSizeChangeBeforeEnd) {
+  decoder_state_.ApplyHeaderTableSizeSetting(1024);
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_, OnHeaderErrorDetected(
+                             HasSubstr("Missing dynamic table size update")));
+  decoder_state_.OnHeaderBlockEnd();
+}
+
+// Confirm that optional size updates are validated.
+TEST_F(HpackDecoderStateTest, InvalidOptionalSizeUpdate) {
+  // Require a size update, but provide one that isn't small enough.
+  SendStartAndVerifyCallback();
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  EXPECT_CALL(listener_,
+              OnHeaderErrorDetected(HasSubstr("size update is above")));
+  SendSizeUpdate(Http2SettingsInfo::DefaultHeaderTableSize() + 1);
+}
+
+TEST_F(HpackDecoderStateTest, InvalidStaticIndex) {
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_, OnHeaderErrorDetected(HasSubstr("Invalid index")));
+  decoder_state_.OnIndexedHeader(0);
+}
+
+TEST_F(HpackDecoderStateTest, InvalidDynamicIndex) {
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_, OnHeaderErrorDetected(HasSubstr("Invalid index")));
+  decoder_state_.OnIndexedHeader(kFirstDynamicTableIndex);
+}
+
+TEST_F(HpackDecoderStateTest, InvalidNameIndex) {
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_,
+              OnHeaderErrorDetected(HasSubstr("Invalid name index")));
+  SetValue("value", UNBUFFERED);
+  decoder_state_.OnNameIndexAndLiteralValue(
+      HpackEntryType::kIndexedLiteralHeader, kFirstDynamicTableIndex,
+      &value_buffer_);
+}
+
+TEST_F(HpackDecoderStateTest, ErrorsSuppressCallbacks) {
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_,
+              OnHeaderErrorDetected(Http2StringPiece("Huffman decode error.")));
+  decoder_state_.OnHpackDecodeError("Huffman decode error.");
+
+  // Further decoded entries are ignored.
+  decoder_state_.OnIndexedHeader(1);
+  decoder_state_.OnDynamicTableSizeUpdate(1);
+  SetValue("value", UNBUFFERED);
+  decoder_state_.OnNameIndexAndLiteralValue(
+      HpackEntryType::kIndexedLiteralHeader, 4, &value_buffer_);
+  SetName("name", UNBUFFERED);
+  decoder_state_.OnLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader,
+                                       &name_buffer_, &value_buffer_);
+  decoder_state_.OnHeaderBlockEnd();
+  decoder_state_.OnHpackDecodeError("NOT FORWARDED");
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_string_buffer.cc b/http2/hpack/decoder/hpack_decoder_string_buffer.cc
new file mode 100644
index 0000000..b20c37a
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_string_buffer.cc
@@ -0,0 +1,235 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+                         const HpackDecoderStringBuffer::State v) {
+  switch (v) {
+    case HpackDecoderStringBuffer::State::RESET:
+      return out << "RESET";
+    case HpackDecoderStringBuffer::State::COLLECTING:
+      return out << "COLLECTING";
+    case HpackDecoderStringBuffer::State::COMPLETE:
+      return out << "COMPLETE";
+  }
+  // Since the value doesn't come over the wire, only a programming bug should
+  // result in reaching this point.
+  int unknown = static_cast<int>(v);
+  HTTP2_BUG << "Invalid HpackDecoderStringBuffer::State: " << unknown;
+  return out << "HpackDecoderStringBuffer::State(" << unknown << ")";
+}
+
+std::ostream& operator<<(std::ostream& out,
+                         const HpackDecoderStringBuffer::Backing v) {
+  switch (v) {
+    case HpackDecoderStringBuffer::Backing::RESET:
+      return out << "RESET";
+    case HpackDecoderStringBuffer::Backing::UNBUFFERED:
+      return out << "UNBUFFERED";
+    case HpackDecoderStringBuffer::Backing::BUFFERED:
+      return out << "BUFFERED";
+    case HpackDecoderStringBuffer::Backing::STATIC:
+      return out << "STATIC";
+  }
+  // Since the value doesn't come over the wire, only a programming bug should
+  // result in reaching this point.
+  auto v2 = static_cast<int>(v);
+  HTTP2_BUG << "Invalid HpackDecoderStringBuffer::Backing: " << v2;
+  return out << "HpackDecoderStringBuffer::Backing(" << v2 << ")";
+}
+
+HpackDecoderStringBuffer::HpackDecoderStringBuffer()
+    : remaining_len_(0),
+      is_huffman_encoded_(false),
+      state_(State::RESET),
+      backing_(Backing::RESET) {}
+HpackDecoderStringBuffer::~HpackDecoderStringBuffer() = default;
+
+void HpackDecoderStringBuffer::Reset() {
+  DVLOG(3) << "HpackDecoderStringBuffer::Reset";
+  state_ = State::RESET;
+}
+
+void HpackDecoderStringBuffer::Set(Http2StringPiece value, bool is_static) {
+  DVLOG(2) << "HpackDecoderStringBuffer::Set";
+  DCHECK_EQ(state_, State::RESET);
+  value_ = value;
+  state_ = State::COMPLETE;
+  backing_ = is_static ? Backing::STATIC : Backing::UNBUFFERED;
+  // TODO(jamessynge): Determine which of these two fields must be set.
+  remaining_len_ = 0;
+  is_huffman_encoded_ = false;
+}
+
+void HpackDecoderStringBuffer::OnStart(bool huffman_encoded, size_t len) {
+  DVLOG(2) << "HpackDecoderStringBuffer::OnStart";
+  DCHECK_EQ(state_, State::RESET);
+
+  remaining_len_ = len;
+  is_huffman_encoded_ = huffman_encoded;
+  state_ = State::COLLECTING;
+
+  if (huffman_encoded) {
+    // We don't set, clear or use value_ for buffered strings until OnEnd.
+    decoder_.Reset();
+    buffer_.clear();
+    backing_ = Backing::BUFFERED;
+
+    // Reserve space in buffer_ for the uncompressed string, assuming the
+    // maximum expansion. The shortest Huffman codes in the RFC are 5 bits long,
+    // which then expand to 8 bits during decoding (i.e. each code is for one
+    // plain text octet, aka byte), so the maximum size is 60% longer than the
+    // encoded size.
+    len = len * 8 / 5;
+    if (buffer_.capacity() < len) {
+      buffer_.reserve(len);
+    }
+  } else {
+    // Assume for now that we won't need to use buffer_, so don't reserve space
+    // in it.
+    backing_ = Backing::RESET;
+    // OnData is not called for empty (zero length) strings, so make sure that
+    // value_ is cleared.
+    value_ = Http2StringPiece();
+  }
+}
+
+bool HpackDecoderStringBuffer::OnData(const char* data, size_t len) {
+  DVLOG(2) << "HpackDecoderStringBuffer::OnData state=" << state_
+           << ", backing=" << backing_;
+  DCHECK_EQ(state_, State::COLLECTING);
+  DCHECK_LE(len, remaining_len_);
+  remaining_len_ -= len;
+
+  if (is_huffman_encoded_) {
+    DCHECK_EQ(backing_, Backing::BUFFERED);
+    return decoder_.Decode(Http2StringPiece(data, len), &buffer_);
+  }
+
+  if (backing_ == Backing::RESET) {
+    // This is the first call to OnData. If data contains the entire string,
+    // don't copy the string. If we later find that the HPACK entry is split
+    // across input buffers, then we'll copy the string into buffer_.
+    if (remaining_len_ == 0) {
+      value_ = Http2StringPiece(data, len);
+      backing_ = Backing::UNBUFFERED;
+      return true;
+    }
+
+    // We need to buffer the string because it is split across input buffers.
+    // Reserve space in buffer_ for the entire string.
+    backing_ = Backing::BUFFERED;
+    buffer_.reserve(remaining_len_ + len);
+    buffer_.assign(data, len);
+    return true;
+  }
+
+  // This is not the first call to OnData for this string, so it should be
+  // buffered.
+  DCHECK_EQ(backing_, Backing::BUFFERED);
+
+  // Append to the current contents of the buffer.
+  buffer_.append(data, len);
+  return true;
+}
+
+bool HpackDecoderStringBuffer::OnEnd() {
+  DVLOG(2) << "HpackDecoderStringBuffer::OnEnd";
+  DCHECK_EQ(state_, State::COLLECTING);
+  DCHECK_EQ(0u, remaining_len_);
+
+  if (is_huffman_encoded_) {
+    DCHECK_EQ(backing_, Backing::BUFFERED);
+    // Did the Huffman encoding of the string end properly?
+    if (!decoder_.InputProperlyTerminated()) {
+      return false;  // No, it didn't.
+    }
+    value_ = buffer_;
+  } else if (backing_ == Backing::BUFFERED) {
+    value_ = buffer_;
+  }
+  state_ = State::COMPLETE;
+  return true;
+}
+
+void HpackDecoderStringBuffer::BufferStringIfUnbuffered() {
+  DVLOG(3) << "HpackDecoderStringBuffer::BufferStringIfUnbuffered state="
+           << state_ << ", backing=" << backing_;
+  if (state_ != State::RESET && backing_ == Backing::UNBUFFERED) {
+    DVLOG(2) << "HpackDecoderStringBuffer buffering Http2String of length "
+             << value_.size();
+    buffer_.assign(value_.data(), value_.size());
+    if (state_ == State::COMPLETE) {
+      value_ = buffer_;
+    }
+    backing_ = Backing::BUFFERED;
+  }
+}
+
+bool HpackDecoderStringBuffer::IsBuffered() const {
+  DVLOG(3) << "HpackDecoderStringBuffer::IsBuffered";
+  return state_ != State::RESET && backing_ == Backing::BUFFERED;
+}
+
+size_t HpackDecoderStringBuffer::BufferedLength() const {
+  DVLOG(3) << "HpackDecoderStringBuffer::BufferedLength";
+  return IsBuffered() ? buffer_.size() : 0;
+}
+
+Http2StringPiece HpackDecoderStringBuffer::str() const {
+  DVLOG(3) << "HpackDecoderStringBuffer::str";
+  DCHECK_EQ(state_, State::COMPLETE);
+  return value_;
+}
+
+Http2String HpackDecoderStringBuffer::ReleaseString() {
+  DVLOG(3) << "HpackDecoderStringBuffer::ReleaseString";
+  DCHECK_EQ(state_, State::COMPLETE);
+  DCHECK_EQ(backing_, Backing::BUFFERED);
+  if (state_ == State::COMPLETE) {
+    state_ = State::RESET;
+    if (backing_ == Backing::BUFFERED) {
+      return std::move(buffer_);
+    } else {
+      return Http2String(value_);
+    }
+  }
+  return "";
+}
+
+void HpackDecoderStringBuffer::OutputDebugStringTo(std::ostream& out) const {
+  out << "{state=" << state_;
+  if (state_ != State::RESET) {
+    out << ", backing=" << backing_;
+    out << ", remaining_len=" << remaining_len_;
+    out << ", is_huffman_encoded=" << is_huffman_encoded_;
+    if (backing_ == Backing::BUFFERED) {
+      out << ", buffer: " << buffer_;
+    } else {
+      out << ", value: " << value_;
+    }
+  }
+  out << "}";
+}
+
+size_t HpackDecoderStringBuffer::EstimateMemoryUsage() const {
+  return Http2EstimateMemoryUsage(buffer_);
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackDecoderStringBuffer& v) {
+  v.OutputDebugStringTo(out);
+  return out;
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_string_buffer.h b/http2/hpack/decoder/hpack_decoder_string_buffer.h
new file mode 100644
index 0000000..8a810b2
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_string_buffer.h
@@ -0,0 +1,102 @@
+// 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_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_
+
+// HpackDecoderStringBuffer helps an HPACK decoder to avoid copies of a string
+// literal (name or value) except when necessary (e.g. when split across two
+// or more HPACK block fragments).
+
+#include <stddef.h>
+
+#include <ostream>
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderStringBuffer {
+ public:
+  enum class State : uint8_t { RESET, COLLECTING, COMPLETE };
+  enum class Backing : uint8_t { RESET, UNBUFFERED, BUFFERED, STATIC };
+
+  HpackDecoderStringBuffer();
+  ~HpackDecoderStringBuffer();
+
+  HpackDecoderStringBuffer(const HpackDecoderStringBuffer&) = delete;
+  HpackDecoderStringBuffer& operator=(const HpackDecoderStringBuffer&) = delete;
+
+  void Reset();
+  void Set(Http2StringPiece value, bool is_static);
+
+  // Note that for Huffman encoded strings the length of the string after
+  // decoding may be larger (expected), the same or even smaller; the latter
+  // are unlikely, but possible if the encoder makes odd choices.
+  void OnStart(bool huffman_encoded, size_t len);
+  bool OnData(const char* data, size_t len);
+  bool OnEnd();
+  void BufferStringIfUnbuffered();
+  bool IsBuffered() const;
+  size_t BufferedLength() const;
+
+  // Accessors for the completely collected string (i.e. Set or OnEnd has just
+  // been called, and no reset of the state has occurred).
+
+  // Returns a Http2StringPiece pointing to the backing store for the string,
+  // either the internal buffer or the original transport buffer (e.g. for a
+  // literal value that wasn't Huffman encoded, and that wasn't split across
+  // transport buffers).
+  Http2StringPiece str() const;
+
+  // Returns the completely collected string by value, using std::move in an
+  // effort to avoid unnecessary copies. ReleaseString() must not be called
+  // unless the string has been buffered (to avoid forcing a potentially
+  // unnecessary copy). ReleaseString() also resets the instance so that it can
+  // be used to collect another string.
+  Http2String ReleaseString();
+
+  State state_for_testing() const { return state_; }
+  Backing backing_for_testing() const { return backing_; }
+  void OutputDebugStringTo(std::ostream& out) const;
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+ private:
+  // Storage for the string being buffered, if buffering is necessary
+  // (e.g. if Huffman encoded, buffer_ is storage for the decoded string).
+  Http2String buffer_;
+
+  // The Http2StringPiece to be returned by HpackDecoderStringBuffer::str(). If
+  // a string has been collected, but not buffered, value_ points to that
+  // string.
+  Http2StringPiece value_;
+
+  // The decoder to use if the string is Huffman encoded.
+  HpackHuffmanDecoder decoder_;
+
+  // Count of bytes not yet passed to OnData.
+  size_t remaining_len_;
+
+  // Is the HPACK string Huffman encoded?
+  bool is_huffman_encoded_;
+
+  // State of the string decoding process.
+  State state_;
+
+  // Where is the string stored?
+  Backing backing_;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(
+    std::ostream& out,
+    const HpackDecoderStringBuffer& v);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_
diff --git a/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc b/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc
new file mode 100644
index 0000000..009b894
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc
@@ -0,0 +1,251 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+
+// Tests of HpackDecoderStringBuffer.
+
+#include <initializer_list>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::HasSubstr;
+
+namespace http2 {
+namespace test {
+namespace {
+
+class HpackDecoderStringBufferTest : public ::testing::Test {
+ protected:
+  typedef HpackDecoderStringBuffer::State State;
+  typedef HpackDecoderStringBuffer::Backing Backing;
+
+  State state() const { return buf_.state_for_testing(); }
+  Backing backing() const { return buf_.backing_for_testing(); }
+
+  // We want to know that LOG(x) << buf_ will work in production should that
+  // be needed, so we test that it outputs the expected values.
+  AssertionResult VerifyLogHasSubstrs(std::initializer_list<Http2String> strs) {
+    VLOG(1) << buf_;
+    std::ostringstream ss;
+    buf_.OutputDebugStringTo(ss);
+    Http2String dbg_str(ss.str());
+    for (const auto& expected : strs) {
+      VERIFY_THAT(dbg_str, HasSubstr(expected));
+    }
+    return AssertionSuccess();
+  }
+
+  HpackDecoderStringBuffer buf_;
+};
+
+TEST_F(HpackDecoderStringBufferTest, SetStatic) {
+  Http2StringPiece data("static string");
+
+  EXPECT_EQ(state(), State::RESET);
+  EXPECT_TRUE(VerifyLogHasSubstrs({"state=RESET"}));
+
+  buf_.Set(data, /*is_static*/ true);
+  LOG(INFO) << buf_;
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::STATIC);
+  EXPECT_EQ(data, buf_.str());
+  EXPECT_EQ(data.data(), buf_.str().data());
+  EXPECT_TRUE(VerifyLogHasSubstrs(
+      {"state=COMPLETE", "backing=STATIC", "value: static string"}));
+
+  // The string is static, so BufferStringIfUnbuffered won't change anything.
+  buf_.BufferStringIfUnbuffered();
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::STATIC);
+  EXPECT_EQ(data, buf_.str());
+  EXPECT_EQ(data.data(), buf_.str().data());
+  EXPECT_TRUE(VerifyLogHasSubstrs(
+      {"state=COMPLETE", "backing=STATIC", "value: static string"}));
+}
+
+TEST_F(HpackDecoderStringBufferTest, PlainWhole) {
+  Http2StringPiece data("some text.");
+
+  LOG(INFO) << buf_;
+  EXPECT_EQ(state(), State::RESET);
+
+  buf_.OnStart(/*huffman_encoded*/ false, data.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::RESET);
+  LOG(INFO) << buf_;
+
+  EXPECT_TRUE(buf_.OnData(data.data(), data.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::UNBUFFERED);
+
+  EXPECT_TRUE(buf_.OnEnd());
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::UNBUFFERED);
+  EXPECT_EQ(0u, buf_.BufferedLength());
+  EXPECT_TRUE(VerifyLogHasSubstrs(
+      {"state=COMPLETE", "backing=UNBUFFERED", "value: some text."}));
+
+  // We expect that the string buffer points to the passed in Http2StringPiece's
+  // backing store.
+  EXPECT_EQ(data.data(), buf_.str().data());
+
+  // Now force it to buffer the string, after which it will still have the same
+  // string value, but the backing store will be different.
+  buf_.BufferStringIfUnbuffered();
+  LOG(INFO) << buf_;
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), data.size());
+  EXPECT_EQ(data, buf_.str());
+  EXPECT_NE(data.data(), buf_.str().data());
+  EXPECT_TRUE(VerifyLogHasSubstrs(
+      {"state=COMPLETE", "backing=BUFFERED", "buffer: some text."}));
+}
+
+TEST_F(HpackDecoderStringBufferTest, PlainSplit) {
+  Http2StringPiece data("some text.");
+  Http2StringPiece part1 = data.substr(0, 1);
+  Http2StringPiece part2 = data.substr(1);
+
+  EXPECT_EQ(state(), State::RESET);
+  buf_.OnStart(/*huffman_encoded*/ false, data.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::RESET);
+
+  // OnData with only a part of the data, not the whole, so buf_ will buffer
+  // the data.
+  EXPECT_TRUE(buf_.OnData(part1.data(), part1.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), part1.size());
+  LOG(INFO) << buf_;
+
+  EXPECT_TRUE(buf_.OnData(part2.data(), part2.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), data.size());
+
+  EXPECT_TRUE(buf_.OnEnd());
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), data.size());
+  LOG(INFO) << buf_;
+
+  Http2StringPiece buffered = buf_.str();
+  EXPECT_EQ(data, buffered);
+  EXPECT_NE(data.data(), buffered.data());
+
+  // The string is already buffered, so BufferStringIfUnbuffered should not make
+  // any change.
+  buf_.BufferStringIfUnbuffered();
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), data.size());
+  EXPECT_EQ(buffered, buf_.str());
+  EXPECT_EQ(buffered.data(), buf_.str().data());
+}
+
+TEST_F(HpackDecoderStringBufferTest, HuffmanWhole) {
+  Http2String encoded = Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff");
+  Http2StringPiece decoded("www.example.com");
+
+  EXPECT_EQ(state(), State::RESET);
+  buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+
+  EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+
+  EXPECT_TRUE(buf_.OnEnd());
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), decoded.size());
+  EXPECT_EQ(decoded, buf_.str());
+  EXPECT_TRUE(VerifyLogHasSubstrs(
+      {"{state=COMPLETE", "backing=BUFFERED", "buffer: www.example.com}"}));
+
+  Http2String s = buf_.ReleaseString();
+  EXPECT_EQ(s, decoded);
+  EXPECT_EQ(state(), State::RESET);
+}
+
+TEST_F(HpackDecoderStringBufferTest, HuffmanSplit) {
+  Http2String encoded = Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff");
+  Http2String part1 = encoded.substr(0, 5);
+  Http2String part2 = encoded.substr(5);
+  Http2StringPiece decoded("www.example.com");
+
+  EXPECT_EQ(state(), State::RESET);
+  buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(0u, buf_.BufferedLength());
+  LOG(INFO) << buf_;
+
+  EXPECT_TRUE(buf_.OnData(part1.data(), part1.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_GT(buf_.BufferedLength(), 0u);
+  EXPECT_LT(buf_.BufferedLength(), decoded.size());
+  LOG(INFO) << buf_;
+
+  EXPECT_TRUE(buf_.OnData(part2.data(), part2.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), decoded.size());
+  LOG(INFO) << buf_;
+
+  EXPECT_TRUE(buf_.OnEnd());
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), decoded.size());
+  EXPECT_EQ(decoded, buf_.str());
+  LOG(INFO) << buf_;
+
+  buf_.Reset();
+  EXPECT_EQ(state(), State::RESET);
+  LOG(INFO) << buf_;
+}
+
+TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnData) {
+  // Explicitly encode the End-of-String symbol, a no-no.
+  Http2String encoded = Http2HexDecode("ffffffff");
+
+  buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+
+  EXPECT_FALSE(buf_.OnData(encoded.data(), encoded.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+
+  LOG(INFO) << buf_;
+}
+
+TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnEnd) {
+  // Last byte of string doesn't end with prefix of End-of-String symbol.
+  Http2String encoded = Http2HexDecode("00");
+
+  buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+
+  EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+
+  EXPECT_FALSE(buf_.OnEnd());
+  LOG(INFO) << buf_;
+}
+
+// TODO(jamessynge): Add tests for ReleaseString().
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_tables.cc b/http2/hpack/decoder/hpack_decoder_tables.cc
new file mode 100644
index 0000000..5002ab1
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_tables.cc
@@ -0,0 +1,153 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+
+namespace http2 {
+namespace {
+
+std::vector<HpackStringPair>* MakeStaticTable() {
+  auto* ptr = new std::vector<HpackStringPair>();
+  ptr->reserve(kFirstDynamicTableIndex);
+  ptr->emplace_back("", "");
+
+#define STATIC_TABLE_ENTRY(name, value, index) \
+  DCHECK_EQ(ptr->size(), index);               \
+  ptr->emplace_back(name, value)
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc"
+
+#undef STATIC_TABLE_ENTRY
+
+  return ptr;
+}
+
+const std::vector<HpackStringPair>* GetStaticTable() {
+  static const std::vector<HpackStringPair>* const g_static_table =
+      MakeStaticTable();
+  return g_static_table;
+}
+
+}  // namespace
+
+HpackDecoderTablesDebugListener::HpackDecoderTablesDebugListener() = default;
+HpackDecoderTablesDebugListener::~HpackDecoderTablesDebugListener() = default;
+
+HpackDecoderStaticTable::HpackDecoderStaticTable(
+    const std::vector<HpackStringPair>* table)
+    : table_(table) {}
+
+HpackDecoderStaticTable::HpackDecoderStaticTable() : table_(GetStaticTable()) {}
+
+const HpackStringPair* HpackDecoderStaticTable::Lookup(size_t index) const {
+  if (0 < index && index < kFirstDynamicTableIndex) {
+    return &((*table_)[index]);
+  }
+  return nullptr;
+}
+
+HpackDecoderDynamicTable::HpackDecoderTableEntry::HpackDecoderTableEntry(
+    const HpackString& name,
+    const HpackString& value)
+    : HpackStringPair(name, value) {}
+
+HpackDecoderDynamicTable::HpackDecoderDynamicTable()
+    : insert_count_(kFirstDynamicTableIndex - 1), debug_listener_(nullptr) {}
+HpackDecoderDynamicTable::~HpackDecoderDynamicTable() = default;
+
+void HpackDecoderDynamicTable::DynamicTableSizeUpdate(size_t size_limit) {
+  DVLOG(3) << "HpackDecoderDynamicTable::DynamicTableSizeUpdate " << size_limit;
+  EnsureSizeNoMoreThan(size_limit);
+  DCHECK_LE(current_size_, size_limit);
+  size_limit_ = size_limit;
+}
+
+// TODO(jamessynge): Check somewhere before here that names received from the
+// peer are valid (e.g. are lower-case, no whitespace, etc.).
+bool HpackDecoderDynamicTable::Insert(const HpackString& name,
+                                      const HpackString& value) {
+  HpackDecoderTableEntry entry(name, value);
+  size_t entry_size = entry.size();
+  DVLOG(2) << "InsertEntry of size=" << entry_size << "\n     name: " << name
+           << "\n    value: " << value;
+  if (entry_size > size_limit_) {
+    DVLOG(2) << "InsertEntry: entry larger than table, removing "
+             << table_.size() << " entries, of total size " << current_size_
+             << " bytes.";
+    table_.clear();
+    current_size_ = 0;
+    return false;  // Not inserted because too large.
+  }
+  ++insert_count_;
+  if (debug_listener_ != nullptr) {
+    entry.time_added = debug_listener_->OnEntryInserted(entry, insert_count_);
+    DVLOG(2) << "OnEntryInserted returned time_added=" << entry.time_added
+             << " for insert_count_=" << insert_count_;
+  }
+  size_t insert_limit = size_limit_ - entry_size;
+  EnsureSizeNoMoreThan(insert_limit);
+  table_.push_front(entry);
+  current_size_ += entry_size;
+  DVLOG(2) << "InsertEntry: current_size_=" << current_size_;
+  DCHECK_GE(current_size_, entry_size);
+  DCHECK_LE(current_size_, size_limit_);
+  return true;
+}
+
+const HpackStringPair* HpackDecoderDynamicTable::Lookup(size_t index) const {
+  if (index < table_.size()) {
+    const HpackDecoderTableEntry& entry = table_[index];
+    if (debug_listener_ != nullptr) {
+      size_t insert_count_of_index = insert_count_ + table_.size() - index;
+      debug_listener_->OnUseEntry(entry, insert_count_of_index,
+                                  entry.time_added);
+    }
+    return &entry;
+  }
+  return nullptr;
+}
+
+void HpackDecoderDynamicTable::EnsureSizeNoMoreThan(size_t limit) {
+  DVLOG(2) << "EnsureSizeNoMoreThan limit=" << limit
+           << ", current_size_=" << current_size_;
+  // Not the most efficient choice, but any easy way to start.
+  while (current_size_ > limit) {
+    RemoveLastEntry();
+  }
+  DCHECK_LE(current_size_, limit);
+}
+
+void HpackDecoderDynamicTable::RemoveLastEntry() {
+  DCHECK(!table_.empty());
+  if (!table_.empty()) {
+    DVLOG(2) << "RemoveLastEntry current_size_=" << current_size_
+             << ", last entry size=" << table_.back().size();
+    DCHECK_GE(current_size_, table_.back().size());
+    current_size_ -= table_.back().size();
+    table_.pop_back();
+    // Empty IFF current_size_ == 0.
+    DCHECK_EQ(table_.empty(), current_size_ == 0);
+  }
+}
+
+HpackDecoderTables::HpackDecoderTables() = default;
+HpackDecoderTables::~HpackDecoderTables() = default;
+
+void HpackDecoderTables::set_debug_listener(
+    HpackDecoderTablesDebugListener* debug_listener) {
+  dynamic_table_.set_debug_listener(debug_listener);
+}
+
+const HpackStringPair* HpackDecoderTables::Lookup(size_t index) const {
+  if (index < kFirstDynamicTableIndex) {
+    return static_table_.Lookup(index);
+  } else {
+    return dynamic_table_.Lookup(index - kFirstDynamicTableIndex);
+  }
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_tables.h b/http2/hpack/decoder/hpack_decoder_tables.h
new file mode 100644
index 0000000..6df145c
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_tables.h
@@ -0,0 +1,197 @@
+// 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_HPACK_DECODER_HPACK_DECODER_TABLES_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
+
+// Static and dynamic tables for the HPACK decoder. See:
+// http://httpwg.org/specs/rfc7541.html#indexing.tables
+
+// Note that the Lookup methods return nullptr if the requested index was not
+// found. This should be treated as a COMPRESSION error according to the HTTP/2
+// spec, which is a connection level protocol error (i.e. the connection must
+// be terminated). See these sections in the two RFCs:
+// http://httpwg.org/specs/rfc7541.html#indexed.header.representation
+// http://httpwg.org/specs/rfc7541.html#index.address.space
+// http://httpwg.org/specs/rfc7540.html#HeaderBlock
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <vector>
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_containers.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class HpackDecoderTablesPeer;
+}  // namespace test
+
+// HpackDecoderTablesDebugListener supports a QUIC experiment, enabling
+// the gathering of information about the time-line of use of HPACK
+// dynamic table entries.
+class HTTP2_EXPORT_PRIVATE HpackDecoderTablesDebugListener {
+ public:
+  HpackDecoderTablesDebugListener();
+  virtual ~HpackDecoderTablesDebugListener();
+
+  HpackDecoderTablesDebugListener(const HpackDecoderTablesDebugListener&) =
+      delete;
+  HpackDecoderTablesDebugListener& operator=(
+      const HpackDecoderTablesDebugListener&) = delete;
+
+  // The entry has been inserted into the dynamic table. insert_count starts at
+  // 62 because 61 is the last index in the static table; insert_count increases
+  // by 1 with each insert into the dynamic table; it is not incremented when
+  // when a entry is too large to fit into the dynamic table at all (which has
+  // the effect of emptying the dynamic table).
+  // Returns a value that can be used as time_added in OnUseEntry.
+  virtual int64_t OnEntryInserted(const HpackStringPair& entry,
+                                  size_t insert_count) = 0;
+
+  // The entry has been used, either for the name or for the name and value.
+  // insert_count is the same as passed to OnEntryInserted when entry was
+  // inserted to the dynamic table, and time_added is the value that was
+  // returned by OnEntryInserted.
+  virtual void OnUseEntry(const HpackStringPair& entry,
+                          size_t insert_count,
+                          int64_t time_added) = 0;
+};
+
+// See http://httpwg.org/specs/rfc7541.html#static.table.definition for the
+// contents, and http://httpwg.org/specs/rfc7541.html#index.address.space for
+// info about accessing the static table.
+class HTTP2_EXPORT_PRIVATE HpackDecoderStaticTable {
+ public:
+  explicit HpackDecoderStaticTable(const std::vector<HpackStringPair>* table);
+  // Uses a global table shared by all threads.
+  HpackDecoderStaticTable();
+
+  // If index is valid, returns a pointer to the entry, otherwise returns
+  // nullptr.
+  const HpackStringPair* Lookup(size_t index) const;
+
+ private:
+  friend class test::HpackDecoderTablesPeer;
+  const std::vector<HpackStringPair>* const table_;
+};
+
+// HpackDecoderDynamicTable implements HPACK compression feature "indexed
+// headers"; previously sent headers may be referenced later by their index
+// in the dynamic table. See these sections of the RFC:
+//   http://httpwg.org/specs/rfc7541.html#dynamic.table
+//   http://httpwg.org/specs/rfc7541.html#dynamic.table.management
+class HTTP2_EXPORT_PRIVATE HpackDecoderDynamicTable {
+ public:
+  HpackDecoderDynamicTable();
+  ~HpackDecoderDynamicTable();
+
+  HpackDecoderDynamicTable(const HpackDecoderDynamicTable&) = delete;
+  HpackDecoderDynamicTable& operator=(const HpackDecoderDynamicTable&) = delete;
+
+  // Set the listener to be notified of insertions into this table, and later
+  // uses of those entries. Added for evaluation of changes to QUIC's use
+  // of HPACK.
+  void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener) {
+    debug_listener_ = debug_listener;
+  }
+
+  // Sets a new size limit, received from the peer; performs evictions if
+  // necessary to ensure that the current size does not exceed the new limit.
+  // The caller needs to have validated that size_limit does not
+  // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
+  void DynamicTableSizeUpdate(size_t size_limit);
+
+  // Returns true if inserted, false if too large (at which point the
+  // dynamic table will be empty.)
+  bool Insert(const HpackString& name, const HpackString& value);
+
+  // If index is valid, returns a pointer to the entry, otherwise returns
+  // nullptr.
+  const HpackStringPair* Lookup(size_t index) const;
+
+  size_t size_limit() const { return size_limit_; }
+  size_t current_size() const { return current_size_; }
+
+ private:
+  friend class test::HpackDecoderTablesPeer;
+  struct HpackDecoderTableEntry : public HpackStringPair {
+    HpackDecoderTableEntry(const HpackString& name, const HpackString& value);
+    int64_t time_added;
+  };
+
+  // Drop older entries to ensure the size is not greater than limit.
+  void EnsureSizeNoMoreThan(size_t limit);
+
+  // Removes the oldest dynamic table entry.
+  void RemoveLastEntry();
+
+  Http2Deque<HpackDecoderTableEntry> table_;
+
+  // The last received DynamicTableSizeUpdate value, initialized to
+  // SETTINGS_HEADER_TABLE_SIZE.
+  size_t size_limit_ = Http2SettingsInfo::DefaultHeaderTableSize();
+
+  size_t current_size_ = 0;
+
+  // insert_count_ and debug_listener_ are used by a QUIC experiment; remove
+  // when the experiment is done.
+  size_t insert_count_;
+  HpackDecoderTablesDebugListener* debug_listener_;
+};
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderTables {
+ public:
+  HpackDecoderTables();
+  ~HpackDecoderTables();
+
+  HpackDecoderTables(const HpackDecoderTables&) = delete;
+  HpackDecoderTables& operator=(const HpackDecoderTables&) = delete;
+
+  // Set the listener to be notified of insertions into the dynamic table, and
+  // later uses of those entries. Added for evaluation of changes to QUIC's use
+  // of HPACK.
+  void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener);
+
+  // Sets a new size limit, received from the peer; performs evictions if
+  // necessary to ensure that the current size does not exceed the new limit.
+  // The caller needs to have validated that size_limit does not
+  // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
+  void DynamicTableSizeUpdate(size_t size_limit) {
+    dynamic_table_.DynamicTableSizeUpdate(size_limit);
+  }
+
+  // Returns true if inserted, false if too large (at which point the
+  // dynamic table will be empty.)
+  // TODO(jamessynge): Add methods for moving the string(s) into the table,
+  // or for otherwise avoiding unnecessary copies.
+  bool Insert(const HpackString& name, const HpackString& value) {
+    return dynamic_table_.Insert(name, value);
+  }
+
+  // If index is valid, returns a pointer to the entry, otherwise returns
+  // nullptr.
+  const HpackStringPair* Lookup(size_t index) const;
+
+  // The size limit that the peer (the HPACK encoder) has told the decoder it is
+  // currently operating with. Defaults to SETTINGS_HEADER_TABLE_SIZE, 4096.
+  size_t header_table_size_limit() const { return dynamic_table_.size_limit(); }
+
+  // Sum of the sizes of the dynamic table entries.
+  size_t current_header_table_size() const {
+    return dynamic_table_.current_size();
+  }
+
+ private:
+  friend class test::HpackDecoderTablesPeer;
+  HpackDecoderStaticTable static_table_;
+  HpackDecoderDynamicTable dynamic_table_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
diff --git a/http2/hpack/decoder/hpack_decoder_tables_test.cc b/http2/hpack/decoder/hpack_decoder_tables_test.cc
new file mode 100644
index 0000000..370c1a5
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_tables_test.cc
@@ -0,0 +1,266 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+
+#include <algorithm>
+#include <tuple>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_util.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+class HpackDecoderTablesPeer {
+ public:
+  static size_t num_dynamic_entries(const HpackDecoderTables& tables) {
+    return tables.dynamic_table_.table_.size();
+  }
+};
+
+namespace {
+struct StaticEntry {
+  const char* name;
+  const char* value;
+  size_t index;
+};
+
+std::vector<StaticEntry> MakeSpecStaticEntries() {
+  std::vector<StaticEntry> static_entries;
+
+#define STATIC_TABLE_ENTRY(name, value, index) \
+  DCHECK_EQ(static_entries.size() + 1, index); \
+  static_entries.push_back({name, value, index});
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc"
+
+#undef STATIC_TABLE_ENTRY
+
+  return static_entries;
+}
+
+template <class C>
+void ShuffleCollection(C* collection, Http2Random* r) {
+  std::shuffle(collection->begin(), collection->end(), *r);
+}
+
+class HpackDecoderStaticTableTest : public ::testing::Test {
+ protected:
+  HpackDecoderStaticTableTest() = default;
+
+  std::vector<StaticEntry> shuffled_static_entries() {
+    std::vector<StaticEntry> entries = MakeSpecStaticEntries();
+    ShuffleCollection(&entries, &random_);
+    return entries;
+  }
+
+  // This test is in a function so that it can be applied to both the static
+  // table and the combined static+dynamic tables.
+  AssertionResult VerifyStaticTableContents() {
+    for (const auto& expected : shuffled_static_entries()) {
+      const HpackStringPair* found = Lookup(expected.index);
+      VERIFY_NE(found, nullptr);
+      VERIFY_EQ(expected.name, found->name) << expected.index;
+      VERIFY_EQ(expected.value, found->value) << expected.index;
+    }
+
+    // There should be no entry with index 0.
+    VERIFY_EQ(nullptr, Lookup(0));
+    return AssertionSuccess();
+  }
+
+  virtual const HpackStringPair* Lookup(size_t index) {
+    return static_table_.Lookup(index);
+  }
+
+  Http2Random* RandomPtr() { return &random_; }
+
+  Http2Random random_;
+
+ private:
+  HpackDecoderStaticTable static_table_;
+};
+
+TEST_F(HpackDecoderStaticTableTest, StaticTableContents) {
+  EXPECT_TRUE(VerifyStaticTableContents());
+}
+
+size_t Size(const Http2String& name, const Http2String& value) {
+  return name.size() + value.size() + 32;
+}
+
+// To support tests with more than a few of hand crafted changes to the dynamic
+// table, we have another, exceedingly simple, implementation of the HPACK
+// dynamic table containing FakeHpackEntry instances. We can thus compare the
+// contents of the actual table with those in fake_dynamic_table_.
+
+typedef std::tuple<Http2String, Http2String, size_t> FakeHpackEntry;
+const Http2String& Name(const FakeHpackEntry& entry) {
+  return std::get<0>(entry);
+}
+const Http2String& Value(const FakeHpackEntry& entry) {
+  return std::get<1>(entry);
+}
+size_t Size(const FakeHpackEntry& entry) {
+  return std::get<2>(entry);
+}
+
+class HpackDecoderTablesTest : public HpackDecoderStaticTableTest {
+ protected:
+  const HpackStringPair* Lookup(size_t index) override {
+    return tables_.Lookup(index);
+  }
+
+  size_t dynamic_size_limit() const {
+    return tables_.header_table_size_limit();
+  }
+  size_t current_dynamic_size() const {
+    return tables_.current_header_table_size();
+  }
+  size_t num_dynamic_entries() const {
+    return HpackDecoderTablesPeer::num_dynamic_entries(tables_);
+  }
+
+  // Insert the name and value into fake_dynamic_table_.
+  void FakeInsert(const Http2String& name, const Http2String& value) {
+    FakeHpackEntry entry(name, value, Size(name, value));
+    fake_dynamic_table_.insert(fake_dynamic_table_.begin(), entry);
+  }
+
+  // Add up the size of all entries in fake_dynamic_table_.
+  size_t FakeSize() {
+    size_t sz = 0;
+    for (const auto& entry : fake_dynamic_table_) {
+      sz += Size(entry);
+    }
+    return sz;
+  }
+
+  // If the total size of the fake_dynamic_table_ is greater than limit,
+  // keep the first N entries such that those N entries have a size not
+  // greater than limit, and such that keeping entry N+1 would have a size
+  // greater than limit. Returns the count of removed bytes.
+  size_t FakeTrim(size_t limit) {
+    size_t original_size = FakeSize();
+    size_t total_size = 0;
+    for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) {
+      total_size += Size(fake_dynamic_table_[ndx]);
+      if (total_size > limit) {
+        // Need to get rid of ndx and all following entries.
+        fake_dynamic_table_.erase(fake_dynamic_table_.begin() + ndx,
+                                  fake_dynamic_table_.end());
+        return original_size - FakeSize();
+      }
+    }
+    return 0;
+  }
+
+  // Verify that the contents of the actual dynamic table match those in
+  // fake_dynamic_table_.
+  AssertionResult VerifyDynamicTableContents() {
+    VERIFY_EQ(current_dynamic_size(), FakeSize());
+    VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size());
+
+    for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) {
+      const HpackStringPair* found = Lookup(ndx + kFirstDynamicTableIndex);
+      VERIFY_NE(found, nullptr);
+
+      const auto& expected = fake_dynamic_table_[ndx];
+      VERIFY_EQ(Name(expected), found->name);
+      VERIFY_EQ(Value(expected), found->value);
+    }
+
+    // Make sure there are no more entries.
+    VERIFY_EQ(nullptr,
+              Lookup(fake_dynamic_table_.size() + kFirstDynamicTableIndex));
+    return AssertionSuccess();
+  }
+
+  // Apply an update to the limit on the maximum size of the dynamic table.
+  AssertionResult DynamicTableSizeUpdate(size_t size_limit) {
+    VERIFY_EQ(current_dynamic_size(), FakeSize());
+    if (size_limit < current_dynamic_size()) {
+      // Will need to trim the dynamic table's oldest entries.
+      tables_.DynamicTableSizeUpdate(size_limit);
+      FakeTrim(size_limit);
+      return VerifyDynamicTableContents();
+    }
+    // Shouldn't change the size.
+    tables_.DynamicTableSizeUpdate(size_limit);
+    return VerifyDynamicTableContents();
+  }
+
+  // Insert an entry into the dynamic table, confirming that trimming of entries
+  // occurs if the total size is greater than the limit, and that older entries
+  // move up by 1 index.
+  AssertionResult Insert(const Http2String& name, const Http2String& value) {
+    size_t old_count = num_dynamic_entries();
+    if (tables_.Insert(HpackString(name), HpackString(value))) {
+      VERIFY_GT(current_dynamic_size(), 0u);
+      VERIFY_GT(num_dynamic_entries(), 0u);
+    } else {
+      VERIFY_EQ(current_dynamic_size(), 0u);
+      VERIFY_EQ(num_dynamic_entries(), 0u);
+    }
+    FakeInsert(name, value);
+    VERIFY_EQ(old_count + 1, fake_dynamic_table_.size());
+    FakeTrim(dynamic_size_limit());
+    VERIFY_EQ(current_dynamic_size(), FakeSize());
+    VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size());
+    return VerifyDynamicTableContents();
+  }
+
+ private:
+  HpackDecoderTables tables_;
+
+  std::vector<FakeHpackEntry> fake_dynamic_table_;
+};
+
+TEST_F(HpackDecoderTablesTest, StaticTableContents) {
+  EXPECT_TRUE(VerifyStaticTableContents());
+}
+
+// Generate a bunch of random header entries, insert them, and confirm they
+// present, as required by the RFC, using VerifyDynamicTableContents above on
+// each Insert. Also apply various resizings of the dynamic table.
+TEST_F(HpackDecoderTablesTest, RandomDynamicTable) {
+  EXPECT_EQ(0u, current_dynamic_size());
+  EXPECT_TRUE(VerifyStaticTableContents());
+  EXPECT_TRUE(VerifyDynamicTableContents());
+
+  std::vector<size_t> table_sizes;
+  table_sizes.push_back(dynamic_size_limit());
+  table_sizes.push_back(0);
+  table_sizes.push_back(dynamic_size_limit() / 2);
+  table_sizes.push_back(dynamic_size_limit());
+  table_sizes.push_back(dynamic_size_limit() / 2);
+  table_sizes.push_back(0);
+  table_sizes.push_back(dynamic_size_limit());
+
+  for (size_t limit : table_sizes) {
+    ASSERT_TRUE(DynamicTableSizeUpdate(limit));
+    for (int insert_count = 0; insert_count < 100; ++insert_count) {
+      Http2String name =
+          GenerateHttp2HeaderName(random_.UniformInRange(2, 40), RandomPtr());
+      Http2String value =
+          GenerateWebSafeString(random_.UniformInRange(2, 600), RandomPtr());
+      ASSERT_TRUE(Insert(name, value));
+    }
+    EXPECT_TRUE(VerifyStaticTableContents());
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_test.cc b/http2/hpack/decoder/hpack_decoder_test.cc
new file mode 100644
index 0000000..563e977
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_test.cc
@@ -0,0 +1,1219 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h"
+
+// Tests of HpackDecoder.
+
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_util.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::ElementsAreArray;
+using ::testing::HasSubstr;
+
+namespace http2 {
+namespace test {
+class HpackDecoderStatePeer {
+ public:
+  static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) {
+    return &state->decoder_tables_;
+  }
+  static void set_listener(HpackDecoderState* state,
+                           HpackDecoderListener* listener) {
+    state->listener_ = listener;
+  }
+};
+class HpackDecoderPeer {
+ public:
+  static HpackDecoderState* GetDecoderState(HpackDecoder* decoder) {
+    return &decoder->decoder_state_;
+  }
+  static HpackDecoderTables* GetDecoderTables(HpackDecoder* decoder) {
+    return HpackDecoderStatePeer::GetDecoderTables(GetDecoderState(decoder));
+  }
+};
+
+namespace {
+
+typedef std::tuple<HpackEntryType, Http2String, Http2String> HpackHeaderEntry;
+typedef std::vector<HpackHeaderEntry> HpackHeaderEntries;
+
+// TODO(jamessynge): Create a ...test_utils.h file with the mock listener
+// and with VerifyDynamicTableContents.
+class MockHpackDecoderListener : public HpackDecoderListener {
+ public:
+  MOCK_METHOD0(OnHeaderListStart, void());
+  MOCK_METHOD3(OnHeader,
+               void(HpackEntryType entry_type,
+                    const HpackString& name,
+                    const HpackString& value));
+  MOCK_METHOD0(OnHeaderListEnd, void());
+  MOCK_METHOD1(OnHeaderErrorDetected, void(Http2StringPiece error_message));
+};
+
+class HpackDecoderTest : public ::testing::TestWithParam<bool>,
+                         public HpackDecoderListener {
+ protected:
+  // Note that we initialize the random number generator with the same seed
+  // for each individual test, therefore the order in which the tests are
+  // executed does not effect the sequence produced by the RNG within any
+  // one test.
+  HpackDecoderTest() : decoder_(this, 4096) {
+    fragment_the_hpack_block_ = GetParam();
+  }
+  ~HpackDecoderTest() override = default;
+
+  void OnHeaderListStart() override {
+    ASSERT_FALSE(saw_start_);
+    ASSERT_FALSE(saw_end_);
+    saw_start_ = true;
+    header_entries_.clear();
+  }
+
+  // Called for each header name-value pair that is decoded, in the order they
+  // appear in the HPACK block. Multiple values for a given key will be emitted
+  // as multiple calls to OnHeader.
+  void OnHeader(HpackEntryType entry_type,
+                const HpackString& name,
+                const HpackString& value) override {
+    ASSERT_TRUE(saw_start_);
+    ASSERT_FALSE(saw_end_);
+    //     header_entries_.push_back({entry_type, name.ToString(),
+    //     value.ToString()});
+    header_entries_.emplace_back(entry_type, name.ToString(), value.ToString());
+  }
+
+  // OnHeaderBlockEnd is called after successfully decoding an HPACK block. Will
+  // only be called once per block, even if it extends into CONTINUATION frames.
+  // A callback method which notifies when the parser finishes handling a
+  // header block (i.e. the containing frame has the END_STREAM flag set).
+  // Also indicates the total number of bytes in this block.
+  void OnHeaderListEnd() override {
+    ASSERT_TRUE(saw_start_);
+    ASSERT_FALSE(saw_end_);
+    ASSERT_TRUE(error_messages_.empty());
+    saw_end_ = true;
+  }
+
+  // OnHeaderErrorDetected is called if an error is detected while decoding.
+  // error_message may be used in a GOAWAY frame as the Opaque Data.
+  void OnHeaderErrorDetected(Http2StringPiece error_message) override {
+    ASSERT_TRUE(saw_start_);
+    error_messages_.push_back(Http2String(error_message));
+    // No further callbacks should be made at this point, so replace 'this' as
+    // the listener with mock_listener_, which is a strict mock, so will
+    // generate an error for any calls.
+    HpackDecoderStatePeer::set_listener(
+        HpackDecoderPeer::GetDecoderState(&decoder_), &mock_listener_);
+  }
+
+  AssertionResult DecodeBlock(Http2StringPiece block) {
+    VLOG(1) << "HpackDecoderTest::DecodeBlock";
+
+    VERIFY_FALSE(decoder_.error_detected());
+    VERIFY_TRUE(error_messages_.empty());
+    VERIFY_FALSE(saw_start_);
+    VERIFY_FALSE(saw_end_);
+    header_entries_.clear();
+
+    VERIFY_FALSE(decoder_.error_detected());
+    VERIFY_TRUE(decoder_.StartDecodingBlock());
+    VERIFY_FALSE(decoder_.error_detected());
+
+    if (fragment_the_hpack_block_) {
+      // See note in ctor regarding RNG.
+      while (!block.empty()) {
+        size_t fragment_size = random_.RandomSizeSkewedLow(block.size());
+        DecodeBuffer db(block.substr(0, fragment_size));
+        VERIFY_TRUE(decoder_.DecodeFragment(&db));
+        VERIFY_EQ(0u, db.Remaining());
+        block.remove_prefix(fragment_size);
+      }
+    } else {
+      DecodeBuffer db(block);
+      VERIFY_TRUE(decoder_.DecodeFragment(&db));
+      VERIFY_EQ(0u, db.Remaining());
+    }
+    VERIFY_FALSE(decoder_.error_detected());
+
+    VERIFY_TRUE(decoder_.EndDecodingBlock());
+    if (saw_end_) {
+      VERIFY_FALSE(decoder_.error_detected());
+      VERIFY_TRUE(error_messages_.empty());
+    } else {
+      VERIFY_TRUE(decoder_.error_detected());
+      VERIFY_FALSE(error_messages_.empty());
+    }
+
+    saw_start_ = saw_end_ = false;
+    return AssertionSuccess();
+  }
+
+  const HpackDecoderTables& GetDecoderTables() {
+    return *HpackDecoderPeer::GetDecoderTables(&decoder_);
+  }
+  const HpackStringPair* Lookup(size_t index) {
+    return GetDecoderTables().Lookup(index);
+  }
+  size_t current_header_table_size() {
+    return GetDecoderTables().current_header_table_size();
+  }
+  size_t header_table_size_limit() {
+    return GetDecoderTables().header_table_size_limit();
+  }
+  void set_header_table_size_limit(size_t size) {
+    HpackDecoderPeer::GetDecoderTables(&decoder_)->DynamicTableSizeUpdate(size);
+  }
+
+  // dynamic_index is one-based, because that is the way RFC 7541 shows it.
+  AssertionResult VerifyEntry(size_t dynamic_index,
+                              const char* name,
+                              const char* value) {
+    const HpackStringPair* entry =
+        Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+    VERIFY_NE(entry, nullptr);
+    VERIFY_EQ(entry->name.ToStringPiece(), name);
+    VERIFY_EQ(entry->value.ToStringPiece(), value);
+    return AssertionSuccess();
+  }
+  AssertionResult VerifyNoEntry(size_t dynamic_index) {
+    const HpackStringPair* entry =
+        Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+    VERIFY_EQ(entry, nullptr);
+    return AssertionSuccess();
+  }
+  AssertionResult VerifyDynamicTableContents(
+      const std::vector<std::pair<const char*, const char*>>& entries) {
+    size_t index = 1;
+    for (const auto& entry : entries) {
+      VERIFY_SUCCESS(VerifyEntry(index, entry.first, entry.second));
+      ++index;
+    }
+    VERIFY_SUCCESS(VerifyNoEntry(index));
+    return AssertionSuccess();
+  }
+
+  Http2Random random_;
+  HpackDecoder decoder_;
+  testing::StrictMock<MockHpackDecoderListener> mock_listener_;
+  HpackHeaderEntries header_entries_;
+  std::vector<Http2String> error_messages_;
+  bool fragment_the_hpack_block_;
+  bool saw_start_ = false;
+  bool saw_end_ = false;
+};
+INSTANTIATE_TEST_CASE_P(AllWays, HpackDecoderTest, ::testing::Bool());
+
+// Test based on RFC 7541, section C.3: Request Examples without Huffman Coding.
+// This section shows several consecutive header lists, corresponding to HTTP
+// requests, on the same connection.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3
+TEST_P(HpackDecoderTest, C3_RequestExamples) {
+  // C.3.1 First Request
+  Http2String hpack_block = HpackExampleToStringOrDie(R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      86                                      | == Indexed - Add ==
+                                              |   idx = 6
+                                              | -> :scheme: http
+      84                                      | == Indexed - Add ==
+                                              |   idx = 4
+                                              | -> :path: /
+      41                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 1)
+                                              |     :authority
+      0f                                      |   Literal value (len = 15)
+      7777 772e 6578 616d 706c 652e 636f 6d   | www.example.com
+                                              | -> :authority:
+                                              |   www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, ":authority",
+                           "www.example.com"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  57) :authority: www.example.com
+  //         Table size:  57
+  ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}}));
+  ASSERT_EQ(57u, current_header_table_size());
+
+  // C.3.2 Second Request
+  hpack_block = HpackExampleToStringOrDie(R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      86                                      | == Indexed - Add ==
+                                              |   idx = 6
+                                              | -> :scheme: http
+      84                                      | == Indexed - Add ==
+                                              |   idx = 4
+                                              | -> :path: /
+      be                                      | == Indexed - Add ==
+                                              |   idx = 62
+                                              | -> :authority:
+                                              |   www.example.com
+      58                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 24)
+                                              |     cache-control
+      08                                      |   Literal value (len = 8)
+      6e6f 2d63 6163 6865                     | no-cache
+                                              | -> cache-control: no-cache
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+                           "www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                           "cache-control", "no-cache"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  53) cache-control: no-cache
+  //   [  2] (s =  57) :authority: www.example.com
+  //         Table size: 110
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"cache-control", "no-cache"}, {":authority", "www.example.com"}}));
+  ASSERT_EQ(110u, current_header_table_size());
+
+  // C.3.2 Third Request
+  hpack_block = HpackExampleToStringOrDie(R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      87                                      | == Indexed - Add ==
+                                              |   idx = 7
+                                              | -> :scheme: https
+      85                                      | == Indexed - Add ==
+                                              |   idx = 5
+                                              | -> :path: /index.html
+      bf                                      | == Indexed - Add ==
+                                              |   idx = 63
+                                              | -> :authority:
+                                              |   www.example.com
+      40                                      | == Literal indexed ==
+      0a                                      |   Literal name (len = 10)
+      6375 7374 6f6d 2d6b 6579                | custom-key
+      0c                                      |   Literal value (len = 12)
+      6375 7374 6f6d 2d76 616c 7565           | custom-value
+                                              | -> custom-key:
+                                              |   custom-value
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "https"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path",
+                           "/index.html"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+                           "www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "custom-key",
+                           "custom-value"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  54) custom-key: custom-value
+  //   [  2] (s =  53) cache-control: no-cache
+  //   [  3] (s =  57) :authority: www.example.com
+  //         Table size: 164
+  ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"},
+                                          {"cache-control", "no-cache"},
+                                          {":authority", "www.example.com"}}));
+  ASSERT_EQ(164u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.4 Request Examples with Huffman Coding.
+// This section shows the same examples as the previous section but uses
+// Huffman encoding for the literal values.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.4
+TEST_P(HpackDecoderTest, C4_RequestExamplesWithHuffmanEncoding) {
+  // C.4.1 First Request
+  Http2String hpack_block = HpackExampleToStringOrDie(R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      86                                      | == Indexed - Add ==
+                                              |   idx = 6
+                                              | -> :scheme: http
+      84                                      | == Indexed - Add ==
+                                              |   idx = 4
+                                              | -> :path: /
+      41                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 1)
+                                              |     :authority
+      8c                                      |   Literal value (len = 12)
+                                              |     Huffman encoded:
+      f1e3 c2e5 f23a 6ba0 ab90 f4ff           | .....:k.....
+                                              |     Decoded:
+                                              | www.example.com
+                                              | -> :authority:
+                                              |   www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, ":authority",
+                           "www.example.com"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  57) :authority: www.example.com
+  //         Table size:  57
+  ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}}));
+  ASSERT_EQ(57u, current_header_table_size());
+
+  // C.4.2 Second Request
+  hpack_block = HpackExampleToStringOrDie(R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      86                                      | == Indexed - Add ==
+                                              |   idx = 6
+                                              | -> :scheme: http
+      84                                      | == Indexed - Add ==
+                                              |   idx = 4
+                                              | -> :path: /
+      be                                      | == Indexed - Add ==
+                                              |   idx = 62
+                                              | -> :authority:
+                                              |   www.example.com
+      58                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 24)
+                                              |     cache-control
+      86                                      |   Literal value (len = 6)
+                                              |     Huffman encoded:
+      a8eb 1064 9cbf                          | ...d..
+                                              |     Decoded:
+                                              | no-cache
+                                              | -> cache-control: no-cache
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+                           "www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                           "cache-control", "no-cache"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  53) cache-control: no-cache
+  //   [  2] (s =  57) :authority: www.example.com
+  //         Table size: 110
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"cache-control", "no-cache"}, {":authority", "www.example.com"}}));
+  ASSERT_EQ(110u, current_header_table_size());
+
+  // C.4.2 Third Request
+  hpack_block = HpackExampleToStringOrDie(R"(
+    82                                      | == Indexed - Add ==
+                                            |   idx = 2
+                                            | -> :method: GET
+    87                                      | == Indexed - Add ==
+                                            |   idx = 7
+                                            | -> :scheme: https
+    85                                      | == Indexed - Add ==
+                                            |   idx = 5
+                                            | -> :path: /index.html
+    bf                                      | == Indexed - Add ==
+                                            |   idx = 63
+                                            | -> :authority:
+                                            |   www.example.com
+    40                                      | == Literal indexed ==
+    88                                      |   Literal name (len = 8)
+                                            |     Huffman encoded:
+    25a8 49e9 5ba9 7d7f                     | %.I.[.}.
+                                            |     Decoded:
+                                            | custom-key
+    89                                      |   Literal value (len = 9)
+                                            |     Huffman encoded:
+    25a8 49e9 5bb8 e8b4 bf                  | %.I.[....
+                                            |     Decoded:
+                                            | custom-value
+                                            | -> custom-key:
+                                            |   custom-value
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "https"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path",
+                           "/index.html"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+                           "www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "custom-key",
+                           "custom-value"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  54) custom-key: custom-value
+  //   [  2] (s =  53) cache-control: no-cache
+  //   [  3] (s =  57) :authority: www.example.com
+  //         Table size: 164
+  ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"},
+                                          {"cache-control", "no-cache"},
+                                          {":authority", "www.example.com"}}));
+  ASSERT_EQ(164u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.5: Response Examples without Huffman
+// Coding. This section shows several consecutive header lists, corresponding
+// to HTTP responses, on the same connection. The HTTP/2 setting parameter
+// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing
+// some evictions to occur.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.5
+TEST_P(HpackDecoderTest, C5_ResponseExamples) {
+  set_header_table_size_limit(256);
+
+  // C.5.1 First Response
+  //
+  // Header list to encode:
+  //
+  //   :status: 302
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+
+  Http2String hpack_block = HpackExampleToStringOrDie(R"(
+      48                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 8)
+                                              |     :status
+      03                                      |   Literal value (len = 3)
+      3330 32                                 | 302
+                                              | -> :status: 302
+      58                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 24)
+                                              |     cache-control
+      07                                      |   Literal value (len = 7)
+      7072 6976 6174 65                       | private
+                                              | -> cache-control: private
+      61                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 33)
+                                              |     date
+      1d                                      |   Literal value (len = 29)
+      4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+      2032 303a 3133 3a32 3120 474d 54        |  20:13:21 GMT
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:21 GMT
+      6e                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 46)
+                                              |     location
+      17                                      |   Literal value (len = 23)
+      6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam
+      706c 652e 636f 6d                       | ple.com
+                                              | -> location:
+                                              |   https://www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(header_entries_,
+              ElementsAreArray({
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   ":status", "302"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "cache-control", "private"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "location", "https://www.example.com"},
+              }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  63) location: https://www.example.com
+  //   [  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  3] (s =  52) cache-control: private
+  //   [  4] (s =  42) :status: 302
+  //         Table size: 222
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"},
+                                  {":status", "302"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.2 Second Response
+  //
+  // The (":status", "302") header field is evicted from the dynamic table to
+  // free space to allow adding the (":status", "307") header field.
+  //
+  // Header list to encode:
+  //
+  //   :status: 307
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+
+  hpack_block = HpackExampleToStringOrDie(R"(
+      48                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 8)
+                                              |     :status
+      03                                      |   Literal value (len = 3)
+      3330 37                                 | 307
+                                              | - evict: :status: 302
+                                              | -> :status: 307
+      c1                                      | == Indexed - Add ==
+                                              |   idx = 65
+                                              | -> cache-control: private
+      c0                                      | == Indexed - Add ==
+                                              |   idx = 64
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:21 GMT
+      bf                                      | == Indexed - Add ==
+                                              |   idx = 63
+                                              | -> location:
+                                              |   https://www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(header_entries_,
+              ElementsAreArray({
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   ":status", "307"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader,
+                                   "cache-control", "private"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader, "date",
+                                   "Mon, 21 Oct 2013 20:13:21 GMT"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+                                   "https://www.example.com"},
+              }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  42) :status: 307
+  //   [  2] (s =  63) location: https://www.example.com
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  4] (s =  52) cache-control: private
+  //         Table size: 222
+
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{":status", "307"},
+                                  {"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.3 Third Response
+  //
+  // Several header fields are evicted from the dynamic table during the
+  // processing of this header list.
+  //
+  // Header list to encode:
+  //
+  //   :status: 200
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:22 GMT
+  //   location: https://www.example.com
+  //   content-encoding: gzip
+  //   set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
+  hpack_block = HpackExampleToStringOrDie(R"(
+      88                                      | == Indexed - Add ==
+                                              |   idx = 8
+                                              | -> :status: 200
+      c1                                      | == Indexed - Add ==
+                                              |   idx = 65
+                                              | -> cache-control: private
+      61                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 33)
+                                              |     date
+      1d                                      |   Literal value (len = 29)
+      4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+      2032 303a 3133 3a32 3220 474d 54        |  20:13:22 GMT
+                                              | - evict: cache-control:
+                                              |   private
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:22 GMT
+      c0                                      | == Indexed - Add ==
+                                              |   idx = 64
+                                              | -> location:
+                                              |   https://www.example.com
+      5a                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 26)
+                                              |     content-encoding
+      04                                      |   Literal value (len = 4)
+      677a 6970                               | gzip
+                                              | - evict: date: Mon, 21 Oct
+                                              |    2013 20:13:21 GMT
+                                              | -> content-encoding: gzip
+      77                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 55)
+                                              |     set-cookie
+      38                                      |   Literal value (len = 56)
+      666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO
+      5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU;
+      206d 6178 2d61 6765 3d33 3630 303b 2076 |  max-age=3600; v
+      6572 7369 6f6e 3d31                     | ersion=1
+                                              | - evict: location:
+                                              |   https://www.example.com
+                                              | - evict: :status: 307
+                                              | -> set-cookie: foo=ASDJKHQ
+                                              |   KBZXOQWEOPIUAXQWEOIU; ma
+                                              |   x-age=3600; version=1
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":status", "200"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, "cache-control",
+                           "private"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "date",
+                           "Mon, 21 Oct 2013 20:13:22 GMT"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+                           "https://www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                           "content-encoding", "gzip"},
+          HpackHeaderEntry{
+              HpackEntryType::kIndexedLiteralHeader, "set-cookie",
+              "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
+  //                    max-age=3600; version=1
+  //   [  2] (s =  52) content-encoding: gzip
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT
+  //         Table size: 215
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"set-cookie",
+        "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+       {"content-encoding", "gzip"},
+       {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}}));
+  ASSERT_EQ(215u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.6: Response Examples with Huffman Coding.
+// This section shows the same examples as the previous section but uses Huffman
+// encoding for the literal values. The HTTP/2 setting parameter
+// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing some
+// evictions to occur. The eviction mechanism uses the length of the decoded
+// literal values, so the same evictions occur as in the previous section.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.6
+TEST_P(HpackDecoderTest, C6_ResponseExamplesWithHuffmanEncoding) {
+  set_header_table_size_limit(256);
+
+  // C.5.1 First Response
+  //
+  // Header list to encode:
+  //
+  //   :status: 302
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+  Http2String hpack_block = HpackExampleToStringOrDie(R"(
+      48                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 8)
+                                              |     :status
+      03                                      |   Literal value (len = 3)
+      3330 32                                 | 302
+                                              | -> :status: 302
+      58                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 24)
+                                              |     cache-control
+      07                                      |   Literal value (len = 7)
+      7072 6976 6174 65                       | private
+                                              | -> cache-control: private
+      61                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 33)
+                                              |     date
+      1d                                      |   Literal value (len = 29)
+      4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+      2032 303a 3133 3a32 3120 474d 54        |  20:13:21 GMT
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:21 GMT
+      6e                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 46)
+                                              |     location
+      17                                      |   Literal value (len = 23)
+      6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam
+      706c 652e 636f 6d                       | ple.com
+                                              | -> location:
+                                              |   https://www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(header_entries_,
+              ElementsAreArray({
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   ":status", "302"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "cache-control", "private"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "location", "https://www.example.com"},
+              }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  63) location: https://www.example.com
+  //   [  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  3] (s =  52) cache-control: private
+  //   [  4] (s =  42) :status: 302
+  //         Table size: 222
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"},
+                                  {":status", "302"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.2 Second Response
+  //
+  // The (":status", "302") header field is evicted from the dynamic table to
+  // free space to allow adding the (":status", "307") header field.
+  //
+  // Header list to encode:
+  //
+  //   :status: 307
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+  hpack_block = HpackExampleToStringOrDie(R"(
+      48                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 8)
+                                              |     :status
+      03                                      |   Literal value (len = 3)
+      3330 37                                 | 307
+                                              | - evict: :status: 302
+                                              | -> :status: 307
+      c1                                      | == Indexed - Add ==
+                                              |   idx = 65
+                                              | -> cache-control: private
+      c0                                      | == Indexed - Add ==
+                                              |   idx = 64
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:21 GMT
+      bf                                      | == Indexed - Add ==
+                                              |   idx = 63
+                                              | -> location:
+                                              |   https://www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(header_entries_,
+              ElementsAreArray({
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   ":status", "307"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader,
+                                   "cache-control", "private"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader, "date",
+                                   "Mon, 21 Oct 2013 20:13:21 GMT"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+                                   "https://www.example.com"},
+              }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  42) :status: 307
+  //   [  2] (s =  63) location: https://www.example.com
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  4] (s =  52) cache-control: private
+  //         Table size: 222
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{":status", "307"},
+                                  {"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.3 Third Response
+  //
+  // Several header fields are evicted from the dynamic table during the
+  // processing of this header list.
+  //
+  // Header list to encode:
+  //
+  //   :status: 200
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:22 GMT
+  //   location: https://www.example.com
+  //   content-encoding: gzip
+  //   set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
+  hpack_block = HpackExampleToStringOrDie(R"(
+      88                                      | == Indexed - Add ==
+                                              |   idx = 8
+                                              | -> :status: 200
+      c1                                      | == Indexed - Add ==
+                                              |   idx = 65
+                                              | -> cache-control: private
+      61                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 33)
+                                              |     date
+      1d                                      |   Literal value (len = 29)
+      4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+      2032 303a 3133 3a32 3220 474d 54        |  20:13:22 GMT
+                                              | - evict: cache-control:
+                                              |   private
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:22 GMT
+      c0                                      | == Indexed - Add ==
+                                              |   idx = 64
+                                              | -> location:
+                                              |   https://www.example.com
+      5a                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 26)
+                                              |     content-encoding
+      04                                      |   Literal value (len = 4)
+      677a 6970                               | gzip
+                                              | - evict: date: Mon, 21 Oct
+                                              |    2013 20:13:21 GMT
+                                              | -> content-encoding: gzip
+      77                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 55)
+                                              |     set-cookie
+      38                                      |   Literal value (len = 56)
+      666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO
+      5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU;
+      206d 6178 2d61 6765 3d33 3630 303b 2076 |  max-age=3600; v
+      6572 7369 6f6e 3d31                     | ersion=1
+                                              | - evict: location:
+                                              |   https://www.example.com
+                                              | - evict: :status: 307
+                                              | -> set-cookie: foo=ASDJKHQ
+                                              |   KBZXOQWEOPIUAXQWEOIU; ma
+                                              |   x-age=3600; version=1
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":status", "200"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, "cache-control",
+                           "private"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "date",
+                           "Mon, 21 Oct 2013 20:13:22 GMT"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+                           "https://www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                           "content-encoding", "gzip"},
+          HpackHeaderEntry{
+              HpackEntryType::kIndexedLiteralHeader, "set-cookie",
+              "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
+  //                    max-age=3600; version=1
+  //   [  2] (s =  52) content-encoding: gzip
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT
+  //         Table size: 215
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"set-cookie",
+        "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+       {"content-encoding", "gzip"},
+       {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}}));
+  ASSERT_EQ(215u, current_header_table_size());
+}
+
+// Confirm that the table size can be changed, but at most twice.
+TEST_P(HpackDecoderTest, ProcessesOptionalTableSizeUpdates) {
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  // One update allowed.
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(3000);
+    EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+    EXPECT_EQ(3000u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+    EXPECT_TRUE(header_entries_.empty());
+  }
+  // Two updates allowed.
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(2000);
+    hbb.AppendDynamicTableSizeUpdate(2500);
+    EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+    EXPECT_EQ(2500u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+    EXPECT_TRUE(header_entries_.empty());
+  }
+  // A third update in the same HPACK block is rejected, so the final
+  // size is 1000, not 500.
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(1500);
+    hbb.AppendDynamicTableSizeUpdate(1000);
+    hbb.AppendDynamicTableSizeUpdate(500);
+    EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+    EXPECT_EQ(1u, error_messages_.size());
+    EXPECT_THAT(error_messages_[0], HasSubstr("size update not allowed"));
+    EXPECT_EQ(1000u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+    EXPECT_TRUE(header_entries_.empty());
+  }
+  // An error has been detected, so calls to HpackDecoder::DecodeFragment
+  // should return immediately.
+  DecodeBuffer db("\x80");
+  EXPECT_FALSE(decoder_.DecodeFragment(&db));
+  EXPECT_EQ(0u, db.Offset());
+  EXPECT_EQ(1u, error_messages_.size());
+}
+
+// Confirm that the table size can be changed when required, but at most twice.
+TEST_P(HpackDecoderTest, ProcessesRequiredTableSizeUpdate) {
+  // One update required, two allowed, one provided, followed by a header.
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  decoder_.ApplyHeaderTableSizeSetting(2048);
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(1024);
+    hbb.AppendIndexedHeader(4);  // :path: /
+    EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+    EXPECT_THAT(header_entries_,
+                ElementsAreArray({HpackHeaderEntry{
+                    HpackEntryType::kIndexedHeader, ":path", "/"}}));
+    EXPECT_EQ(1024u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+  }
+  // One update required, two allowed, two provided, followed by a header.
+  decoder_.ApplyHeaderTableSizeSetting(1000);
+  decoder_.ApplyHeaderTableSizeSetting(1500);
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(500);
+    hbb.AppendDynamicTableSizeUpdate(1250);
+    hbb.AppendIndexedHeader(5);  // :path: /index.html
+    EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+    EXPECT_THAT(header_entries_,
+                ElementsAreArray({HpackHeaderEntry{
+                    HpackEntryType::kIndexedHeader, ":path", "/index.html"}}));
+    EXPECT_EQ(1250u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+  }
+  // One update required, two allowed, three provided, followed by a header.
+  // The third update is rejected, so the final size is 1000, not 500.
+  decoder_.ApplyHeaderTableSizeSetting(500);
+  decoder_.ApplyHeaderTableSizeSetting(1000);
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(200);
+    hbb.AppendDynamicTableSizeUpdate(700);
+    hbb.AppendDynamicTableSizeUpdate(900);
+    hbb.AppendIndexedHeader(5);  // Not decoded.
+    EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+    EXPECT_FALSE(saw_end_);
+    EXPECT_EQ(1u, error_messages_.size());
+    EXPECT_THAT(error_messages_[0], HasSubstr("size update not allowed"));
+    EXPECT_EQ(700u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+    EXPECT_TRUE(header_entries_.empty());
+  }
+  // Now that an error has been detected, StartDecodingBlock should return
+  // false.
+  EXPECT_FALSE(decoder_.StartDecodingBlock());
+}
+
+// Confirm that required size updates are validated.
+TEST_P(HpackDecoderTest, InvalidRequiredSizeUpdate) {
+  // Require a size update, but provide one that isn't small enough (must be
+  // zero or one, in this case).
+  decoder_.ApplyHeaderTableSizeSetting(1);
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  HpackBlockBuilder hbb;
+  hbb.AppendDynamicTableSizeUpdate(2);
+  EXPECT_TRUE(decoder_.StartDecodingBlock());
+  DecodeBuffer db(hbb.buffer());
+  EXPECT_FALSE(decoder_.DecodeFragment(&db));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0], HasSubstr("above low water mark"));
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+}
+
+// Confirm that required size updates are indeed required before the end.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeEnd) {
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  EXPECT_FALSE(DecodeBlock(""));
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0],
+              HasSubstr("Missing dynamic table size update"));
+  EXPECT_FALSE(saw_end_);
+}
+
+// Confirm that required size updates are indeed required before an
+// indexed header.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeader) {
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  HpackBlockBuilder hbb;
+  hbb.AppendIndexedHeader(1);
+  EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0],
+              HasSubstr("Missing dynamic table size update"));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that required size updates are indeed required before an indexed
+// header name.
+// TODO(jamessynge): Move some of these to hpack_decoder_state_test.cc.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeaderName) {
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  HpackBlockBuilder hbb;
+  hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 2,
+                                     false, "PUT");
+  EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0],
+              HasSubstr("Missing dynamic table size update"));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that required size updates are indeed required before a literal
+// header name.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeLiteralName) {
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  HpackBlockBuilder hbb;
+  hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+                                false, "name", false, "some data.");
+  EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0],
+              HasSubstr("Missing dynamic table size update"));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that an excessively long varint is detected, in this case an
+// index of 127, but with lots of additional high-order 0 bits provided,
+// too many to be allowed.
+TEST_P(HpackDecoderTest, InvalidIndexedHeaderVarint) {
+  EXPECT_TRUE(decoder_.StartDecodingBlock());
+  DecodeBuffer db("\xff\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x00");
+  EXPECT_FALSE(decoder_.DecodeFragment(&db));
+  EXPECT_TRUE(decoder_.error_detected());
+  EXPECT_FALSE(saw_end_);
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0], HasSubstr("malformed"));
+  EXPECT_TRUE(header_entries_.empty());
+  // Now that an error has been detected, EndDecodingBlock should not succeed.
+  EXPECT_FALSE(decoder_.EndDecodingBlock());
+}
+
+// Confirm that an invalid index into the tables is detected, in this case an
+// index of 0.
+TEST_P(HpackDecoderTest, InvalidIndex) {
+  EXPECT_TRUE(decoder_.StartDecodingBlock());
+  DecodeBuffer db("\x80");
+  EXPECT_FALSE(decoder_.DecodeFragment(&db));
+  EXPECT_TRUE(decoder_.error_detected());
+  EXPECT_FALSE(saw_end_);
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0], HasSubstr("Invalid index"));
+  EXPECT_TRUE(header_entries_.empty());
+  // Now that an error has been detected, EndDecodingBlock should not succeed.
+  EXPECT_FALSE(decoder_.EndDecodingBlock());
+}
+
+// Confirm that EndDecodingBlock detects a truncated HPACK block.
+TEST_P(HpackDecoderTest, TruncatedBlock) {
+  HpackBlockBuilder hbb;
+  hbb.AppendDynamicTableSizeUpdate(3000);
+  EXPECT_EQ(3u, hbb.size());
+  hbb.AppendDynamicTableSizeUpdate(4000);
+  EXPECT_EQ(6u, hbb.size());
+  // Decodes this block if the whole thing is provided.
+  EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+  EXPECT_EQ(4000u, header_table_size_limit());
+  // Multiple times even.
+  EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+  EXPECT_EQ(4000u, header_table_size_limit());
+  // But not if the block is truncated.
+  EXPECT_FALSE(DecodeBlock(hbb.buffer().substr(0, hbb.size() - 1)));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0], HasSubstr("truncated"));
+  // The first update was decoded.
+  EXPECT_EQ(3000u, header_table_size_limit());
+  EXPECT_EQ(0u, current_header_table_size());
+  EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that an oversized string is detected, ending decoding.
+TEST_P(HpackDecoderTest, OversizeStringDetected) {
+  HpackBlockBuilder hbb;
+  hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+                                false, "name", false, "some data.");
+  hbb.AppendLiteralNameAndValue(HpackEntryType::kUnindexedLiteralHeader, false,
+                                "name2", false, "longer data");
+
+  // Normally able to decode this block.
+  EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+  EXPECT_THAT(header_entries_,
+              ElementsAreArray(
+                  {HpackHeaderEntry{HpackEntryType::kNeverIndexedLiteralHeader,
+                                    "name", "some data."},
+                   HpackHeaderEntry{HpackEntryType::kUnindexedLiteralHeader,
+                                    "name2", "longer data"}}));
+
+  // But not if the maximum size of strings is less than the longest string.
+  decoder_.set_max_string_size_bytes(10);
+  EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+  EXPECT_THAT(
+      header_entries_,
+      ElementsAreArray({HpackHeaderEntry{
+          HpackEntryType::kNeverIndexedLiteralHeader, "name", "some data."}}));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0], HasSubstr("too long"));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_collector.cc b/http2/hpack/decoder/hpack_entry_collector.cc
new file mode 100644
index 0000000..9d17465
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_collector.cc
@@ -0,0 +1,301 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_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 Http2String& value)
+    : header_type_(type),
+      index_(index),
+      value_(value, value_huffman),
+      started_(true),
+      ended_(true) {}
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
+                                         bool name_huffman,
+                                         const Http2String& name,
+                                         bool value_huffman,
+                                         const Http2String& 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,
+    Http2StringPiece 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,
+    Http2StringPiece expected_name,
+    bool expected_value_huffman,
+    Http2StringPiece 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) {
+        CHECK(name_.IsClear());
+        hbb->AppendNameIndexAndLiteralValue(header_type_, index_,
+                                            value_.huffman_encoded, value_.s);
+      } else {
+        CHECK(name_.HasEnded()) << *this;
+        hbb->AppendLiteralNameAndValue(header_type_, name_.huffman_encoded,
+                                       name_.s, value_.huffman_encoded,
+                                       value_.s);
+      }
+      return;
+
+    default:
+      ADD_FAILURE() << *this;
+  }
+}
+
+Http2String HpackEntryCollector::ToString() const {
+  Http2String 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 {
+        Http2StrAppend(&result, header_type_);
+      }
+  }
+  if (index_ != 0) {
+    Http2StrAppend(&result, " Index=", index_);
+  }
+  if (!name_.IsClear()) {
+    Http2StrAppend(&result, " Name", name_.ToString());
+  }
+  if (!value_.IsClear()) {
+    Http2StrAppend(&result, " Value", value_.ToString());
+  }
+  if (!started_) {
+    EXPECT_FALSE(ended_);
+    Http2StrAppend(&result, " !started");
+  } else if (!ended_) {
+    Http2StrAppend(&result, " !ended");
+  } else {
+    Http2StrAppend(&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/http2/hpack/decoder/hpack_entry_collector.h b/http2/hpack/decoder/hpack_entry_collector.h
new file mode 100644
index 0000000..c2c5fe5
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_collector.h
@@ -0,0 +1,155 @@
+// 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_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_
+#define QUICHE_HTTP2_HPACK_DECODER_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 "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+class 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 Http2String& value);
+  HpackEntryCollector(HpackEntryType type,
+                      bool name_huffman,
+                      const Http2String& name,
+                      bool value_huffman,
+                      const Http2String& 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,
+      Http2StringPiece 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,
+      Http2StringPiece expected_name,
+      bool expected_value_huffman,
+      Http2StringPiece 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.
+  Http2String 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;
+};
+
+bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b);
+bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b);
+std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_
diff --git a/http2/hpack/decoder/hpack_entry_decoder.cc b/http2/hpack/decoder/hpack_entry_decoder.cc
new file mode 100644
index 0000000..89562a9
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_decoder.cc
@@ -0,0 +1,267 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h"
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+namespace {
+// Converts calls from HpackStringDecoder when decoding a header name into the
+// appropriate HpackEntryDecoderListener::OnName* calls.
+class NameDecoderListener {
+ public:
+  explicit NameDecoderListener(HpackEntryDecoderListener* listener)
+      : listener_(listener) {}
+  bool OnStringStart(bool huffman_encoded, size_t len) {
+    listener_->OnNameStart(huffman_encoded, len);
+    return true;
+  }
+  void OnStringData(const char* data, size_t len) {
+    listener_->OnNameData(data, len);
+  }
+  void OnStringEnd() { listener_->OnNameEnd(); }
+
+ private:
+  HpackEntryDecoderListener* listener_;
+};
+
+// Converts calls from HpackStringDecoder when decoding a header value into
+// the appropriate HpackEntryDecoderListener::OnValue* calls.
+class ValueDecoderListener {
+ public:
+  explicit ValueDecoderListener(HpackEntryDecoderListener* listener)
+      : listener_(listener) {}
+  bool OnStringStart(bool huffman_encoded, size_t len) {
+    listener_->OnValueStart(huffman_encoded, len);
+    return true;
+  }
+  void OnStringData(const char* data, size_t len) {
+    listener_->OnValueData(data, len);
+  }
+  void OnStringEnd() { listener_->OnValueEnd(); }
+
+ private:
+  HpackEntryDecoderListener* listener_;
+};
+}  // namespace
+
+DecodeStatus HpackEntryDecoder::Start(DecodeBuffer* db,
+                                      HpackEntryDecoderListener* listener) {
+  DCHECK(db != nullptr);
+  DCHECK(listener != nullptr);
+  DCHECK(db->HasData());
+  DecodeStatus status = entry_type_decoder_.Start(db);
+  switch (status) {
+    case DecodeStatus::kDecodeDone:
+      // The type of the entry and its varint fit into the current decode
+      // buffer.
+      if (entry_type_decoder_.entry_type() == HpackEntryType::kIndexedHeader) {
+        // The entry consists solely of the entry type and varint.
+        // This is by far the most common case in practice.
+        listener->OnIndexedHeader(entry_type_decoder_.varint());
+        return DecodeStatus::kDecodeDone;
+      }
+      state_ = EntryDecoderState::kDecodedType;
+      return Resume(db, listener);
+    case DecodeStatus::kDecodeInProgress:
+      // Hit the end of the decode buffer before fully decoding
+      // the entry type and varint.
+      DCHECK_EQ(0u, db->Remaining());
+      state_ = EntryDecoderState::kResumeDecodingType;
+      return status;
+    case DecodeStatus::kDecodeError:
+      // The varint must have been invalid (too long).
+      return status;
+  }
+
+  HTTP2_BUG << "Unreachable";
+  return DecodeStatus::kDecodeError;
+}
+
+DecodeStatus HpackEntryDecoder::Resume(DecodeBuffer* db,
+                                       HpackEntryDecoderListener* listener) {
+  DCHECK(db != nullptr);
+  DCHECK(listener != nullptr);
+
+  DecodeStatus status;
+
+  do {
+    switch (state_) {
+      case EntryDecoderState::kResumeDecodingType:
+        // entry_type_decoder_ returned kDecodeInProgress when last called.
+        DVLOG(1) << "kResumeDecodingType: db->Remaining=" << db->Remaining();
+        status = entry_type_decoder_.Resume(db);
+        if (status != DecodeStatus::kDecodeDone) {
+          return status;
+        }
+        state_ = EntryDecoderState::kDecodedType;
+        HTTP2_FALLTHROUGH;
+
+      case EntryDecoderState::kDecodedType:
+        // entry_type_decoder_ returned kDecodeDone, now need to decide how
+        // to proceed.
+        DVLOG(1) << "kDecodedType: db->Remaining=" << db->Remaining();
+        if (DispatchOnType(listener)) {
+          // All done.
+          return DecodeStatus::kDecodeDone;
+        }
+        continue;
+
+      case EntryDecoderState::kStartDecodingName:
+        DVLOG(1) << "kStartDecodingName: db->Remaining=" << db->Remaining();
+        {
+          NameDecoderListener ncb(listener);
+          status = string_decoder_.Start(db, &ncb);
+        }
+        if (status != DecodeStatus::kDecodeDone) {
+          // On the assumption that the status is kDecodeInProgress, set
+          // state_ accordingly; unnecessary if status is kDecodeError, but
+          // that will only happen if the varint encoding the name's length
+          // is too long.
+          state_ = EntryDecoderState::kResumeDecodingName;
+          return status;
+        }
+        state_ = EntryDecoderState::kStartDecodingValue;
+        HTTP2_FALLTHROUGH;
+
+      case EntryDecoderState::kStartDecodingValue:
+        DVLOG(1) << "kStartDecodingValue: db->Remaining=" << db->Remaining();
+        {
+          ValueDecoderListener vcb(listener);
+          status = string_decoder_.Start(db, &vcb);
+        }
+        if (status == DecodeStatus::kDecodeDone) {
+          // Done with decoding the literal value, so we've reached the
+          // end of the header entry.
+          return status;
+        }
+        // On the assumption that the status is kDecodeInProgress, set
+        // state_ accordingly; unnecessary if status is kDecodeError, but
+        // that will only happen if the varint encoding the value's length
+        // is too long.
+        state_ = EntryDecoderState::kResumeDecodingValue;
+        return status;
+
+      case EntryDecoderState::kResumeDecodingName:
+        // The literal name was split across decode buffers.
+        DVLOG(1) << "kResumeDecodingName: db->Remaining=" << db->Remaining();
+        {
+          NameDecoderListener ncb(listener);
+          status = string_decoder_.Resume(db, &ncb);
+        }
+        if (status != DecodeStatus::kDecodeDone) {
+          // On the assumption that the status is kDecodeInProgress, set
+          // state_ accordingly; unnecessary if status is kDecodeError, but
+          // that will only happen if the varint encoding the name's length
+          // is too long.
+          state_ = EntryDecoderState::kResumeDecodingName;
+          return status;
+        }
+        state_ = EntryDecoderState::kStartDecodingValue;
+        break;
+
+      case EntryDecoderState::kResumeDecodingValue:
+        // The literal value was split across decode buffers.
+        DVLOG(1) << "kResumeDecodingValue: db->Remaining=" << db->Remaining();
+        {
+          ValueDecoderListener vcb(listener);
+          status = string_decoder_.Resume(db, &vcb);
+        }
+        if (status == DecodeStatus::kDecodeDone) {
+          // Done with decoding the value, therefore the entry as a whole.
+          return status;
+        }
+        // On the assumption that the status is kDecodeInProgress, set
+        // state_ accordingly; unnecessary if status is kDecodeError, but
+        // that will only happen if the varint encoding the value's length
+        // is too long.
+        state_ = EntryDecoderState::kResumeDecodingValue;
+        return status;
+    }
+  } while (true);
+}
+
+bool HpackEntryDecoder::DispatchOnType(HpackEntryDecoderListener* listener) {
+  const HpackEntryType entry_type = entry_type_decoder_.entry_type();
+  const uint32_t varint = entry_type_decoder_.varint();
+  switch (entry_type) {
+    case HpackEntryType::kIndexedHeader:
+      // The entry consists solely of the entry type and varint. See:
+      // http://httpwg.org/specs/rfc7541.html#indexed.header.representation
+      listener->OnIndexedHeader(varint);
+      return true;
+
+    case HpackEntryType::kIndexedLiteralHeader:
+    case HpackEntryType::kUnindexedLiteralHeader:
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      // The entry has a literal value, and if the varint is zero also has a
+      // literal name preceding the value. See:
+      // http://httpwg.org/specs/rfc7541.html#literal.header.representation
+      listener->OnStartLiteralHeader(entry_type, varint);
+      if (varint == 0) {
+        state_ = EntryDecoderState::kStartDecodingName;
+      } else {
+        state_ = EntryDecoderState::kStartDecodingValue;
+      }
+      return false;
+
+    case HpackEntryType::kDynamicTableSizeUpdate:
+      // The entry consists solely of the entry type and varint. FWIW, I've
+      // never seen this type of entry in production (primarily browser
+      // traffic) so if you're designing an HPACK successor someday, consider
+      // dropping it or giving it a much longer prefix. See:
+      // http://httpwg.org/specs/rfc7541.html#encoding.context.update
+      listener->OnDynamicTableSizeUpdate(varint);
+      return true;
+  }
+
+  HTTP2_BUG << "Unreachable, entry_type=" << entry_type;
+  return true;
+}
+
+void HpackEntryDecoder::OutputDebugString(std::ostream& out) const {
+  out << "HpackEntryDecoder(state=" << state_ << ", " << entry_type_decoder_
+      << ", " << string_decoder_ << ")";
+}
+
+Http2String HpackEntryDecoder::DebugString() const {
+  std::stringstream s;
+  s << *this;
+  return s.str();
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackEntryDecoder& v) {
+  v.OutputDebugString(out);
+  return out;
+}
+
+std::ostream& operator<<(std::ostream& out,
+                         HpackEntryDecoder::EntryDecoderState state) {
+  typedef HpackEntryDecoder::EntryDecoderState EntryDecoderState;
+  switch (state) {
+    case EntryDecoderState::kResumeDecodingType:
+      return out << "kResumeDecodingType";
+    case EntryDecoderState::kDecodedType:
+      return out << "kDecodedType";
+    case EntryDecoderState::kStartDecodingName:
+      return out << "kStartDecodingName";
+    case EntryDecoderState::kResumeDecodingName:
+      return out << "kResumeDecodingName";
+    case EntryDecoderState::kStartDecodingValue:
+      return out << "kStartDecodingValue";
+    case EntryDecoderState::kResumeDecodingValue:
+      return out << "kResumeDecodingValue";
+  }
+  return out << static_cast<int>(state);
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_decoder.h b/http2/hpack/decoder/hpack_entry_decoder.h
new file mode 100644
index 0000000..fe59d96
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_decoder.h
@@ -0,0 +1,85 @@
+// 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_HPACK_DECODER_HPACK_ENTRY_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_
+
+// HpackEntryDecoder decodes a single HPACK entry (i.e. one header or one
+// dynamic table size update), in a resumable fashion. The first call, Start(),
+// must provide a non-empty decode buffer. Continue with calls to Resume() if
+// Start, and any subsequent calls to Resume, returns kDecodeInProgress.
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoder {
+ public:
+  enum class EntryDecoderState {
+    // Have started decoding the type/varint, but didn't finish on the previous
+    // attempt.  Next state is kResumeDecodingType or kDecodedType.
+    kResumeDecodingType,
+
+    // Have just finished decoding the type/varint. Final state if the type is
+    // kIndexedHeader or kDynamicTableSizeUpdate. Otherwise, the next state is
+    // kStartDecodingName (if the varint is 0), else kStartDecodingValue.
+    kDecodedType,
+
+    // Ready to start decoding the literal name of a header entry. Next state
+    // is kResumeDecodingName (if the name is split across decode buffers),
+    // else kStartDecodingValue.
+    kStartDecodingName,
+
+    // Resume decoding the literal name of a header that is split across decode
+    // buffers.
+    kResumeDecodingName,
+
+    // Ready to start decoding the literal value of a header entry. Final state
+    // if the value string is entirely in the decode buffer, else the next state
+    // is kResumeDecodingValue.
+    kStartDecodingValue,
+
+    // Resume decoding the literal value of a header that is split across decode
+    // buffers.
+    kResumeDecodingValue,
+  };
+
+  // Only call when the decode buffer has data (i.e. HpackBlockDecoder must
+  // not call until there is data).
+  DecodeStatus Start(DecodeBuffer* db, HpackEntryDecoderListener* listener);
+
+  // Only call Resume if the previous call (Start or Resume) returned
+  // kDecodeInProgress; Resume is also called from Start when it has succeeded
+  // in decoding the entry type and its varint.
+  DecodeStatus Resume(DecodeBuffer* db, HpackEntryDecoderListener* listener);
+
+  Http2String DebugString() const;
+  void OutputDebugString(std::ostream& out) const;
+
+ private:
+  // Implements handling state kDecodedType.
+  bool DispatchOnType(HpackEntryDecoderListener* listener);
+
+  HpackEntryTypeDecoder entry_type_decoder_;
+  HpackStringDecoder string_decoder_;
+  EntryDecoderState state_ = EntryDecoderState();
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const HpackEntryDecoder& v);
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(
+    std::ostream& out,
+    HpackEntryDecoder::EntryDecoderState state);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_
diff --git a/http2/hpack/decoder/hpack_entry_decoder_listener.cc b/http2/hpack/decoder/hpack_entry_decoder_listener.cc
new file mode 100644
index 0000000..b783b15
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_decoder_listener.cc
@@ -0,0 +1,81 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+
+#include "base/logging.h"
+
+namespace http2 {
+
+void HpackEntryDecoderVLoggingListener::OnIndexedHeader(size_t index) {
+  VLOG(1) << "OnIndexedHeader, index=" << index;
+  if (wrapped_) {
+    wrapped_->OnIndexedHeader(index);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnStartLiteralHeader(
+    HpackEntryType entry_type,
+    size_t maybe_name_index) {
+  VLOG(1) << "OnStartLiteralHeader: entry_type=" << entry_type
+          << ", maybe_name_index=" << maybe_name_index;
+  if (wrapped_) {
+    wrapped_->OnStartLiteralHeader(entry_type, maybe_name_index);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnNameStart(bool huffman_encoded,
+                                                    size_t len) {
+  VLOG(1) << "OnNameStart: H=" << huffman_encoded << ", len=" << len;
+  if (wrapped_) {
+    wrapped_->OnNameStart(huffman_encoded, len);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnNameData(const char* data,
+                                                   size_t len) {
+  VLOG(1) << "OnNameData: len=" << len;
+  if (wrapped_) {
+    wrapped_->OnNameData(data, len);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnNameEnd() {
+  VLOG(1) << "OnNameEnd";
+  if (wrapped_) {
+    wrapped_->OnNameEnd();
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnValueStart(bool huffman_encoded,
+                                                     size_t len) {
+  VLOG(1) << "OnValueStart: H=" << huffman_encoded << ", len=" << len;
+  if (wrapped_) {
+    wrapped_->OnValueStart(huffman_encoded, len);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnValueData(const char* data,
+                                                    size_t len) {
+  VLOG(1) << "OnValueData: len=" << len;
+  if (wrapped_) {
+    wrapped_->OnValueData(data, len);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnValueEnd() {
+  VLOG(1) << "OnValueEnd";
+  if (wrapped_) {
+    wrapped_->OnValueEnd();
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnDynamicTableSizeUpdate(size_t size) {
+  VLOG(1) << "OnDynamicTableSizeUpdate: size=" << size;
+  if (wrapped_) {
+    wrapped_->OnDynamicTableSizeUpdate(size);
+  }
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_decoder_listener.h b/http2/hpack/decoder/hpack_entry_decoder_listener.h
new file mode 100644
index 0000000..fd11f59
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_decoder_listener.h
@@ -0,0 +1,110 @@
+// 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_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_
+
+// Defines HpackEntryDecoderListener, the base class of listeners that
+// HpackEntryDecoder calls. Also defines HpackEntryDecoderVLoggingListener
+// which logs before calling another HpackEntryDecoderListener implementation.
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoderListener {
+ public:
+  virtual ~HpackEntryDecoderListener() {}
+
+  // Called when an indexed header (i.e. one in the static or dynamic table) has
+  // been decoded from an HPACK block. index is supposed to be non-zero, but
+  // that has not been checked by the caller.
+  virtual void OnIndexedHeader(size_t index) = 0;
+
+  // Called when the start of a header with a literal value, and maybe a literal
+  // name, has been decoded. maybe_name_index is zero if the header has a
+  // literal name, else it is a reference into the static or dynamic table, from
+  // which the name should be determined. When the name is literal, the next
+  // call will be to OnNameStart; else it will be to OnValueStart. entry_type
+  // indicates whether the peer has added the entry to its dynamic table, and
+  // whether a proxy is permitted to do so when forwarding the entry.
+  virtual void OnStartLiteralHeader(HpackEntryType entry_type,
+                                    size_t maybe_name_index) = 0;
+
+  // Called when the encoding (Huffman compressed or plain text) and the encoded
+  // length of a literal name has been decoded. OnNameData will be called next,
+  // and repeatedly until the sum of lengths passed to OnNameData is len.
+  virtual void OnNameStart(bool huffman_encoded, size_t len) = 0;
+
+  // Called when len bytes of an encoded header name have been decoded.
+  virtual void OnNameData(const char* data, size_t len) = 0;
+
+  // Called after the entire name has been passed to OnNameData.
+  // OnValueStart will be called next.
+  virtual void OnNameEnd() = 0;
+
+  // Called when the encoding (Huffman compressed or plain text) and the encoded
+  // length of a literal value has been decoded. OnValueData will be called
+  // next, and repeatedly until the sum of lengths passed to OnValueData is len.
+  virtual void OnValueStart(bool huffman_encoded, size_t len) = 0;
+
+  // Called when len bytes of an encoded header value have been decoded.
+  virtual void OnValueData(const char* data, size_t len) = 0;
+
+  // Called after the entire value has been passed to OnValueData, marking the
+  // end of a header entry with a literal value, and maybe a literal name.
+  virtual void OnValueEnd() = 0;
+
+  // Called when an update to the size of the peer's dynamic table has been
+  // decoded.
+  virtual void OnDynamicTableSizeUpdate(size_t size) = 0;
+};
+
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoderVLoggingListener
+    : public HpackEntryDecoderListener {
+ public:
+  HpackEntryDecoderVLoggingListener() : wrapped_(nullptr) {}
+  explicit HpackEntryDecoderVLoggingListener(HpackEntryDecoderListener* wrapped)
+      : wrapped_(wrapped) {}
+  ~HpackEntryDecoderVLoggingListener() override {}
+
+  void OnIndexedHeader(size_t index) override;
+  void OnStartLiteralHeader(HpackEntryType entry_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;
+
+ private:
+  HpackEntryDecoderListener* const wrapped_;
+};
+
+// A no-op implementation of HpackEntryDecoderListener.
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoderNoOpListener
+    : public HpackEntryDecoderListener {
+ public:
+  ~HpackEntryDecoderNoOpListener() override {}
+
+  void OnIndexedHeader(size_t index) override {}
+  void OnStartLiteralHeader(HpackEntryType entry_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 {}
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_
diff --git a/http2/hpack/decoder/hpack_entry_decoder_test.cc b/http2/hpack/decoder/hpack_entry_decoder_test.cc
new file mode 100644
index 0000000..7249bd5
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_decoder_test.cc
@@ -0,0 +1,211 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h"
+
+// Tests of HpackEntryDecoder.
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+class HpackEntryDecoderTest : public RandomDecoderTest {
+ protected:
+  HpackEntryDecoderTest() : listener_(&collector_) {}
+
+  DecodeStatus StartDecoding(DecodeBuffer* b) override {
+    collector_.Clear();
+    return decoder_.Start(b, &listener_);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+    return decoder_.Resume(b, &listener_);
+  }
+
+  AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db,
+                                               const Validator& validator) {
+    // StartDecoding, above, requires the DecodeBuffer be non-empty so that it
+    // can call Start with the prefix byte.
+    bool return_non_zero_on_first = true;
+    return RandomDecoderTest::DecodeAndValidateSeveralWays(
+        db, return_non_zero_on_first, validator);
+  }
+
+  AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb,
+                                               const Validator& validator) {
+    DecodeBuffer db(hbb.buffer());
+    return DecodeAndValidateSeveralWays(&db, validator);
+  }
+
+  HpackEntryDecoder decoder_;
+  HpackEntryCollector collector_;
+  HpackEntryDecoderVLoggingListener listener_;
+};
+
+TEST_F(HpackEntryDecoderTest, IndexedHeader_Literals) {
+  {
+    const char input[] = {'\x82'};  // == Index 2 ==
+    DecodeBuffer b(input);
+    auto do_check = [this]() {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(2));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+  collector_.Clear();
+  {
+    const char input[] = {'\xfe'};  // == Index 126 ==
+    DecodeBuffer b(input);
+    auto do_check = [this]() {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(126));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+  collector_.Clear();
+  {
+    const char input[] = {'\xff', '\x00'};  // == Index 127 ==
+    DecodeBuffer b(input);
+    auto do_check = [this]() {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(127));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+}
+
+TEST_F(HpackEntryDecoderTest, IndexedHeader_Various) {
+  // Indices chosen to hit encoding and table boundaries.
+  for (const uint32_t ndx : {1, 2, 61, 62, 63, 126, 127, 254, 255, 256}) {
+    HpackBlockBuilder hbb;
+    hbb.AppendIndexedHeader(ndx);
+
+    auto do_check = [this, ndx]() {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(ndx));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+}
+
+TEST_F(HpackEntryDecoderTest, IndexedLiteralValue_Literal) {
+  const char input[] =
+      "\x7f"            // == Literal indexed, name index 0x40 ==
+      "\x01"            // 2nd byte of name index (0x01 + 0x3f == 0x40)
+      "\x0d"            // Value length (13)
+      "custom-header";  // Value
+  DecodeBuffer b(input, sizeof input - 1);
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralValueHeader(
+        HpackEntryType::kIndexedLiteralHeader, 0x40, false, "custom-header"));
+  };
+  EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+TEST_F(HpackEntryDecoderTest, IndexedLiteralNameValue_Literal) {
+  const char input[] =
+      "\x40"            // == Literal indexed ==
+      "\x0a"            // Name length (10)
+      "custom-key"      // Name
+      "\x0d"            // Value length (13)
+      "custom-header";  // Value
+
+  DecodeBuffer b(input, sizeof input - 1);
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralNameValueHeader(
+        HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false,
+        "custom-header"));
+  };
+  EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+TEST_F(HpackEntryDecoderTest, DynamicTableSizeUpdate_Literal) {
+  // Size update, length 31.
+  const char input[] = "\x3f\x00";
+  DecodeBuffer b(input, 2);
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateDynamicTableSizeUpdate(31));
+  };
+  EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+class HpackLiteralEntryDecoderTest
+    : public HpackEntryDecoderTest,
+      public ::testing::WithParamInterface<HpackEntryType> {
+ protected:
+  HpackLiteralEntryDecoderTest() : entry_type_(GetParam()) {}
+
+  const HpackEntryType entry_type_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+    AllLiteralTypes,
+    HpackLiteralEntryDecoderTest,
+    testing::Values(HpackEntryType::kIndexedLiteralHeader,
+                    HpackEntryType::kUnindexedLiteralHeader,
+                    HpackEntryType::kNeverIndexedLiteralHeader));
+
+TEST_P(HpackLiteralEntryDecoderTest, RandNameIndexAndLiteralValue) {
+  for (int n = 0; n < 10; n++) {
+    const uint32_t ndx = 1 + Random().Rand8();
+    const bool value_is_huffman_encoded = (n % 2) == 0;
+    const Http2String value = Random().RandString(Random().Rand8());
+    HpackBlockBuilder hbb;
+    hbb.AppendNameIndexAndLiteralValue(entry_type_, ndx,
+                                       value_is_huffman_encoded, value);
+    auto do_check = [this, ndx, value_is_huffman_encoded,
+                     value]() -> AssertionResult {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralValueHeader(
+          entry_type_, ndx, value_is_huffman_encoded, value));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+}
+
+TEST_P(HpackLiteralEntryDecoderTest, RandLiteralNameAndValue) {
+  for (int n = 0; n < 10; n++) {
+    const bool name_is_huffman_encoded = (n & 1) == 0;
+    const int name_len = 1 + Random().Rand8();
+    const Http2String name = Random().RandString(name_len);
+    const bool value_is_huffman_encoded = (n & 2) == 0;
+    const int value_len = Random().Skewed(10);
+    const Http2String value = Random().RandString(value_len);
+    HpackBlockBuilder hbb;
+    hbb.AppendLiteralNameAndValue(entry_type_, name_is_huffman_encoded, name,
+                                  value_is_huffman_encoded, value);
+    auto do_check = [this, name_is_huffman_encoded, name,
+                     value_is_huffman_encoded, value]() -> AssertionResult {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralNameValueHeader(
+          entry_type_, name_is_huffman_encoded, name, value_is_huffman_encoded,
+          value));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_type_decoder.cc b/http2/hpack/decoder/hpack_entry_type_decoder.cc
new file mode 100644
index 0000000..121e3d0
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_type_decoder.cc
@@ -0,0 +1,358 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+Http2String HpackEntryTypeDecoder::DebugString() const {
+  return Http2StrCat(
+      "HpackEntryTypeDecoder(varint_decoder=", varint_decoder_.DebugString(),
+      ", entry_type=", entry_type_, ")");
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackEntryTypeDecoder& v) {
+  return out << v.DebugString();
+}
+
+// This ridiculous looking function turned out to be the winner in benchmarking
+// of several very different alternative implementations. It would be even
+// faster (~7%) if inlined in the header file, but I'm not sure if that is
+// worth doing... yet.
+// TODO(jamessynge): Benchmark again at a higher level (e.g. at least at the
+// full HTTP/2 decoder level, but preferably still higher) to determine if the
+// alternatives that take less code/data space are preferable in that situation.
+DecodeStatus HpackEntryTypeDecoder::Start(DecodeBuffer* db) {
+  DCHECK(db != nullptr);
+  DCHECK(db->HasData());
+
+  // The high four bits (nibble) of first byte of the entry determine the type
+  // of the entry, and may also be the initial bits of the varint that
+  // represents an index or table size. Note the use of the word 'initial'
+  // rather than 'high'; the HPACK encoding of varints is not in network
+  // order (i.e. not big-endian, the high-order byte isn't first), nor in
+  // little-endian order. See:
+  // http://httpwg.org/specs/rfc7541.html#integer.representation
+  uint8_t byte = db->DecodeUInt8();
+  switch (byte) {
+    case 0b00000000:
+    case 0b00000001:
+    case 0b00000010:
+    case 0b00000011:
+    case 0b00000100:
+    case 0b00000101:
+    case 0b00000110:
+    case 0b00000111:
+    case 0b00001000:
+    case 0b00001001:
+    case 0b00001010:
+    case 0b00001011:
+    case 0b00001100:
+    case 0b00001101:
+    case 0b00001110:
+      // The low 4 bits of |byte| are the initial bits of the varint.
+      // One of those bits is 0, so the varint is only one byte long.
+      entry_type_ = HpackEntryType::kUnindexedLiteralHeader;
+      varint_decoder_.set_value(byte);
+      return DecodeStatus::kDecodeDone;
+
+    case 0b00001111:
+      // The low 4 bits of |byte| are the initial bits of the varint. All 4
+      // are 1, so the varint extends into another byte.
+      entry_type_ = HpackEntryType::kUnindexedLiteralHeader;
+      return varint_decoder_.StartExtended(4, db);
+
+    case 0b00010000:
+    case 0b00010001:
+    case 0b00010010:
+    case 0b00010011:
+    case 0b00010100:
+    case 0b00010101:
+    case 0b00010110:
+    case 0b00010111:
+    case 0b00011000:
+    case 0b00011001:
+    case 0b00011010:
+    case 0b00011011:
+    case 0b00011100:
+    case 0b00011101:
+    case 0b00011110:
+      // The low 4 bits of |byte| are the initial bits of the varint.
+      // One of those bits is 0, so the varint is only one byte long.
+      entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader;
+      varint_decoder_.set_value(byte & 0x0f);
+      return DecodeStatus::kDecodeDone;
+
+    case 0b00011111:
+      // The low 4 bits of |byte| are the initial bits of the varint.
+      // All of those bits are 1, so the varint extends into another byte.
+      entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader;
+      return varint_decoder_.StartExtended(4, db);
+
+    case 0b00100000:
+    case 0b00100001:
+    case 0b00100010:
+    case 0b00100011:
+    case 0b00100100:
+    case 0b00100101:
+    case 0b00100110:
+    case 0b00100111:
+    case 0b00101000:
+    case 0b00101001:
+    case 0b00101010:
+    case 0b00101011:
+    case 0b00101100:
+    case 0b00101101:
+    case 0b00101110:
+    case 0b00101111:
+    case 0b00110000:
+    case 0b00110001:
+    case 0b00110010:
+    case 0b00110011:
+    case 0b00110100:
+    case 0b00110101:
+    case 0b00110110:
+    case 0b00110111:
+    case 0b00111000:
+    case 0b00111001:
+    case 0b00111010:
+    case 0b00111011:
+    case 0b00111100:
+    case 0b00111101:
+    case 0b00111110:
+      entry_type_ = HpackEntryType::kDynamicTableSizeUpdate;
+      // The low 5 bits of |byte| are the initial bits of the varint.
+      // One of those bits is 0, so the varint is only one byte long.
+      varint_decoder_.set_value(byte & 0x01f);
+      return DecodeStatus::kDecodeDone;
+
+    case 0b00111111:
+      entry_type_ = HpackEntryType::kDynamicTableSizeUpdate;
+      // The low 5 bits of |byte| are the initial bits of the varint.
+      // All of those bits are 1, so the varint extends into another byte.
+      return varint_decoder_.StartExtended(5, db);
+
+    case 0b01000000:
+    case 0b01000001:
+    case 0b01000010:
+    case 0b01000011:
+    case 0b01000100:
+    case 0b01000101:
+    case 0b01000110:
+    case 0b01000111:
+    case 0b01001000:
+    case 0b01001001:
+    case 0b01001010:
+    case 0b01001011:
+    case 0b01001100:
+    case 0b01001101:
+    case 0b01001110:
+    case 0b01001111:
+    case 0b01010000:
+    case 0b01010001:
+    case 0b01010010:
+    case 0b01010011:
+    case 0b01010100:
+    case 0b01010101:
+    case 0b01010110:
+    case 0b01010111:
+    case 0b01011000:
+    case 0b01011001:
+    case 0b01011010:
+    case 0b01011011:
+    case 0b01011100:
+    case 0b01011101:
+    case 0b01011110:
+    case 0b01011111:
+    case 0b01100000:
+    case 0b01100001:
+    case 0b01100010:
+    case 0b01100011:
+    case 0b01100100:
+    case 0b01100101:
+    case 0b01100110:
+    case 0b01100111:
+    case 0b01101000:
+    case 0b01101001:
+    case 0b01101010:
+    case 0b01101011:
+    case 0b01101100:
+    case 0b01101101:
+    case 0b01101110:
+    case 0b01101111:
+    case 0b01110000:
+    case 0b01110001:
+    case 0b01110010:
+    case 0b01110011:
+    case 0b01110100:
+    case 0b01110101:
+    case 0b01110110:
+    case 0b01110111:
+    case 0b01111000:
+    case 0b01111001:
+    case 0b01111010:
+    case 0b01111011:
+    case 0b01111100:
+    case 0b01111101:
+    case 0b01111110:
+      entry_type_ = HpackEntryType::kIndexedLiteralHeader;
+      // The low 6 bits of |byte| are the initial bits of the varint.
+      // One of those bits is 0, so the varint is only one byte long.
+      varint_decoder_.set_value(byte & 0x03f);
+      return DecodeStatus::kDecodeDone;
+
+    case 0b01111111:
+      entry_type_ = HpackEntryType::kIndexedLiteralHeader;
+      // The low 6 bits of |byte| are the initial bits of the varint.
+      // All of those bits are 1, so the varint extends into another byte.
+      return varint_decoder_.StartExtended(6, db);
+
+    case 0b10000000:
+    case 0b10000001:
+    case 0b10000010:
+    case 0b10000011:
+    case 0b10000100:
+    case 0b10000101:
+    case 0b10000110:
+    case 0b10000111:
+    case 0b10001000:
+    case 0b10001001:
+    case 0b10001010:
+    case 0b10001011:
+    case 0b10001100:
+    case 0b10001101:
+    case 0b10001110:
+    case 0b10001111:
+    case 0b10010000:
+    case 0b10010001:
+    case 0b10010010:
+    case 0b10010011:
+    case 0b10010100:
+    case 0b10010101:
+    case 0b10010110:
+    case 0b10010111:
+    case 0b10011000:
+    case 0b10011001:
+    case 0b10011010:
+    case 0b10011011:
+    case 0b10011100:
+    case 0b10011101:
+    case 0b10011110:
+    case 0b10011111:
+    case 0b10100000:
+    case 0b10100001:
+    case 0b10100010:
+    case 0b10100011:
+    case 0b10100100:
+    case 0b10100101:
+    case 0b10100110:
+    case 0b10100111:
+    case 0b10101000:
+    case 0b10101001:
+    case 0b10101010:
+    case 0b10101011:
+    case 0b10101100:
+    case 0b10101101:
+    case 0b10101110:
+    case 0b10101111:
+    case 0b10110000:
+    case 0b10110001:
+    case 0b10110010:
+    case 0b10110011:
+    case 0b10110100:
+    case 0b10110101:
+    case 0b10110110:
+    case 0b10110111:
+    case 0b10111000:
+    case 0b10111001:
+    case 0b10111010:
+    case 0b10111011:
+    case 0b10111100:
+    case 0b10111101:
+    case 0b10111110:
+    case 0b10111111:
+    case 0b11000000:
+    case 0b11000001:
+    case 0b11000010:
+    case 0b11000011:
+    case 0b11000100:
+    case 0b11000101:
+    case 0b11000110:
+    case 0b11000111:
+    case 0b11001000:
+    case 0b11001001:
+    case 0b11001010:
+    case 0b11001011:
+    case 0b11001100:
+    case 0b11001101:
+    case 0b11001110:
+    case 0b11001111:
+    case 0b11010000:
+    case 0b11010001:
+    case 0b11010010:
+    case 0b11010011:
+    case 0b11010100:
+    case 0b11010101:
+    case 0b11010110:
+    case 0b11010111:
+    case 0b11011000:
+    case 0b11011001:
+    case 0b11011010:
+    case 0b11011011:
+    case 0b11011100:
+    case 0b11011101:
+    case 0b11011110:
+    case 0b11011111:
+    case 0b11100000:
+    case 0b11100001:
+    case 0b11100010:
+    case 0b11100011:
+    case 0b11100100:
+    case 0b11100101:
+    case 0b11100110:
+    case 0b11100111:
+    case 0b11101000:
+    case 0b11101001:
+    case 0b11101010:
+    case 0b11101011:
+    case 0b11101100:
+    case 0b11101101:
+    case 0b11101110:
+    case 0b11101111:
+    case 0b11110000:
+    case 0b11110001:
+    case 0b11110010:
+    case 0b11110011:
+    case 0b11110100:
+    case 0b11110101:
+    case 0b11110110:
+    case 0b11110111:
+    case 0b11111000:
+    case 0b11111001:
+    case 0b11111010:
+    case 0b11111011:
+    case 0b11111100:
+    case 0b11111101:
+    case 0b11111110:
+      entry_type_ = HpackEntryType::kIndexedHeader;
+      // The low 7 bits of |byte| are the initial bits of the varint.
+      // One of those bits is 0, so the varint is only one byte long.
+      varint_decoder_.set_value(byte & 0x07f);
+      return DecodeStatus::kDecodeDone;
+
+    case 0b11111111:
+      entry_type_ = HpackEntryType::kIndexedHeader;
+      // The low 7 bits of |byte| are the initial bits of the varint.
+      // All of those bits are 1, so the varint extends into another byte.
+      return varint_decoder_.StartExtended(7, db);
+  }
+  HTTP2_BUG << "Unreachable, byte=" << std::hex << static_cast<uint32_t>(byte);
+  return DecodeStatus::kDecodeError;
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_type_decoder.h b/http2/hpack/decoder/hpack_entry_type_decoder.h
new file mode 100644
index 0000000..1c0f2ac
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_type_decoder.h
@@ -0,0 +1,57 @@
+// 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_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_
+
+// Decodes the type of an HPACK entry, and the variable length integer whose
+// prefix is in the low-order bits of the same byte, "below" the type bits.
+// The integer represents an index into static or dynamic table, which may be
+// zero, or is the new size limit of the dynamic table.
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackEntryTypeDecoder {
+ public:
+  // Only call when the decode buffer has data (i.e. HpackEntryDecoder must
+  // not call until there is data).
+  DecodeStatus Start(DecodeBuffer* db);
+
+  // Only call Resume if the previous call (Start or Resume) returned
+  // DecodeStatus::kDecodeInProgress.
+  DecodeStatus Resume(DecodeBuffer* db) { return varint_decoder_.Resume(db); }
+
+  // Returns the decoded entry type. Only call if the preceding call to Start
+  // or Resume returned kDecodeDone.
+  HpackEntryType entry_type() const { return entry_type_; }
+
+  // Returns the decoded variable length integer. Only call if the
+  // preceding call to Start or Resume returned kDecodeDone.
+  uint32_t varint() const { return varint_decoder_.value(); }
+
+  Http2String DebugString() const;
+
+ private:
+  HpackVarintDecoder varint_decoder_;
+
+  // This field is initialized just to keep ASAN happy about reading it
+  // from DebugString().
+  HpackEntryType entry_type_ = HpackEntryType::kIndexedHeader;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const HpackEntryTypeDecoder& v);
+
+}  // namespace http2
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_
diff --git a/http2/hpack/decoder/hpack_entry_type_decoder_test.cc b/http2/hpack/decoder/hpack_entry_type_decoder_test.cc
new file mode 100644
index 0000000..afff7cc
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_type_decoder_test.cc
@@ -0,0 +1,87 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+const bool kReturnNonZeroOnFirst = true;
+
+class HpackEntryTypeDecoderTest : public RandomDecoderTest {
+ protected:
+  DecodeStatus StartDecoding(DecodeBuffer* b) override {
+    CHECK_LT(0u, b->Remaining());
+    return decoder_.Start(b);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+    return decoder_.Resume(b);
+  }
+
+  HpackEntryTypeDecoder decoder_;
+};
+
+TEST_F(HpackEntryTypeDecoderTest, DynamicTableSizeUpdate) {
+  for (uint32_t size = 0; size < 1000 * 1000; size += 256) {
+    HpackBlockBuilder bb;
+    bb.AppendDynamicTableSizeUpdate(size);
+    DecodeBuffer db(bb.buffer());
+    auto validator = [size, this]() -> AssertionResult {
+      VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, decoder_.entry_type());
+      VERIFY_EQ(size, decoder_.varint());
+      return AssertionSuccess();
+    };
+    EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst,
+                                             ValidateDoneAndEmpty(validator)))
+        << "\nentry_type=kDynamicTableSizeUpdate, size=" << size;
+    // Run the validator again to make sure that DecodeAndValidateSeveralWays
+    // did the right thing.
+    EXPECT_TRUE(validator());
+  }
+}
+
+TEST_F(HpackEntryTypeDecoderTest, HeaderWithIndex) {
+  std::vector<HpackEntryType> entry_types = {
+      HpackEntryType::kIndexedHeader,
+      HpackEntryType::kIndexedLiteralHeader,
+      HpackEntryType::kUnindexedLiteralHeader,
+      HpackEntryType::kNeverIndexedLiteralHeader,
+  };
+  for (const HpackEntryType entry_type : entry_types) {
+    const uint32_t first = entry_type == HpackEntryType::kIndexedHeader ? 1 : 0;
+    for (uint32_t index = first; index < 1000; ++index) {
+      HpackBlockBuilder bb;
+      bb.AppendEntryTypeAndVarint(entry_type, index);
+      DecodeBuffer db(bb.buffer());
+      auto validator = [entry_type, index, this]() -> AssertionResult {
+        VERIFY_EQ(entry_type, decoder_.entry_type());
+        VERIFY_EQ(index, decoder_.varint());
+        return AssertionSuccess();
+      };
+      EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst,
+                                               ValidateDoneAndEmpty(validator)))
+          << "\nentry_type=" << entry_type << ", index=" << index;
+      // Run the validator again to make sure that DecodeAndValidateSeveralWays
+      // did the right thing.
+      EXPECT_TRUE(validator());
+    }
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_string_collector.cc b/http2/hpack/decoder/hpack_string_collector.cc
new file mode 100644
index 0000000..247ce9c
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_collector.cc
@@ -0,0 +1,123 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <ostream>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_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 Http2String& 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) {
+  Http2StringPiece sp(data, length);
+  EXPECT_TRUE(IsInProgress()) << ToString();
+  EXPECT_LE(sp.size(), len) << ToString();
+  Http2StrAppend(&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(
+    Http2StringPiece 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();
+}
+
+Http2String 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=\"" << Http2HexEscape(v.s) << "\")";
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_string_collector.h b/http2/hpack/decoder/hpack_string_collector.h
new file mode 100644
index 0000000..76be13b
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_collector.h
@@ -0,0 +1,63 @@
+// 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_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_
+
+// Supports tests of decoding HPACK strings.
+
+#include <stddef.h>
+
+#include <iosfwd>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+// Records the callbacks associated with a decoding a string; must
+// call Clear() between decoding successive strings.
+struct HpackStringCollector : public HpackStringDecoderListener {
+  enum CollectorState {
+    kGenesis,
+    kStarted,
+    kEnded,
+  };
+
+  HpackStringCollector();
+  HpackStringCollector(const Http2String& 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(Http2StringPiece str,
+                                       bool is_huffman_encoded) const;
+
+  Http2String ToString() const;
+
+  Http2String 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);
+
+std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_
diff --git a/http2/hpack/decoder/hpack_string_decoder.cc b/http2/hpack/decoder/hpack_string_decoder.cc
new file mode 100644
index 0000000..0b9eb59
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_decoder.cc
@@ -0,0 +1,35 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h"
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+Http2String HpackStringDecoder::DebugString() const {
+  return Http2StrCat("HpackStringDecoder(state=", StateToString(state_),
+                     ", length=", length_decoder_.DebugString(),
+                     ", remaining=", remaining_,
+                     ", huffman=", huffman_encoded_ ? "true)" : "false)");
+}
+
+// static
+Http2String HpackStringDecoder::StateToString(StringDecoderState v) {
+  switch (v) {
+    case kStartDecodingLength:
+      return "kStartDecodingLength";
+    case kDecodingString:
+      return "kDecodingString";
+    case kResumeDecodingLength:
+      return "kResumeDecodingLength";
+  }
+  return Http2StrCat("UNKNOWN_STATE(", static_cast<uint32_t>(v), ")");
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackStringDecoder& v) {
+  return out << v.DebugString();
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_string_decoder.h b/http2/hpack/decoder/hpack_string_decoder.h
new file mode 100644
index 0000000..8ec0169
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_decoder.h
@@ -0,0 +1,208 @@
+// 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_HPACK_DECODER_HPACK_STRING_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
+
+// HpackStringDecoder decodes strings encoded per the HPACK spec; this does
+// not mean decompressing Huffman encoded strings, just identifying the length,
+// encoding and contents for a listener.
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <cstdint>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+// Decodes a single string in an HPACK header entry. The high order bit of
+// the first byte of the length is the H (Huffman) bit indicating whether
+// the value is Huffman encoded, and the remainder of the byte is the first
+// 7 bits of an HPACK varint.
+//
+// Call Start() to begin decoding; if it returns kDecodeInProgress, then call
+// Resume() when more input is available, repeating until kDecodeInProgress is
+// not returned. If kDecodeDone or kDecodeError is returned, then Resume() must
+// not be called until Start() has been called to start decoding a new string.
+class HTTP2_EXPORT_PRIVATE HpackStringDecoder {
+ public:
+  enum StringDecoderState {
+    kStartDecodingLength,
+    kDecodingString,
+    kResumeDecodingLength,
+  };
+
+  template <class Listener>
+  DecodeStatus Start(DecodeBuffer* db, Listener* cb) {
+    // Fast decode path is used if the string is under 127 bytes and the
+    // entire length of the string is in the decode buffer. More than 83% of
+    // string lengths are encoded in just one byte.
+    if (db->HasData() && (*db->cursor() & 0x7f) != 0x7f) {
+      // The string is short.
+      uint8_t h_and_prefix = db->DecodeUInt8();
+      uint8_t length = h_and_prefix & 0x7f;
+      bool huffman_encoded = (h_and_prefix & 0x80) == 0x80;
+      cb->OnStringStart(huffman_encoded, length);
+      if (length <= db->Remaining()) {
+        // Yeah, we've got the whole thing in the decode buffer.
+        // Ideally this will be the common case. Note that we don't
+        // update any of the member variables in this path.
+        cb->OnStringData(db->cursor(), length);
+        db->AdvanceCursor(length);
+        cb->OnStringEnd();
+        return DecodeStatus::kDecodeDone;
+      }
+      // Not all in the buffer.
+      huffman_encoded_ = huffman_encoded;
+      remaining_ = length;
+      // Call Resume to decode the string body, which is only partially
+      // in the decode buffer (or not at all).
+      state_ = kDecodingString;
+      return Resume(db, cb);
+    }
+    // Call Resume to decode the string length, which is either not in
+    // the decode buffer, or spans multiple bytes.
+    state_ = kStartDecodingLength;
+    return Resume(db, cb);
+  }
+
+  template <class Listener>
+  DecodeStatus Resume(DecodeBuffer* db, Listener* cb) {
+    DecodeStatus status;
+    while (true) {
+      switch (state_) {
+        case kStartDecodingLength:
+          DVLOG(2) << "kStartDecodingLength: db->Remaining=" << db->Remaining();
+          if (!StartDecodingLength(db, cb, &status)) {
+            // The length is split across decode buffers.
+            return status;
+          }
+          // We've finished decoding the length, which spanned one or more
+          // bytes. Approximately 17% of strings have a length that is greater
+          // than 126 bytes, and thus the length is encoded in more than one
+          // byte, and so doesn't get the benefit of the optimization in
+          // Start() for single byte lengths. But, we still expect that most
+          // of such strings will be contained entirely in a single decode
+          // buffer, and hence this fall through skips another trip through the
+          // switch above and more importantly skips setting the state_ variable
+          // again in those cases where we don't need it.
+          HTTP2_FALLTHROUGH;
+
+        case kDecodingString:
+          DVLOG(2) << "kDecodingString: db->Remaining=" << db->Remaining()
+                   << "    remaining_=" << remaining_;
+          return DecodeString(db, cb);
+
+        case kResumeDecodingLength:
+          DVLOG(2) << "kResumeDecodingLength: db->Remaining="
+                   << db->Remaining();
+          if (!ResumeDecodingLength(db, cb, &status)) {
+            return status;
+          }
+      }
+    }
+  }
+
+  Http2String DebugString() const;
+
+ private:
+  static Http2String StateToString(StringDecoderState v);
+
+  // Returns true if the length is fully decoded and the listener wants the
+  // decoding to continue, false otherwise; status is set to the status from
+  // the varint decoder.
+  // If the length is not fully decoded, case state_ is set appropriately
+  // for the next call to Resume.
+  template <class Listener>
+  bool StartDecodingLength(DecodeBuffer* db,
+                           Listener* cb,
+                           DecodeStatus* status) {
+    if (db->Empty()) {
+      *status = DecodeStatus::kDecodeInProgress;
+      state_ = kStartDecodingLength;
+      return false;
+    }
+    uint8_t h_and_prefix = db->DecodeUInt8();
+    huffman_encoded_ = (h_and_prefix & 0x80) == 0x80;
+    *status = length_decoder_.Start(h_and_prefix, 7, db);
+    if (*status == DecodeStatus::kDecodeDone) {
+      OnStringStart(cb, status);
+      return true;
+    }
+    // Set the state to cover the DecodeStatus::kDecodeInProgress case.
+    // Won't be needed if the status is kDecodeError.
+    state_ = kResumeDecodingLength;
+    return false;
+  }
+
+  // Returns true if the length is fully decoded and the listener wants the
+  // decoding to continue, false otherwise; status is set to the status from
+  // the varint decoder; state_ is updated when fully decoded.
+  // If the length is not fully decoded, case state_ is set appropriately
+  // for the next call to Resume.
+  template <class Listener>
+  bool ResumeDecodingLength(DecodeBuffer* db,
+                            Listener* cb,
+                            DecodeStatus* status) {
+    DCHECK_EQ(state_, kResumeDecodingLength);
+    *status = length_decoder_.Resume(db);
+    if (*status == DecodeStatus::kDecodeDone) {
+      state_ = kDecodingString;
+      OnStringStart(cb, status);
+      return true;
+    }
+    return false;
+  }
+
+  // Returns true if the listener wants the decoding to continue, and
+  // false otherwise, in which case status set.
+  template <class Listener>
+  void OnStringStart(Listener* cb, DecodeStatus* status) {
+    remaining_ = length_decoder_.value();
+    // Make callback so consumer knows what is coming.
+    cb->OnStringStart(huffman_encoded_, remaining_);
+  }
+
+  // Passes the available portion of the string to the listener, and signals
+  // the end of the string when it is reached. Returns kDecodeDone or
+  // kDecodeInProgress as appropriate.
+  template <class Listener>
+  DecodeStatus DecodeString(DecodeBuffer* db, Listener* cb) {
+    size_t len = std::min(remaining_, db->Remaining());
+    if (len > 0) {
+      cb->OnStringData(db->cursor(), len);
+      db->AdvanceCursor(len);
+      remaining_ -= len;
+    }
+    if (remaining_ == 0) {
+      cb->OnStringEnd();
+      return DecodeStatus::kDecodeDone;
+    }
+    state_ = kDecodingString;
+    return DecodeStatus::kDecodeInProgress;
+  }
+
+  HpackVarintDecoder length_decoder_;
+
+  // These fields are initialized just to keep ASAN happy about reading
+  // them from DebugString().
+  size_t remaining_ = 0;
+  StringDecoderState state_ = kStartDecodingLength;
+  bool huffman_encoded_ = false;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const HpackStringDecoder& v);
+
+}  // namespace http2
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
diff --git a/http2/hpack/decoder/hpack_string_decoder_listener.cc b/http2/hpack/decoder/hpack_string_decoder_listener.cc
new file mode 100644
index 0000000..e0fbc65
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_decoder_listener.cc
@@ -0,0 +1,36 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h"
+
+#include "base/logging.h"
+
+namespace http2 {
+namespace test {
+
+void HpackStringDecoderVLoggingListener::OnStringStart(bool huffman_encoded,
+                                                       size_t len) {
+  VLOG(1) << "OnStringStart: H=" << huffman_encoded << ", len=" << len;
+  if (wrapped_) {
+    wrapped_->OnStringStart(huffman_encoded, len);
+  }
+}
+
+void HpackStringDecoderVLoggingListener::OnStringData(const char* data,
+                                                      size_t len) {
+  VLOG(1) << "OnStringData: len=" << len;
+  if (wrapped_) {
+    return wrapped_->OnStringData(data, len);
+  }
+}
+
+void HpackStringDecoderVLoggingListener::OnStringEnd() {
+  VLOG(1) << "OnStringEnd";
+  if (wrapped_) {
+    return wrapped_->OnStringEnd();
+  }
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_string_decoder_listener.h b/http2/hpack/decoder/hpack_string_decoder_listener.h
new file mode 100644
index 0000000..35a0417
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_decoder_listener.h
@@ -0,0 +1,62 @@
+// 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_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_
+
+// Defines HpackStringDecoderListener which defines the methods required by an
+// HpackStringDecoder. Also defines HpackStringDecoderVLoggingListener which
+// logs before calling another HpackStringDecoderListener implementation.
+// For now these are only used by tests, so placed in the test namespace.
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+
+// HpackStringDecoder methods require a listener that implements the methods
+// below, but it is NOT necessary to extend this class because the methods
+// are templates.
+class HTTP2_EXPORT_PRIVATE HpackStringDecoderListener {
+ public:
+  virtual ~HpackStringDecoderListener() {}
+
+  // Called at the start of decoding an HPACK string. The encoded length of the
+  // string is |len| bytes, which may be zero. The string is Huffman encoded
+  // if huffman_encoded is true, else it is plain text (i.e. the encoded length
+  // is then the plain text length).
+  virtual void OnStringStart(bool huffman_encoded, size_t len) = 0;
+
+  // Called when some data is available, or once when the string length is zero
+  // (to simplify the decoder, it doesn't have a special case for len==0).
+  virtual void OnStringData(const char* data, size_t len) = 0;
+
+  // Called after OnStringData has provided all of the encoded bytes of the
+  // string.
+  virtual void OnStringEnd() = 0;
+};
+
+class HTTP2_EXPORT_PRIVATE HpackStringDecoderVLoggingListener
+    : public HpackStringDecoderListener {
+ public:
+  HpackStringDecoderVLoggingListener() : wrapped_(nullptr) {}
+  explicit HpackStringDecoderVLoggingListener(
+      HpackStringDecoderListener* wrapped)
+      : wrapped_(wrapped) {}
+  ~HpackStringDecoderVLoggingListener() override {}
+
+  void OnStringStart(bool huffman_encoded, size_t len) override;
+  void OnStringData(const char* data, size_t len) override;
+  void OnStringEnd() override;
+
+ private:
+  HpackStringDecoderListener* const wrapped_;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_
diff --git a/http2/hpack/decoder/hpack_string_decoder_test.cc b/http2/hpack/decoder/hpack_string_decoder_test.cc
new file mode 100644
index 0000000..349b649
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_decoder_test.cc
@@ -0,0 +1,155 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h"
+
+// Tests of HpackStringDecoder.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+const bool kMayReturnZeroOnFirst = false;
+const bool kCompressed = true;
+const bool kUncompressed = false;
+
+class HpackStringDecoderTest : public RandomDecoderTest {
+ protected:
+  HpackStringDecoderTest() : listener_(&collector_) {}
+
+  DecodeStatus StartDecoding(DecodeBuffer* b) override {
+    ++start_decoding_calls_;
+    collector_.Clear();
+    return decoder_.Start(b, &listener_);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+    // Provides coverage of DebugString and StateToString.
+    // Not validating output.
+    VLOG(1) << decoder_.DebugString();
+    VLOG(2) << collector_;
+    return decoder_.Resume(b, &listener_);
+  }
+
+  AssertionResult Collected(Http2StringPiece s, bool huffman_encoded) {
+    VLOG(1) << collector_;
+    return collector_.Collected(s, huffman_encoded);
+  }
+
+  // expected_str is a Http2String rather than a const Http2String& or
+  // Http2StringPiece so that the lambda makes a copy of the string, and thus
+  // the string to be passed to Collected outlives the call to MakeValidator.
+  Validator MakeValidator(const Http2String& expected_str,
+                          bool expected_huffman) {
+    return
+        [expected_str, expected_huffman, this](
+            const DecodeBuffer& input, DecodeStatus status) -> AssertionResult {
+          AssertionResult result = Collected(expected_str, expected_huffman);
+          if (result) {
+            VERIFY_EQ(collector_,
+                      HpackStringCollector(expected_str, expected_huffman));
+          } else {
+            VERIFY_NE(collector_,
+                      HpackStringCollector(expected_str, expected_huffman));
+          }
+          VLOG(2) << collector_.ToString();
+          collector_.Clear();
+          VLOG(2) << collector_;
+          return result;
+        };
+  }
+
+  HpackStringDecoder decoder_;
+  HpackStringCollector collector_;
+  HpackStringDecoderVLoggingListener listener_;
+  size_t start_decoding_calls_ = 0;
+};
+
+TEST_F(HpackStringDecoderTest, DecodeEmptyString) {
+  {
+    Validator validator = ValidateDoneAndEmpty(MakeValidator("", kCompressed));
+    const char kData[] = {'\x80'};
+    DecodeBuffer b(kData);
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+  }
+  {
+    // Make sure it stops after decoding the empty string.
+    Validator validator =
+        ValidateDoneAndOffset(1, MakeValidator("", kUncompressed));
+    const char kData[] = {'\x00', '\xff'};
+    DecodeBuffer b(kData);
+    EXPECT_EQ(2u, b.Remaining());
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+    EXPECT_EQ(1u, b.Remaining());
+  }
+}
+
+TEST_F(HpackStringDecoderTest, DecodeShortString) {
+  {
+    // Make sure it stops after decoding the non-empty string.
+    Validator validator =
+        ValidateDoneAndOffset(11, MakeValidator("start end.", kCompressed));
+    const char kData[] = "\x8astart end.Don't peek at this.";
+    DecodeBuffer b(kData);
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+  }
+  {
+    Validator validator =
+        ValidateDoneAndOffset(11, MakeValidator("start end.", kUncompressed));
+    Http2StringPiece data("\x0astart end.");
+    DecodeBuffer b(data);
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+  }
+}
+
+TEST_F(HpackStringDecoderTest, DecodeLongStrings) {
+  Http2String name = Random().RandString(1024);
+  Http2String value = Random().RandString(65536);
+  HpackBlockBuilder hbb;
+
+  hbb.AppendString(false, name);
+  uint32_t offset_after_name = hbb.size();
+  EXPECT_EQ(3 + name.size(), offset_after_name);
+
+  hbb.AppendString(true, value);
+  uint32_t offset_after_value = hbb.size();
+  EXPECT_EQ(3 + name.size() + 4 + value.size(), offset_after_value);
+
+  DecodeBuffer b(hbb.buffer());
+
+  // Decode the name...
+  EXPECT_TRUE(DecodeAndValidateSeveralWays(
+      &b, kMayReturnZeroOnFirst,
+      ValidateDoneAndOffset(offset_after_name,
+                            MakeValidator(name, kUncompressed))));
+  EXPECT_EQ(offset_after_name, b.Offset());
+  EXPECT_EQ(offset_after_value - offset_after_name, b.Remaining());
+
+  // Decode the value...
+  EXPECT_TRUE(DecodeAndValidateSeveralWays(
+      &b, kMayReturnZeroOnFirst,
+      ValidateDoneAndOffset(offset_after_value - offset_after_name,
+                            MakeValidator(value, kCompressed))));
+  EXPECT_EQ(offset_after_value, b.Offset());
+  EXPECT_EQ(0u, b.Remaining());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_whole_entry_buffer.cc b/http2/hpack/decoder/hpack_whole_entry_buffer.cc
new file mode 100644
index 0000000..b3e29b1
--- /dev/null
+++ b/http2/hpack/decoder/hpack_whole_entry_buffer.cc
@@ -0,0 +1,139 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+HpackWholeEntryBuffer::HpackWholeEntryBuffer(HpackWholeEntryListener* listener,
+                                             size_t max_string_size_bytes)
+    : max_string_size_bytes_(max_string_size_bytes) {
+  set_listener(listener);
+}
+HpackWholeEntryBuffer::~HpackWholeEntryBuffer() = default;
+
+void HpackWholeEntryBuffer::set_listener(HpackWholeEntryListener* listener) {
+  listener_ = HTTP2_DIE_IF_NULL(listener);
+}
+
+void HpackWholeEntryBuffer::set_max_string_size_bytes(
+    size_t max_string_size_bytes) {
+  max_string_size_bytes_ = max_string_size_bytes;
+}
+
+void HpackWholeEntryBuffer::BufferStringsIfUnbuffered() {
+  name_.BufferStringIfUnbuffered();
+  value_.BufferStringIfUnbuffered();
+}
+
+size_t HpackWholeEntryBuffer::EstimateMemoryUsage() const {
+  return Http2EstimateMemoryUsage(name_) + Http2EstimateMemoryUsage(value_);
+}
+
+void HpackWholeEntryBuffer::OnIndexedHeader(size_t index) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnIndexedHeader: index=" << index;
+  listener_->OnIndexedHeader(index);
+}
+
+void HpackWholeEntryBuffer::OnStartLiteralHeader(HpackEntryType entry_type,
+                                                 size_t maybe_name_index) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnStartLiteralHeader: entry_type="
+           << entry_type << ",  maybe_name_index=" << maybe_name_index;
+  entry_type_ = entry_type;
+  maybe_name_index_ = maybe_name_index;
+}
+
+void HpackWholeEntryBuffer::OnNameStart(bool huffman_encoded, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnNameStart: huffman_encoded="
+           << (huffman_encoded ? "true" : "false") << ",  len=" << len;
+  DCHECK_EQ(maybe_name_index_, 0u);
+  if (!error_detected_) {
+    if (len > max_string_size_bytes_) {
+      DVLOG(1) << "Name length (" << len << ") is longer than permitted ("
+               << max_string_size_bytes_ << ")";
+      ReportError("HPACK entry name size is too long.");
+      return;
+    }
+    name_.OnStart(huffman_encoded, len);
+  }
+}
+
+void HpackWholeEntryBuffer::OnNameData(const char* data, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnNameData: len=" << len << " data:\n"
+           << Http2HexDump(Http2StringPiece(data, len));
+  DCHECK_EQ(maybe_name_index_, 0u);
+  if (!error_detected_ && !name_.OnData(data, len)) {
+    ReportError("Error decoding HPACK entry name.");
+  }
+}
+
+void HpackWholeEntryBuffer::OnNameEnd() {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnNameEnd";
+  DCHECK_EQ(maybe_name_index_, 0u);
+  if (!error_detected_ && !name_.OnEnd()) {
+    ReportError("Error decoding HPACK entry name.");
+  }
+}
+
+void HpackWholeEntryBuffer::OnValueStart(bool huffman_encoded, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnValueStart: huffman_encoded="
+           << (huffman_encoded ? "true" : "false") << ",  len=" << len;
+  if (!error_detected_) {
+    if (len > max_string_size_bytes_) {
+      DVLOG(1) << "Value length (" << len << ") is longer than permitted ("
+               << max_string_size_bytes_ << ")";
+      ReportError("HPACK entry value size is too long.");
+      return;
+    }
+    value_.OnStart(huffman_encoded, len);
+  }
+}
+
+void HpackWholeEntryBuffer::OnValueData(const char* data, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnValueData: len=" << len << " data:\n"
+           << Http2HexDump(Http2StringPiece(data, len));
+  if (!error_detected_ && !value_.OnData(data, len)) {
+    ReportError("Error decoding HPACK entry value.");
+  }
+}
+
+void HpackWholeEntryBuffer::OnValueEnd() {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnValueEnd";
+  if (error_detected_) {
+    return;
+  }
+  if (!value_.OnEnd()) {
+    ReportError("Error decoding HPACK entry value.");
+    return;
+  }
+  if (maybe_name_index_ == 0) {
+    listener_->OnLiteralNameAndValue(entry_type_, &name_, &value_);
+    name_.Reset();
+  } else {
+    listener_->OnNameIndexAndLiteralValue(entry_type_, maybe_name_index_,
+                                          &value_);
+  }
+  value_.Reset();
+}
+
+void HpackWholeEntryBuffer::OnDynamicTableSizeUpdate(size_t size) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnDynamicTableSizeUpdate: size=" << size;
+  listener_->OnDynamicTableSizeUpdate(size);
+}
+
+void HpackWholeEntryBuffer::ReportError(Http2StringPiece error_message) {
+  if (!error_detected_) {
+    DVLOG(1) << "HpackWholeEntryBuffer::ReportError: " << error_message;
+    error_detected_ = true;
+    listener_->OnHpackDecodeError(error_message);
+    listener_ = HpackWholeEntryNoOpListener::NoOpListener();
+  }
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_whole_entry_buffer.h b/http2/hpack/decoder/hpack_whole_entry_buffer.h
new file mode 100644
index 0000000..61bf583
--- /dev/null
+++ b/http2/hpack/decoder/hpack_whole_entry_buffer.h
@@ -0,0 +1,104 @@
+// 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_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
+
+// HpackWholeEntryBuffer isolates a listener from the fact that an entry may
+// be split across multiple input buffers, providing one callback per entry.
+// HpackWholeEntryBuffer requires that the HpackEntryDecoderListener be made in
+// the correct order, which is tested by hpack_entry_decoder_test.cc.
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+// TODO(jamessynge): Consider renaming HpackEntryDecoderListener to
+// HpackEntryPartsListener or HpackEntryFragmentsListener.
+class HTTP2_EXPORT_PRIVATE HpackWholeEntryBuffer
+    : public HpackEntryDecoderListener {
+ public:
+  // max_string_size specifies the maximum size of an on-the-wire string (name
+  // or value, plain or Huffman encoded) that will be accepted. See sections
+  // 5.1 and 5.2 of RFC 7541. This is a defense against OOM attacks; HTTP/2
+  // allows a decoder to enforce any limit of the size of the header lists
+  // that it is willing decode, including less than the MAX_HEADER_LIST_SIZE
+  // setting, a setting that is initially unlimited. For example, we might
+  // choose to send a MAX_HEADER_LIST_SIZE of 64KB, and to use that same value
+  // as the upper bound for individual strings.
+  HpackWholeEntryBuffer(HpackWholeEntryListener* listener,
+                        size_t max_string_size);
+  ~HpackWholeEntryBuffer() override;
+
+  HpackWholeEntryBuffer(const HpackWholeEntryBuffer&) = delete;
+  HpackWholeEntryBuffer& operator=(const HpackWholeEntryBuffer&) = delete;
+
+  // Set the listener to be notified when a whole entry has been decoded.
+  // The listener may be changed at any time.
+  void set_listener(HpackWholeEntryListener* listener);
+
+  // Set how much encoded data this decoder is willing to buffer.
+  // TODO(jamessynge): Come up with consistent semantics for this protection
+  // across the various decoders; e.g. should it be for a single string or
+  // a single header entry?
+  void set_max_string_size_bytes(size_t max_string_size_bytes);
+
+  // Ensure that decoded strings pointed to by the HpackDecoderStringBuffer
+  // instances name_ and value_ are buffered, which allows any underlying
+  // transport buffer to be freed or reused without overwriting the decoded
+  // strings. This is needed only when an HPACK entry is split across transport
+  // buffers. See HpackDecoder::DecodeFragment.
+  void BufferStringsIfUnbuffered();
+
+  // Was an error detected? After an error has been detected and reported,
+  // no further callbacks will be made to the listener.
+  bool error_detected() const { return error_detected_; }
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+  // Implement the HpackEntryDecoderListener methods.
+
+  void OnIndexedHeader(size_t index) override;
+  void OnStartLiteralHeader(HpackEntryType entry_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;
+
+ private:
+  void ReportError(Http2StringPiece error_message);
+
+  HpackWholeEntryListener* listener_;
+  HpackDecoderStringBuffer name_, value_;
+
+  // max_string_size_bytes_ specifies the maximum allowed size of an on-the-wire
+  // string. Larger strings will be reported as errors to the listener; the
+  // endpoint should treat these as COMPRESSION errors, which are CONNECTION
+  // level errors.
+  size_t max_string_size_bytes_;
+
+  // The name index (or zero) of the current header entry with a literal value.
+  size_t maybe_name_index_;
+
+  // The type of the current header entry (with literals) that is being decoded.
+  HpackEntryType entry_type_;
+
+  bool error_detected_ = false;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
diff --git a/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc b/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc
new file mode 100644
index 0000000..75b281c
--- /dev/null
+++ b/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc
@@ -0,0 +1,206 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h"
+
+// Tests of HpackWholeEntryBuffer: does it buffer correctly, and does it
+// detect Huffman decoding errors and oversize string errors?
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+using ::testing::InSequence;
+using ::testing::Property;
+using ::testing::StrictMock;
+
+namespace http2 {
+namespace test {
+namespace {
+
+constexpr size_t kMaxStringSize = 20;
+
+class MockHpackWholeEntryListener : public HpackWholeEntryListener {
+ public:
+  ~MockHpackWholeEntryListener() override = default;
+
+  MOCK_METHOD1(OnIndexedHeader, void(size_t index));
+  MOCK_METHOD3(OnNameIndexAndLiteralValue,
+               void(HpackEntryType entry_type,
+                    size_t name_index,
+                    HpackDecoderStringBuffer* value_buffer));
+  MOCK_METHOD3(OnLiteralNameAndValue,
+               void(HpackEntryType entry_type,
+                    HpackDecoderStringBuffer* name_buffer,
+                    HpackDecoderStringBuffer* value_buffer));
+  MOCK_METHOD1(OnDynamicTableSizeUpdate, void(size_t size));
+  MOCK_METHOD1(OnHpackDecodeError, void(Http2StringPiece error_message));
+};
+
+class HpackWholeEntryBufferTest : public ::testing::Test {
+ protected:
+  HpackWholeEntryBufferTest() : entry_buffer_(&listener_, kMaxStringSize) {}
+  ~HpackWholeEntryBufferTest() override = default;
+
+  StrictMock<MockHpackWholeEntryListener> listener_;
+  HpackWholeEntryBuffer entry_buffer_;
+};
+
+// OnIndexedHeader is an immediate pass through.
+TEST_F(HpackWholeEntryBufferTest, OnIndexedHeader) {
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(17));
+    entry_buffer_.OnIndexedHeader(17);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(62));
+    entry_buffer_.OnIndexedHeader(62);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(62));
+    entry_buffer_.OnIndexedHeader(62);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(128));
+    entry_buffer_.OnIndexedHeader(128);
+  }
+  StrictMock<MockHpackWholeEntryListener> listener2;
+  entry_buffer_.set_listener(&listener2);
+  {
+    InSequence seq;
+    EXPECT_CALL(listener2, OnIndexedHeader(100));
+    entry_buffer_.OnIndexedHeader(100);
+  }
+}
+
+// OnDynamicTableSizeUpdate is an immediate pass through.
+TEST_F(HpackWholeEntryBufferTest, OnDynamicTableSizeUpdate) {
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(4096));
+    entry_buffer_.OnDynamicTableSizeUpdate(4096);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(0));
+    entry_buffer_.OnDynamicTableSizeUpdate(0);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024));
+    entry_buffer_.OnDynamicTableSizeUpdate(1024);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024));
+    entry_buffer_.OnDynamicTableSizeUpdate(1024);
+  }
+  StrictMock<MockHpackWholeEntryListener> listener2;
+  entry_buffer_.set_listener(&listener2);
+  {
+    InSequence seq;
+    EXPECT_CALL(listener2, OnDynamicTableSizeUpdate(0));
+    entry_buffer_.OnDynamicTableSizeUpdate(0);
+  }
+}
+
+TEST_F(HpackWholeEntryBufferTest, OnNameIndexAndLiteralValue) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader,
+                                     123);
+  entry_buffer_.OnValueStart(false, 10);
+  entry_buffer_.OnValueData("some data.", 10);
+
+  // Force the value to be buffered.
+  entry_buffer_.BufferStringsIfUnbuffered();
+
+  EXPECT_CALL(
+      listener_,
+      OnNameIndexAndLiteralValue(
+          HpackEntryType::kNeverIndexedLiteralHeader, 123,
+          AllOf(Property(&HpackDecoderStringBuffer::str, "some data."),
+                Property(&HpackDecoderStringBuffer::BufferedLength, 10))));
+
+  entry_buffer_.OnValueEnd();
+}
+
+TEST_F(HpackWholeEntryBufferTest, OnLiteralNameAndValue) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0);
+  // Force the name to be buffered by delivering it in two pieces.
+  entry_buffer_.OnNameStart(false, 9);
+  entry_buffer_.OnNameData("some-", 5);
+  entry_buffer_.OnNameData("name", 4);
+  entry_buffer_.OnNameEnd();
+  entry_buffer_.OnValueStart(false, 12);
+  entry_buffer_.OnValueData("Header Value", 12);
+
+  EXPECT_CALL(
+      listener_,
+      OnLiteralNameAndValue(
+          HpackEntryType::kIndexedLiteralHeader,
+          AllOf(Property(&HpackDecoderStringBuffer::str, "some-name"),
+                Property(&HpackDecoderStringBuffer::BufferedLength, 9)),
+          AllOf(Property(&HpackDecoderStringBuffer::str, "Header Value"),
+                Property(&HpackDecoderStringBuffer::BufferedLength, 0))));
+
+  entry_buffer_.OnValueEnd();
+}
+
+// Verify that a name longer than the allowed size generates an error.
+TEST_F(HpackWholeEntryBufferTest, NameTooLong) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0);
+  EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry name")));
+  entry_buffer_.OnNameStart(false, kMaxStringSize + 1);
+}
+
+// Verify that a name longer than the allowed size generates an error.
+TEST_F(HpackWholeEntryBufferTest, ValueTooLong) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 1);
+  EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry value")));
+  entry_buffer_.OnValueStart(false, kMaxStringSize + 1);
+}
+
+// Verify that a Huffman encoded name with an explicit EOS generates an error
+// for an explicit EOS.
+TEST_F(HpackWholeEntryBufferTest, NameHuffmanError) {
+  const char data[] = "\xff\xff\xff";
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kUnindexedLiteralHeader,
+                                     0);
+  entry_buffer_.OnNameStart(true, 4);
+  entry_buffer_.OnNameData(data, 3);
+
+  EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry name")));
+
+  entry_buffer_.OnNameData(data, 1);
+
+  // After an error is reported, the listener is not called again.
+  EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(8096)).Times(0);
+  entry_buffer_.OnDynamicTableSizeUpdate(8096);
+}
+
+// Verify that a Huffman encoded value that isn't properly terminated with
+// a partial EOS symbol generates an error.
+TEST_F(HpackWholeEntryBufferTest, ValueeHuffmanError) {
+  const char data[] = "\x00\x00\x00";
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader,
+                                     61);
+  entry_buffer_.OnValueStart(true, 3);
+  entry_buffer_.OnValueData(data, 3);
+
+  EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry value")));
+
+  entry_buffer_.OnValueEnd();
+
+  // After an error is reported, the listener is not called again.
+  EXPECT_CALL(listener_, OnIndexedHeader(17)).Times(0);
+  entry_buffer_.OnIndexedHeader(17);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_whole_entry_listener.cc b/http2/hpack/decoder/hpack_whole_entry_listener.cc
new file mode 100644
index 0000000..b92e64a
--- /dev/null
+++ b/http2/hpack/decoder/hpack_whole_entry_listener.cc
@@ -0,0 +1,33 @@
+// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h"
+
+namespace http2 {
+
+HpackWholeEntryListener::~HpackWholeEntryListener() = default;
+
+HpackWholeEntryNoOpListener::~HpackWholeEntryNoOpListener() = default;
+
+void HpackWholeEntryNoOpListener::OnIndexedHeader(size_t index) {}
+void HpackWholeEntryNoOpListener::OnNameIndexAndLiteralValue(
+    HpackEntryType entry_type,
+    size_t name_index,
+    HpackDecoderStringBuffer* value_buffer) {}
+void HpackWholeEntryNoOpListener::OnLiteralNameAndValue(
+    HpackEntryType entry_type,
+    HpackDecoderStringBuffer* name_buffer,
+    HpackDecoderStringBuffer* value_buffer) {}
+void HpackWholeEntryNoOpListener::OnDynamicTableSizeUpdate(size_t size) {}
+void HpackWholeEntryNoOpListener::OnHpackDecodeError(
+    Http2StringPiece error_message) {}
+
+// static
+HpackWholeEntryNoOpListener* HpackWholeEntryNoOpListener::NoOpListener() {
+  static HpackWholeEntryNoOpListener* static_instance =
+      new HpackWholeEntryNoOpListener();
+  return static_instance;
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_whole_entry_listener.h b/http2/hpack/decoder/hpack_whole_entry_listener.h
new file mode 100644
index 0000000..2e559ce
--- /dev/null
+++ b/http2/hpack/decoder/hpack_whole_entry_listener.h
@@ -0,0 +1,80 @@
+// 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.
+
+// Defines HpackWholeEntryListener, the base class of listeners for decoded
+// complete HPACK entries, as opposed to HpackEntryDecoderListener which
+// receives multiple callbacks for some single entries.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackWholeEntryListener {
+ public:
+  virtual ~HpackWholeEntryListener();
+
+  // Called when an indexed header (i.e. one in the static or dynamic table) has
+  // been decoded from an HPACK block. index is supposed to be non-zero, but
+  // that has not been checked by the caller.
+  virtual void OnIndexedHeader(size_t index) = 0;
+
+  // Called when a header entry with a name index and literal value has
+  // been fully decoded from an HPACK block. name_index is NOT zero.
+  // entry_type will be kIndexedLiteralHeader, kUnindexedLiteralHeader, or
+  // kNeverIndexedLiteralHeader.
+  virtual void OnNameIndexAndLiteralValue(
+      HpackEntryType entry_type,
+      size_t name_index,
+      HpackDecoderStringBuffer* value_buffer) = 0;
+
+  // Called when a header entry with a literal name and literal value
+  // has been fully decoded from an HPACK block. entry_type will be
+  // kIndexedLiteralHeader, kUnindexedLiteralHeader, or
+  // kNeverIndexedLiteralHeader.
+  virtual void OnLiteralNameAndValue(
+      HpackEntryType entry_type,
+      HpackDecoderStringBuffer* name_buffer,
+      HpackDecoderStringBuffer* value_buffer) = 0;
+
+  // Called when an update to the size of the peer's dynamic table has been
+  // decoded.
+  virtual void OnDynamicTableSizeUpdate(size_t size) = 0;
+
+  // OnHpackDecodeError is called if an error is detected while decoding.
+  // error_message may be used in a GOAWAY frame as the Opaque Data.
+  virtual void OnHpackDecodeError(Http2StringPiece error_message) = 0;
+};
+
+// A no-op implementation of HpackWholeEntryDecoderListener, useful for ignoring
+// callbacks once an error is detected.
+class HpackWholeEntryNoOpListener : public HpackWholeEntryListener {
+ public:
+  ~HpackWholeEntryNoOpListener() override;
+
+  void OnIndexedHeader(size_t index) override;
+  void OnNameIndexAndLiteralValue(
+      HpackEntryType entry_type,
+      size_t name_index,
+      HpackDecoderStringBuffer* value_buffer) override;
+  void OnLiteralNameAndValue(HpackEntryType entry_type,
+                             HpackDecoderStringBuffer* name_buffer,
+                             HpackDecoderStringBuffer* value_buffer) override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+  void OnHpackDecodeError(Http2StringPiece error_message) override;
+
+  // Returns a listener that ignores all the calls.
+  static HpackWholeEntryNoOpListener* NoOpListener();
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_
diff --git a/http2/hpack/hpack_static_table_entries.inc b/http2/hpack/hpack_static_table_entries.inc
new file mode 100644
index 0000000..c6ae125
--- /dev/null
+++ b/http2/hpack/hpack_static_table_entries.inc
@@ -0,0 +1,65 @@
+// This file is designed to be included by C/C++ files which need the contents
+// of the HPACK static table. It may be included more than once if necessary.
+// See http://httpwg.org/specs/rfc7541.html#static.table.definition
+
+STATIC_TABLE_ENTRY(":authority", "", 1);
+STATIC_TABLE_ENTRY(":method", "GET", 2);
+STATIC_TABLE_ENTRY(":method", "POST", 3);
+STATIC_TABLE_ENTRY(":path", "/", 4);
+STATIC_TABLE_ENTRY(":path", "/index.html", 5);
+STATIC_TABLE_ENTRY(":scheme", "http", 6);
+STATIC_TABLE_ENTRY(":scheme", "https", 7);
+STATIC_TABLE_ENTRY(":status", "200", 8);
+STATIC_TABLE_ENTRY(":status", "204", 9);
+STATIC_TABLE_ENTRY(":status", "206", 10);
+STATIC_TABLE_ENTRY(":status", "304", 11);
+STATIC_TABLE_ENTRY(":status", "400", 12);
+STATIC_TABLE_ENTRY(":status", "404", 13);
+STATIC_TABLE_ENTRY(":status", "500", 14);
+STATIC_TABLE_ENTRY("accept-charset", "", 15);
+STATIC_TABLE_ENTRY("accept-encoding", "gzip, deflate", 16);
+STATIC_TABLE_ENTRY("accept-language", "", 17);
+STATIC_TABLE_ENTRY("accept-ranges", "", 18);
+STATIC_TABLE_ENTRY("accept", "", 19);
+STATIC_TABLE_ENTRY("access-control-allow-origin", "", 20);
+STATIC_TABLE_ENTRY("age", "", 21);
+STATIC_TABLE_ENTRY("allow", "", 22);
+STATIC_TABLE_ENTRY("authorization", "", 23);
+STATIC_TABLE_ENTRY("cache-control", "", 24);
+STATIC_TABLE_ENTRY("content-disposition", "", 25);
+STATIC_TABLE_ENTRY("content-encoding", "", 26);
+STATIC_TABLE_ENTRY("content-language", "", 27);
+STATIC_TABLE_ENTRY("content-length", "", 28);
+STATIC_TABLE_ENTRY("content-location", "", 29);
+STATIC_TABLE_ENTRY("content-range", "", 30);
+STATIC_TABLE_ENTRY("content-type", "", 31);
+STATIC_TABLE_ENTRY("cookie", "", 32);
+STATIC_TABLE_ENTRY("date", "", 33);
+STATIC_TABLE_ENTRY("etag", "", 34);
+STATIC_TABLE_ENTRY("expect", "", 35);
+STATIC_TABLE_ENTRY("expires", "", 36);
+STATIC_TABLE_ENTRY("from", "", 37);
+STATIC_TABLE_ENTRY("host", "", 38);
+STATIC_TABLE_ENTRY("if-match", "", 39);
+STATIC_TABLE_ENTRY("if-modified-since", "", 40);
+STATIC_TABLE_ENTRY("if-none-match", "", 41);
+STATIC_TABLE_ENTRY("if-range", "", 42);
+STATIC_TABLE_ENTRY("if-unmodified-since", "", 43);
+STATIC_TABLE_ENTRY("last-modified", "", 44);
+STATIC_TABLE_ENTRY("link", "", 45);
+STATIC_TABLE_ENTRY("location", "", 46);
+STATIC_TABLE_ENTRY("max-forwards", "", 47);
+STATIC_TABLE_ENTRY("proxy-authenticate", "", 48);
+STATIC_TABLE_ENTRY("proxy-authorization", "", 49);
+STATIC_TABLE_ENTRY("range", "", 50);
+STATIC_TABLE_ENTRY("referer", "", 51);
+STATIC_TABLE_ENTRY("refresh", "", 52);
+STATIC_TABLE_ENTRY("retry-after", "", 53);
+STATIC_TABLE_ENTRY("server", "", 54);
+STATIC_TABLE_ENTRY("set-cookie", "", 55);
+STATIC_TABLE_ENTRY("strict-transport-security", "", 56);
+STATIC_TABLE_ENTRY("transfer-encoding", "", 57);
+STATIC_TABLE_ENTRY("user-agent", "", 58);
+STATIC_TABLE_ENTRY("vary", "", 59);
+STATIC_TABLE_ENTRY("via", "", 60);
+STATIC_TABLE_ENTRY("www-authenticate", "", 61);
diff --git a/http2/hpack/hpack_string.cc b/http2/hpack/hpack_string.cc
new file mode 100644
index 0000000..58bb821
--- /dev/null
+++ b/http2/hpack/hpack_string.cc
@@ -0,0 +1,72 @@
+// 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 "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+HpackString::HpackString(const char* data) : str_(data) {}
+HpackString::HpackString(Http2StringPiece str) : str_(Http2String(str)) {}
+HpackString::HpackString(Http2String str) : str_(std::move(str)) {}
+HpackString::HpackString(const HpackString& other) = default;
+HpackString::~HpackString() = default;
+
+Http2StringPiece HpackString::ToStringPiece() const {
+  return str_;
+}
+
+bool HpackString::operator==(const HpackString& other) const {
+  return str_ == other.str_;
+}
+bool HpackString::operator==(Http2StringPiece str) const {
+  return str == str_;
+}
+
+bool operator==(Http2StringPiece a, const HpackString& b) {
+  return b == a;
+}
+bool operator!=(Http2StringPiece a, const HpackString& b) {
+  return !(b == a);
+}
+bool operator!=(const HpackString& a, const HpackString& b) {
+  return !(a == b);
+}
+bool operator!=(const HpackString& a, Http2StringPiece b) {
+  return !(a == b);
+}
+std::ostream& operator<<(std::ostream& out, const HpackString& v) {
+  return out << v.ToString();
+}
+
+HpackStringPair::HpackStringPair(const HpackString& name,
+                                 const HpackString& value)
+    : name(name), value(value) {
+  DVLOG(3) << DebugString() << " ctor";
+}
+
+HpackStringPair::HpackStringPair(Http2StringPiece name, Http2StringPiece value)
+    : name(name), value(value) {
+  DVLOG(3) << DebugString() << " ctor";
+}
+
+HpackStringPair::~HpackStringPair() {
+  DVLOG(3) << DebugString() << " dtor";
+}
+
+Http2String HpackStringPair::DebugString() const {
+  return Http2StrCat("HpackStringPair(name=", name.ToString(),
+                     ", value=", value.ToString(), ")");
+}
+
+std::ostream& operator<<(std::ostream& os, const HpackStringPair& p) {
+  os << p.DebugString();
+  return os;
+}
+
+}  // namespace http2
diff --git a/http2/hpack/hpack_string.h b/http2/hpack/hpack_string.h
new file mode 100644
index 0000000..b1c499c
--- /dev/null
+++ b/http2/hpack/hpack_string.h
@@ -0,0 +1,75 @@
+// 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_HPACK_HPACK_STRING_H_
+#define QUICHE_HTTP2_HPACK_HPACK_STRING_H_
+
+// HpackString is currently a very simple container for a string, but allows us
+// to relatively easily experiment with alternate string storage mechanisms for
+// handling strings to be encoded with HPACK, or decoded from HPACK, such as
+// a ref-counted string.
+
+#include <stddef.h>
+
+#include <iosfwd>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackString {
+ public:
+  explicit HpackString(const char* data);
+  explicit HpackString(Http2StringPiece str);
+  explicit HpackString(Http2String str);
+  HpackString(const HpackString& other);
+
+  // Not sure yet whether this move ctor is required/sensible.
+  HpackString(HpackString&& other) = default;
+
+  ~HpackString();
+
+  size_t size() const { return str_.size(); }
+  const Http2String& ToString() const { return str_; }
+  Http2StringPiece ToStringPiece() const;
+
+  bool operator==(const HpackString& other) const;
+
+  bool operator==(Http2StringPiece str) const;
+
+ private:
+  Http2String str_;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(Http2StringPiece a, const HpackString& b);
+HTTP2_EXPORT_PRIVATE bool operator!=(Http2StringPiece a, const HpackString& b);
+HTTP2_EXPORT_PRIVATE bool operator!=(const HpackString& a,
+                                     const HpackString& b);
+HTTP2_EXPORT_PRIVATE bool operator!=(const HpackString& a, Http2StringPiece b);
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const HpackString& v);
+
+struct HTTP2_EXPORT_PRIVATE HpackStringPair {
+  HpackStringPair(const HpackString& name, const HpackString& value);
+  HpackStringPair(Http2StringPiece name, Http2StringPiece value);
+  ~HpackStringPair();
+
+  // Returns the size of a header entry with this name and value, per the RFC:
+  // http://httpwg.org/specs/rfc7541.html#calculating.table.size
+  size_t size() const { return 32 + name.size() + value.size(); }
+
+  Http2String DebugString() const;
+
+  const HpackString name;
+  const HpackString value;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
+                                              const HpackStringPair& p);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_HPACK_STRING_H_
diff --git a/http2/hpack/hpack_string_test.cc b/http2/hpack/hpack_string_test.cc
new file mode 100644
index 0000000..87f5975
--- /dev/null
+++ b/http2/hpack/hpack_string_test.cc
@@ -0,0 +1,149 @@
+// 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 "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+
+// Tests of HpackString.
+
+#include <utility>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+
+const char kStr0[] = "s0: Some string to be copied into another string.";
+const char kStr1[] = "S1 - some string to be copied into yet another string.";
+
+class HpackStringTest : public ::testing::Test {
+ protected:
+  AssertionResult VerifyNotEqual(HpackString* actual,
+                                 const Http2String& not_expected_str) {
+    const char* not_expected_ptr = not_expected_str.c_str();
+    Http2StringPiece not_expected_sp(not_expected_str);
+
+    VERIFY_NE(*actual, not_expected_ptr);
+    VERIFY_NE(*actual, not_expected_sp);
+    VERIFY_NE(*actual, not_expected_str);
+    VERIFY_NE(actual->ToStringPiece(), not_expected_sp);
+
+    if (!(not_expected_ptr != *actual)) {
+      return AssertionFailure();
+    }
+    if (!(not_expected_sp != *actual)) {
+      return AssertionFailure();
+    }
+    if (!(not_expected_str != *actual)) {
+      return AssertionFailure();
+    }
+    if (!(not_expected_sp != actual->ToStringPiece())) {
+      return AssertionFailure();
+    }
+
+    return AssertionSuccess();
+  }
+
+  AssertionResult VerifyEqual(HpackString* actual,
+                              const Http2String& expected_str) {
+    VERIFY_EQ(actual->size(), expected_str.size());
+
+    const char* expected_ptr = expected_str.c_str();
+    const Http2StringPiece expected_sp(expected_str);
+
+    VERIFY_EQ(*actual, expected_ptr);
+    VERIFY_EQ(*actual, expected_sp);
+    VERIFY_EQ(*actual, expected_str);
+    VERIFY_EQ(actual->ToStringPiece(), expected_sp);
+
+    if (!(expected_sp == *actual)) {
+      return AssertionFailure();
+    }
+    if (!(expected_ptr == *actual)) {
+      return AssertionFailure();
+    }
+    if (!(expected_str == *actual)) {
+      return AssertionFailure();
+    }
+    if (!(expected_sp == actual->ToStringPiece())) {
+      return AssertionFailure();
+    }
+
+    return AssertionSuccess();
+  }
+};
+
+TEST_F(HpackStringTest, CharArrayConstructor) {
+  HpackString hs0(kStr0);
+  EXPECT_TRUE(VerifyEqual(&hs0, kStr0));
+  EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1));
+
+  HpackString hs1(kStr1);
+  EXPECT_TRUE(VerifyEqual(&hs1, kStr1));
+  EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0));
+}
+
+TEST_F(HpackStringTest, StringPieceConstructor) {
+  Http2StringPiece sp0(kStr0);
+  HpackString hs0(sp0);
+  EXPECT_TRUE(VerifyEqual(&hs0, kStr0));
+  EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1));
+
+  Http2StringPiece sp1(kStr1);
+  HpackString hs1(sp1);
+  EXPECT_TRUE(VerifyEqual(&hs1, kStr1));
+  EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0));
+}
+
+TEST_F(HpackStringTest, MoveStringConstructor) {
+  Http2String str0(kStr0);
+  HpackString hs0(str0);
+  EXPECT_TRUE(VerifyEqual(&hs0, kStr0));
+  EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1));
+
+  Http2String str1(kStr1);
+  HpackString hs1(str1);
+  EXPECT_TRUE(VerifyEqual(&hs1, kStr1));
+  EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0));
+}
+
+TEST_F(HpackStringTest, CopyConstructor) {
+  Http2StringPiece sp0(kStr0);
+  HpackString hs0(sp0);
+  HpackString hs1(hs0);
+  EXPECT_EQ(hs0, hs1);
+
+  EXPECT_TRUE(VerifyEqual(&hs0, kStr0));
+  EXPECT_TRUE(VerifyEqual(&hs1, kStr0));
+
+  EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1));
+  EXPECT_TRUE(VerifyNotEqual(&hs1, kStr1));
+}
+
+TEST_F(HpackStringTest, MoveConstructor) {
+  Http2StringPiece sp0(kStr0);
+  HpackString hs0(sp0);
+  EXPECT_TRUE(VerifyEqual(&hs0, kStr0));
+  EXPECT_TRUE(VerifyNotEqual(&hs0, ""));
+
+  HpackString hs1(std::move(hs0));
+  EXPECT_NE(hs0, hs1);
+
+  EXPECT_TRUE(VerifyEqual(&hs1, kStr0));
+  EXPECT_TRUE(VerifyEqual(&hs0, ""));
+  EXPECT_TRUE(VerifyNotEqual(&hs1, ""));
+
+  LOG(INFO) << hs0;
+  LOG(INFO) << hs1;
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/http2_hpack_constants.cc b/http2/hpack/http2_hpack_constants.cc
new file mode 100644
index 0000000..f258ab9
--- /dev/null
+++ b/http2/hpack/http2_hpack_constants.cc
@@ -0,0 +1,31 @@
+// 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 "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+Http2String HpackEntryTypeToString(HpackEntryType v) {
+  switch (v) {
+    case HpackEntryType::kIndexedHeader:
+      return "kIndexedHeader";
+    case HpackEntryType::kDynamicTableSizeUpdate:
+      return "kDynamicTableSizeUpdate";
+    case HpackEntryType::kIndexedLiteralHeader:
+      return "kIndexedLiteralHeader";
+    case HpackEntryType::kUnindexedLiteralHeader:
+      return "kUnindexedLiteralHeader";
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      return "kNeverIndexedLiteralHeader";
+  }
+  return Http2StrCat("UnknownHpackEntryType(", static_cast<int>(v), ")");
+}
+
+std::ostream& operator<<(std::ostream& out, HpackEntryType v) {
+  return out << HpackEntryTypeToString(v);
+}
+
+}  // namespace http2
diff --git a/http2/hpack/http2_hpack_constants.h b/http2/hpack/http2_hpack_constants.h
new file mode 100644
index 0000000..de0d685
--- /dev/null
+++ b/http2/hpack/http2_hpack_constants.h
@@ -0,0 +1,63 @@
+// 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_HPACK_HTTP2_HPACK_CONSTANTS_H_
+#define QUICHE_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_
+
+// Enum HpackEntryType identifies the 5 basic types of HPACK Block Entries.
+//
+// See the spec for details:
+// https://http2.github.io/http2-spec/compression.html#rfc.section.6
+
+#include <ostream>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+const size_t kFirstDynamicTableIndex = 62;
+
+enum class HpackEntryType {
+  // Entry is an index into the static or dynamic table. Decoding it has no
+  // effect on the dynamic table.
+  kIndexedHeader,
+
+  // The entry contains a literal value. The name may be either a literal or a
+  // reference to an entry in the static or dynamic table.
+  // The entry is added to the dynamic table after decoding.
+  kIndexedLiteralHeader,
+
+  // The entry contains a literal value. The name may be either a literal or a
+  // reference to an entry in the static or dynamic table.
+  // The entry is not added to the dynamic table after decoding, but a proxy
+  // may choose to insert the entry into its dynamic table when forwarding
+  // to another endpoint.
+  kUnindexedLiteralHeader,
+
+  // The entry contains a literal value. The name may be either a literal or a
+  // reference to an entry in the static or dynamic table.
+  // The entry is not added to the dynamic table after decoding, and a proxy
+  // must NOT insert the entry into its dynamic table when forwarding to another
+  // endpoint.
+  kNeverIndexedLiteralHeader,
+
+  // Entry conveys the size limit of the dynamic table of the encoder to
+  // the decoder. May be used to flush the table by sending a zero and then
+  // resetting the size back up to the maximum that the encoder will use
+  // (within the limits of SETTINGS_HEADER_TABLE_SIZE sent by the
+  // decoder to the encoder, with the default of 4096 assumed).
+  kDynamicTableSizeUpdate,
+};
+
+// Returns the name of the enum member.
+HTTP2_EXPORT_PRIVATE Http2String HpackEntryTypeToString(HpackEntryType v);
+
+// Inserts the name of the enum member into |out|.
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              HpackEntryType v);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_
diff --git a/http2/hpack/http2_hpack_constants_test.cc b/http2/hpack/http2_hpack_constants_test.cc
new file mode 100644
index 0000000..5b20b18
--- /dev/null
+++ b/http2/hpack/http2_hpack_constants_test.cc
@@ -0,0 +1,72 @@
+// 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 "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_mock_log.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+TEST(HpackEntryTypeTest, HpackEntryTypeToString) {
+  EXPECT_EQ("kIndexedHeader",
+            HpackEntryTypeToString(HpackEntryType::kIndexedHeader));
+  EXPECT_EQ("kDynamicTableSizeUpdate",
+            HpackEntryTypeToString(HpackEntryType::kDynamicTableSizeUpdate));
+  EXPECT_EQ("kIndexedLiteralHeader",
+            HpackEntryTypeToString(HpackEntryType::kIndexedLiteralHeader));
+  EXPECT_EQ("kUnindexedLiteralHeader",
+            HpackEntryTypeToString(HpackEntryType::kUnindexedLiteralHeader));
+  EXPECT_EQ("kNeverIndexedLiteralHeader",
+            HpackEntryTypeToString(HpackEntryType::kNeverIndexedLiteralHeader));
+  EXPECT_EQ("UnknownHpackEntryType(12321)",
+            HpackEntryTypeToString(static_cast<HpackEntryType>(12321)));
+}
+
+TEST(HpackEntryTypeTest, OutputHpackEntryType) {
+  {
+    CREATE_HTTP2_MOCK_LOG(log);
+    log.StartCapturingLogs();
+    EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kIndexedHeader");
+    LOG(INFO) << HpackEntryType::kIndexedHeader;
+  }
+  {
+    CREATE_HTTP2_MOCK_LOG(log);
+    log.StartCapturingLogs();
+    EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kDynamicTableSizeUpdate");
+    LOG(INFO) << HpackEntryType::kDynamicTableSizeUpdate;
+  }
+  {
+    CREATE_HTTP2_MOCK_LOG(log);
+    log.StartCapturingLogs();
+    EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kIndexedLiteralHeader");
+    LOG(INFO) << HpackEntryType::kIndexedLiteralHeader;
+  }
+  {
+    CREATE_HTTP2_MOCK_LOG(log);
+    log.StartCapturingLogs();
+    EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kUnindexedLiteralHeader");
+    LOG(INFO) << HpackEntryType::kUnindexedLiteralHeader;
+  }
+  {
+    CREATE_HTTP2_MOCK_LOG(log);
+    log.StartCapturingLogs();
+    EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kNeverIndexedLiteralHeader");
+    LOG(INFO) << HpackEntryType::kNeverIndexedLiteralHeader;
+  }
+  {
+    CREATE_HTTP2_MOCK_LOG(log);
+    log.StartCapturingLogs();
+    EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "UnknownHpackEntryType(1234321)");
+    LOG(INFO) << static_cast<HpackEntryType>(1234321);
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/huffman/hpack_huffman_decoder.cc b/http2/hpack/huffman/hpack_huffman_decoder.cc
new file mode 100644
index 0000000..d1076ed
--- /dev/null
+++ b/http2/hpack/huffman/hpack_huffman_decoder.cc
@@ -0,0 +1,487 @@
+// 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 "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+
+#include <bitset>
+#include <limits>
+
+#include "base/logging.h"
+
+// Terminology:
+//
+// Symbol - a plain text (unencoded) character (uint8), or the End-of-String
+//          (EOS) symbol, 256.
+//
+// Code - the sequence of bits used to encode a symbol, varying in length from
+//        5 bits for the most common symbols (e.g. '0', '1', and 'a'), to
+//        30 bits for the least common (e.g. the EOS symbol).
+//        For those symbols whose codes have the same length, their code values
+//        are sorted such that the lower symbol value has a lower code value.
+//
+// Canonical - a symbol's cardinal value when sorted first by code length, and
+//             then by symbol value. For example, canonical 0 is for ASCII '0'
+//             (uint8 value 0x30), which is the first of the symbols whose code
+//             is 5 bits long, and the last canonical is EOS, which is the last
+//             of the symbols whose code is 30 bits long.
+
+// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature.
+
+namespace http2 {
+namespace {
+
+// HuffmanCode is used to store the codes associated with symbols (a pattern of
+// from 5 to 30 bits).
+typedef uint32_t HuffmanCode;
+
+// HuffmanCodeBitCount is used to store a count of bits in a code.
+typedef uint16_t HuffmanCodeBitCount;
+
+// HuffmanCodeBitSet is used for producing a string version of a code because
+// std::bitset logs nicely.
+typedef std::bitset<32> HuffmanCodeBitSet;
+typedef std::bitset<64> HuffmanAccumulatorBitSet;
+
+static constexpr HuffmanCodeBitCount kMinCodeBitCount = 5;
+static constexpr HuffmanCodeBitCount kMaxCodeBitCount = 30;
+static constexpr HuffmanCodeBitCount kHuffmanCodeBitCount =
+    std::numeric_limits<HuffmanCode>::digits;
+
+static_assert(std::numeric_limits<HuffmanCode>::digits >= kMaxCodeBitCount,
+              "HuffmanCode isn't big enough.");
+
+static_assert(std::numeric_limits<HuffmanAccumulator>::digits >=
+                  kMaxCodeBitCount,
+              "HuffmanAccumulator isn't big enough.");
+
+static constexpr HuffmanAccumulatorBitCount kHuffmanAccumulatorBitCount =
+    std::numeric_limits<HuffmanAccumulator>::digits;
+static constexpr HuffmanAccumulatorBitCount kExtraAccumulatorBitCount =
+    kHuffmanAccumulatorBitCount - kHuffmanCodeBitCount;
+
+// PrefixInfo holds info about a group of codes that are all of the same length.
+struct PrefixInfo {
+  // Given the leading bits (32 in this case) of the encoded string, and that
+  // they start with a code of length |code_length|, return the corresponding
+  // canonical for that leading code.
+  uint32_t DecodeToCanonical(HuffmanCode bits) const {
+    // What is the position of the canonical symbol being decoded within
+    // the canonical symbols of |length|?
+    HuffmanCode ordinal_in_length =
+        ((bits - first_code) >> (kHuffmanCodeBitCount - code_length));
+
+    // Combined with |canonical| to produce the position of the canonical symbol
+    // being decoded within all of the canonical symbols.
+    return first_canonical + ordinal_in_length;
+  }
+
+  const HuffmanCode first_code;  // First code of this length, left justified in
+                                 // the field (i.e. the first bit of the code is
+                                 // the high-order bit).
+  const uint16_t code_length;    // Length of the prefix code |base|.
+  const uint16_t first_canonical;  // First canonical symbol of this length.
+};
+
+inline std::ostream& operator<<(std::ostream& out, const PrefixInfo& v) {
+  return out << "{first_code: " << HuffmanCodeBitSet(v.first_code)
+             << ", code_length: " << v.code_length
+             << ", first_canonical: " << v.first_canonical << "}";
+}
+
+// Given |value|, a sequence of the leading bits remaining to be decoded,
+// figure out which group of canonicals (by code length) that value starts
+// with. This function was generated.
+PrefixInfo PrefixToInfo(HuffmanCode value) {
+  if (value < 0b10111000000000000000000000000000) {
+    if (value < 0b01010000000000000000000000000000) {
+      return {0b00000000000000000000000000000000, 5, 0};
+    } else {
+      return {0b01010000000000000000000000000000, 6, 10};
+    }
+  } else {
+    if (value < 0b11111110000000000000000000000000) {
+      if (value < 0b11111000000000000000000000000000) {
+        return {0b10111000000000000000000000000000, 7, 36};
+      } else {
+        return {0b11111000000000000000000000000000, 8, 68};
+      }
+    } else {
+      if (value < 0b11111111110000000000000000000000) {
+        if (value < 0b11111111101000000000000000000000) {
+          if (value < 0b11111111010000000000000000000000) {
+            return {0b11111110000000000000000000000000, 10, 74};
+          } else {
+            return {0b11111111010000000000000000000000, 11, 79};
+          }
+        } else {
+          return {0b11111111101000000000000000000000, 12, 82};
+        }
+      } else {
+        if (value < 0b11111111111111100000000000000000) {
+          if (value < 0b11111111111110000000000000000000) {
+            if (value < 0b11111111111100000000000000000000) {
+              return {0b11111111110000000000000000000000, 13, 84};
+            } else {
+              return {0b11111111111100000000000000000000, 14, 90};
+            }
+          } else {
+            return {0b11111111111110000000000000000000, 15, 92};
+          }
+        } else {
+          if (value < 0b11111111111111110100100000000000) {
+            if (value < 0b11111111111111101110000000000000) {
+              if (value < 0b11111111111111100110000000000000) {
+                return {0b11111111111111100000000000000000, 19, 95};
+              } else {
+                return {0b11111111111111100110000000000000, 20, 98};
+              }
+            } else {
+              return {0b11111111111111101110000000000000, 21, 106};
+            }
+          } else {
+            if (value < 0b11111111111111111110101000000000) {
+              if (value < 0b11111111111111111011000000000000) {
+                return {0b11111111111111110100100000000000, 22, 119};
+              } else {
+                return {0b11111111111111111011000000000000, 23, 145};
+              }
+            } else {
+              if (value < 0b11111111111111111111101111000000) {
+                if (value < 0b11111111111111111111100000000000) {
+                  if (value < 0b11111111111111111111011000000000) {
+                    return {0b11111111111111111110101000000000, 24, 174};
+                  } else {
+                    return {0b11111111111111111111011000000000, 25, 186};
+                  }
+                } else {
+                  return {0b11111111111111111111100000000000, 26, 190};
+                }
+              } else {
+                if (value < 0b11111111111111111111111111110000) {
+                  if (value < 0b11111111111111111111111000100000) {
+                    return {0b11111111111111111111101111000000, 27, 205};
+                  } else {
+                    return {0b11111111111111111111111000100000, 28, 224};
+                  }
+                } else {
+                  return {0b11111111111111111111111111110000, 30, 253};
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+// Mapping from canonical symbol (0 to 255) to actual symbol.
+// clang-format off
+constexpr unsigned char kCanonicalToSymbol[] = {
+    '0',  '1',  '2',  'a',  'c',  'e',  'i',  'o',
+    's',  't',  0x20, '%',  '-',  '.',  '/',  '3',
+    '4',  '5',  '6',  '7',  '8',  '9',  '=',  'A',
+    '_',  'b',  'd',  'f',  'g',  'h',  'l',  'm',
+    'n',  'p',  'r',  'u',  ':',  'B',  'C',  'D',
+    'E',  'F',  'G',  'H',  'I',  'J',  'K',  'L',
+    'M',  'N',  'O',  'P',  'Q',  'R',  'S',  'T',
+    'U',  'V',  'W',  'Y',  'j',  'k',  'q',  'v',
+    'w',  'x',  'y',  'z',  '&',  '*',  ',',  ';',
+    'X',  'Z',  '!',  '\"', '(',  ')',  '?',  '\'',
+    '+',  '|',  '#',  '>',  0x00, '$',  '@',  '[',
+    ']',  '~',  '^',  '}',  '<',  '`',  '{',  '\\',
+    0xc3, 0xd0, 0x80, 0x82, 0x83, 0xa2, 0xb8, 0xc2,
+    0xe0, 0xe2, 0x99, 0xa1, 0xa7, 0xac, 0xb0, 0xb1,
+    0xb3, 0xd1, 0xd8, 0xd9, 0xe3, 0xe5, 0xe6, 0x81,
+    0x84, 0x85, 0x86, 0x88, 0x92, 0x9a, 0x9c, 0xa0,
+    0xa3, 0xa4, 0xa9, 0xaa, 0xad, 0xb2, 0xb5, 0xb9,
+    0xba, 0xbb, 0xbd, 0xbe, 0xc4, 0xc6, 0xe4, 0xe8,
+    0xe9, 0x01, 0x87, 0x89, 0x8a, 0x8b, 0x8c, 0x8d,
+    0x8f, 0x93, 0x95, 0x96, 0x97, 0x98, 0x9b, 0x9d,
+    0x9e, 0xa5, 0xa6, 0xa8, 0xae, 0xaf, 0xb4, 0xb6,
+    0xb7, 0xbc, 0xbf, 0xc5, 0xe7, 0xef, 0x09, 0x8e,
+    0x90, 0x91, 0x94, 0x9f, 0xab, 0xce, 0xd7, 0xe1,
+    0xec, 0xed, 0xc7, 0xcf, 0xea, 0xeb, 0xc0, 0xc1,
+    0xc8, 0xc9, 0xca, 0xcd, 0xd2, 0xd5, 0xda, 0xdb,
+    0xee, 0xf0, 0xf2, 0xf3, 0xff, 0xcb, 0xcc, 0xd3,
+    0xd4, 0xd6, 0xdd, 0xde, 0xdf, 0xf1, 0xf4, 0xf5,
+    0xf6, 0xf7, 0xf8, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe,
+    0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0b,
+    0x0c, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
+    0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+    0x1e, 0x1f, 0x7f, 0xdc, 0xf9, 0x0a, 0x0d, 0x16,
+};
+// clang-format on
+
+constexpr size_t kShortCodeTableSize = 124;
+struct ShortCodeInfo {
+  uint8_t symbol;
+  uint8_t length;
+} kShortCodeTable[kShortCodeTableSize] = {
+    {0x30, 5},  // Match: 0b0000000, Symbol: 0
+    {0x30, 5},  // Match: 0b0000001, Symbol: 0
+    {0x30, 5},  // Match: 0b0000010, Symbol: 0
+    {0x30, 5},  // Match: 0b0000011, Symbol: 0
+    {0x31, 5},  // Match: 0b0000100, Symbol: 1
+    {0x31, 5},  // Match: 0b0000101, Symbol: 1
+    {0x31, 5},  // Match: 0b0000110, Symbol: 1
+    {0x31, 5},  // Match: 0b0000111, Symbol: 1
+    {0x32, 5},  // Match: 0b0001000, Symbol: 2
+    {0x32, 5},  // Match: 0b0001001, Symbol: 2
+    {0x32, 5},  // Match: 0b0001010, Symbol: 2
+    {0x32, 5},  // Match: 0b0001011, Symbol: 2
+    {0x61, 5},  // Match: 0b0001100, Symbol: a
+    {0x61, 5},  // Match: 0b0001101, Symbol: a
+    {0x61, 5},  // Match: 0b0001110, Symbol: a
+    {0x61, 5},  // Match: 0b0001111, Symbol: a
+    {0x63, 5},  // Match: 0b0010000, Symbol: c
+    {0x63, 5},  // Match: 0b0010001, Symbol: c
+    {0x63, 5},  // Match: 0b0010010, Symbol: c
+    {0x63, 5},  // Match: 0b0010011, Symbol: c
+    {0x65, 5},  // Match: 0b0010100, Symbol: e
+    {0x65, 5},  // Match: 0b0010101, Symbol: e
+    {0x65, 5},  // Match: 0b0010110, Symbol: e
+    {0x65, 5},  // Match: 0b0010111, Symbol: e
+    {0x69, 5},  // Match: 0b0011000, Symbol: i
+    {0x69, 5},  // Match: 0b0011001, Symbol: i
+    {0x69, 5},  // Match: 0b0011010, Symbol: i
+    {0x69, 5},  // Match: 0b0011011, Symbol: i
+    {0x6f, 5},  // Match: 0b0011100, Symbol: o
+    {0x6f, 5},  // Match: 0b0011101, Symbol: o
+    {0x6f, 5},  // Match: 0b0011110, Symbol: o
+    {0x6f, 5},  // Match: 0b0011111, Symbol: o
+    {0x73, 5},  // Match: 0b0100000, Symbol: s
+    {0x73, 5},  // Match: 0b0100001, Symbol: s
+    {0x73, 5},  // Match: 0b0100010, Symbol: s
+    {0x73, 5},  // Match: 0b0100011, Symbol: s
+    {0x74, 5},  // Match: 0b0100100, Symbol: t
+    {0x74, 5},  // Match: 0b0100101, Symbol: t
+    {0x74, 5},  // Match: 0b0100110, Symbol: t
+    {0x74, 5},  // Match: 0b0100111, Symbol: t
+    {0x20, 6},  // Match: 0b0101000, Symbol: (space)
+    {0x20, 6},  // Match: 0b0101001, Symbol: (space)
+    {0x25, 6},  // Match: 0b0101010, Symbol: %
+    {0x25, 6},  // Match: 0b0101011, Symbol: %
+    {0x2d, 6},  // Match: 0b0101100, Symbol: -
+    {0x2d, 6},  // Match: 0b0101101, Symbol: -
+    {0x2e, 6},  // Match: 0b0101110, Symbol: .
+    {0x2e, 6},  // Match: 0b0101111, Symbol: .
+    {0x2f, 6},  // Match: 0b0110000, Symbol: /
+    {0x2f, 6},  // Match: 0b0110001, Symbol: /
+    {0x33, 6},  // Match: 0b0110010, Symbol: 3
+    {0x33, 6},  // Match: 0b0110011, Symbol: 3
+    {0x34, 6},  // Match: 0b0110100, Symbol: 4
+    {0x34, 6},  // Match: 0b0110101, Symbol: 4
+    {0x35, 6},  // Match: 0b0110110, Symbol: 5
+    {0x35, 6},  // Match: 0b0110111, Symbol: 5
+    {0x36, 6},  // Match: 0b0111000, Symbol: 6
+    {0x36, 6},  // Match: 0b0111001, Symbol: 6
+    {0x37, 6},  // Match: 0b0111010, Symbol: 7
+    {0x37, 6},  // Match: 0b0111011, Symbol: 7
+    {0x38, 6},  // Match: 0b0111100, Symbol: 8
+    {0x38, 6},  // Match: 0b0111101, Symbol: 8
+    {0x39, 6},  // Match: 0b0111110, Symbol: 9
+    {0x39, 6},  // Match: 0b0111111, Symbol: 9
+    {0x3d, 6},  // Match: 0b1000000, Symbol: =
+    {0x3d, 6},  // Match: 0b1000001, Symbol: =
+    {0x41, 6},  // Match: 0b1000010, Symbol: A
+    {0x41, 6},  // Match: 0b1000011, Symbol: A
+    {0x5f, 6},  // Match: 0b1000100, Symbol: _
+    {0x5f, 6},  // Match: 0b1000101, Symbol: _
+    {0x62, 6},  // Match: 0b1000110, Symbol: b
+    {0x62, 6},  // Match: 0b1000111, Symbol: b
+    {0x64, 6},  // Match: 0b1001000, Symbol: d
+    {0x64, 6},  // Match: 0b1001001, Symbol: d
+    {0x66, 6},  // Match: 0b1001010, Symbol: f
+    {0x66, 6},  // Match: 0b1001011, Symbol: f
+    {0x67, 6},  // Match: 0b1001100, Symbol: g
+    {0x67, 6},  // Match: 0b1001101, Symbol: g
+    {0x68, 6},  // Match: 0b1001110, Symbol: h
+    {0x68, 6},  // Match: 0b1001111, Symbol: h
+    {0x6c, 6},  // Match: 0b1010000, Symbol: l
+    {0x6c, 6},  // Match: 0b1010001, Symbol: l
+    {0x6d, 6},  // Match: 0b1010010, Symbol: m
+    {0x6d, 6},  // Match: 0b1010011, Symbol: m
+    {0x6e, 6},  // Match: 0b1010100, Symbol: n
+    {0x6e, 6},  // Match: 0b1010101, Symbol: n
+    {0x70, 6},  // Match: 0b1010110, Symbol: p
+    {0x70, 6},  // Match: 0b1010111, Symbol: p
+    {0x72, 6},  // Match: 0b1011000, Symbol: r
+    {0x72, 6},  // Match: 0b1011001, Symbol: r
+    {0x75, 6},  // Match: 0b1011010, Symbol: u
+    {0x75, 6},  // Match: 0b1011011, Symbol: u
+    {0x3a, 7},  // Match: 0b1011100, Symbol: :
+    {0x42, 7},  // Match: 0b1011101, Symbol: B
+    {0x43, 7},  // Match: 0b1011110, Symbol: C
+    {0x44, 7},  // Match: 0b1011111, Symbol: D
+    {0x45, 7},  // Match: 0b1100000, Symbol: E
+    {0x46, 7},  // Match: 0b1100001, Symbol: F
+    {0x47, 7},  // Match: 0b1100010, Symbol: G
+    {0x48, 7},  // Match: 0b1100011, Symbol: H
+    {0x49, 7},  // Match: 0b1100100, Symbol: I
+    {0x4a, 7},  // Match: 0b1100101, Symbol: J
+    {0x4b, 7},  // Match: 0b1100110, Symbol: K
+    {0x4c, 7},  // Match: 0b1100111, Symbol: L
+    {0x4d, 7},  // Match: 0b1101000, Symbol: M
+    {0x4e, 7},  // Match: 0b1101001, Symbol: N
+    {0x4f, 7},  // Match: 0b1101010, Symbol: O
+    {0x50, 7},  // Match: 0b1101011, Symbol: P
+    {0x51, 7},  // Match: 0b1101100, Symbol: Q
+    {0x52, 7},  // Match: 0b1101101, Symbol: R
+    {0x53, 7},  // Match: 0b1101110, Symbol: S
+    {0x54, 7},  // Match: 0b1101111, Symbol: T
+    {0x55, 7},  // Match: 0b1110000, Symbol: U
+    {0x56, 7},  // Match: 0b1110001, Symbol: V
+    {0x57, 7},  // Match: 0b1110010, Symbol: W
+    {0x59, 7},  // Match: 0b1110011, Symbol: Y
+    {0x6a, 7},  // Match: 0b1110100, Symbol: j
+    {0x6b, 7},  // Match: 0b1110101, Symbol: k
+    {0x71, 7},  // Match: 0b1110110, Symbol: q
+    {0x76, 7},  // Match: 0b1110111, Symbol: v
+    {0x77, 7},  // Match: 0b1111000, Symbol: w
+    {0x78, 7},  // Match: 0b1111001, Symbol: x
+    {0x79, 7},  // Match: 0b1111010, Symbol: y
+    {0x7a, 7},  // Match: 0b1111011, Symbol: z
+};
+
+}  // namespace
+
+HuffmanBitBuffer::HuffmanBitBuffer() {
+  Reset();
+}
+
+void HuffmanBitBuffer::Reset() {
+  accumulator_ = 0;
+  count_ = 0;
+}
+
+size_t HuffmanBitBuffer::AppendBytes(Http2StringPiece input) {
+  HuffmanAccumulatorBitCount free_cnt = free_count();
+  size_t bytes_available = input.size();
+  if (free_cnt < 8 || bytes_available == 0) {
+    return 0;
+  }
+
+  // Top up |accumulator_| until there isn't room for a whole byte.
+  size_t bytes_used = 0;
+  auto* ptr = reinterpret_cast<const uint8_t*>(input.data());
+  do {
+    auto b = static_cast<HuffmanAccumulator>(*ptr++);
+    free_cnt -= 8;
+    accumulator_ |= (b << free_cnt);
+    ++bytes_used;
+  } while (free_cnt >= 8 && bytes_used < bytes_available);
+  count_ += (bytes_used * 8);
+  return bytes_used;
+}
+
+HuffmanAccumulatorBitCount HuffmanBitBuffer::free_count() const {
+  return kHuffmanAccumulatorBitCount - count_;
+}
+
+void HuffmanBitBuffer::ConsumeBits(HuffmanAccumulatorBitCount code_length) {
+  DCHECK_LE(code_length, count_);
+  accumulator_ <<= code_length;
+  count_ -= code_length;
+}
+
+bool HuffmanBitBuffer::InputProperlyTerminated() const {
+  auto cnt = count();
+  if (cnt < 8) {
+    if (cnt == 0) {
+      return true;
+    }
+    HuffmanAccumulator expected = ~(~HuffmanAccumulator() >> cnt);
+    // We expect all the bits below the high order |cnt| bits of accumulator_
+    // to be cleared as we perform left shift operations while decoding.
+    DCHECK_EQ(accumulator_ & ~expected, 0u)
+        << "\n  expected: " << HuffmanAccumulatorBitSet(expected) << "\n  "
+        << *this;
+    return accumulator_ == expected;
+  }
+  return false;
+}
+
+Http2String HuffmanBitBuffer::DebugString() const {
+  std::stringstream ss;
+  ss << "{accumulator: " << HuffmanAccumulatorBitSet(accumulator_)
+     << "; count: " << count_ << "}";
+  return ss.str();
+}
+
+HpackHuffmanDecoder::HpackHuffmanDecoder() = default;
+
+HpackHuffmanDecoder::~HpackHuffmanDecoder() = default;
+
+bool HpackHuffmanDecoder::Decode(Http2StringPiece input, Http2String* output) {
+  DVLOG(1) << "HpackHuffmanDecoder::Decode";
+
+  // Fill bit_buffer_ from input.
+  input.remove_prefix(bit_buffer_.AppendBytes(input));
+
+  while (true) {
+    DVLOG(3) << "Enter Decode Loop, bit_buffer_: " << bit_buffer_;
+    if (bit_buffer_.count() >= 7) {
+      // Get high 7 bits of the bit buffer, see if that contains a complete
+      // code of 5, 6 or 7 bits.
+      uint8_t short_code =
+          bit_buffer_.value() >> (kHuffmanAccumulatorBitCount - 7);
+      DCHECK_LT(short_code, 128);
+      if (short_code < kShortCodeTableSize) {
+        ShortCodeInfo info = kShortCodeTable[short_code];
+        bit_buffer_.ConsumeBits(info.length);
+        output->push_back(static_cast<char>(info.symbol));
+        continue;
+      }
+      // The code is more than 7 bits long. Use PrefixToInfo, etc. to decode
+      // longer codes.
+    } else {
+      // We may have (mostly) drained bit_buffer_. If we can top it up, try
+      // using the table decoder above.
+      size_t byte_count = bit_buffer_.AppendBytes(input);
+      if (byte_count > 0) {
+        input.remove_prefix(byte_count);
+        continue;
+      }
+    }
+
+    HuffmanCode code_prefix = bit_buffer_.value() >> kExtraAccumulatorBitCount;
+    DVLOG(3) << "code_prefix: " << HuffmanCodeBitSet(code_prefix);
+
+    PrefixInfo prefix_info = PrefixToInfo(code_prefix);
+    DVLOG(3) << "prefix_info: " << prefix_info;
+    DCHECK_LE(kMinCodeBitCount, prefix_info.code_length);
+    DCHECK_LE(prefix_info.code_length, kMaxCodeBitCount);
+
+    if (prefix_info.code_length <= bit_buffer_.count()) {
+      // We have enough bits for one code.
+      uint32_t canonical = prefix_info.DecodeToCanonical(code_prefix);
+      if (canonical < 256) {
+        // Valid code.
+        char c = kCanonicalToSymbol[canonical];
+        output->push_back(c);
+        bit_buffer_.ConsumeBits(prefix_info.code_length);
+        continue;
+      }
+      // Encoder is not supposed to explicity encode the EOS symbol.
+      DLOG(ERROR) << "EOS explicitly encoded!\n " << bit_buffer_ << "\n "
+                  << prefix_info;
+      return false;
+    }
+    // bit_buffer_ doesn't have enough bits in it to decode the next symbol.
+    // Append to it as many bytes as are available AND fit.
+    size_t byte_count = bit_buffer_.AppendBytes(input);
+    if (byte_count == 0) {
+      DCHECK_EQ(input.size(), 0u);
+      return true;
+    }
+    input.remove_prefix(byte_count);
+  }
+}
+
+Http2String HpackHuffmanDecoder::DebugString() const {
+  return bit_buffer_.DebugString();
+}
+
+}  // namespace http2
diff --git a/http2/hpack/huffman/hpack_huffman_decoder.h b/http2/hpack/huffman/hpack_huffman_decoder.h
new file mode 100644
index 0000000..8e511d6
--- /dev/null
+++ b/http2/hpack/huffman/hpack_huffman_decoder.h
@@ -0,0 +1,134 @@
+// 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_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_
+#define QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_
+
+// HpackHuffmanDecoder is an incremental decoder of strings that have been
+// encoded using the Huffman table defined in the HPACK spec.
+// By incremental, we mean that the HpackHuffmanDecoder::Decode method does
+// not require the entire string to be provided, and can instead decode the
+// string as fragments of it become available (e.g. as HPACK block fragments
+// are received for decoding by HpackEntryDecoder).
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <iosfwd>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+// HuffmanAccumulator is used to store bits during decoding, e.g. next N bits
+// that have not yet been decoded, but have been extracted from the encoded
+// string).  An advantage of using a uint64 for the accumulator
+// is that it has room for the bits of the longest code plus the bits of a full
+// byte; that means that when adding more bits to the accumulator, it can always
+// be done in whole bytes. For example, if we currently have 26 bits in the
+// accumulator, and need more to decode the current symbol, we can add a whole
+// byte to the accumulator, and not have to do juggling with adding 6 bits (to
+// reach 30), and then keep track of the last two bits we've not been able to
+// add to the accumulator.
+typedef uint64_t HuffmanAccumulator;
+typedef size_t HuffmanAccumulatorBitCount;
+
+// HuffmanBitBuffer stores the leading edge of bits to be decoded. The high
+// order bit of accumulator_ is the next bit to be decoded.
+class HTTP2_EXPORT_PRIVATE HuffmanBitBuffer {
+ public:
+  HuffmanBitBuffer();
+
+  // Prepare for decoding a new Huffman encoded string.
+  void Reset();
+
+  // Add as many whole bytes to the accumulator (accumulator_) as possible,
+  // returning the number of bytes added.
+  size_t AppendBytes(Http2StringPiece input);
+
+  // Get the bits of the accumulator.
+  HuffmanAccumulator value() const { return accumulator_; }
+
+  // Number of bits of the encoded string that are in the accumulator
+  // (accumulator_).
+  HuffmanAccumulatorBitCount count() const { return count_; }
+
+  // Are there no bits in the accumulator?
+  bool IsEmpty() const { return count_ == 0; }
+
+  // Number of additional bits that can be added to the accumulator.
+  HuffmanAccumulatorBitCount free_count() const;
+
+  // Consume the leading |code_length| bits of the accumulator.
+  void ConsumeBits(HuffmanAccumulatorBitCount code_length);
+
+  // Are the contents valid for the end of a Huffman encoded string? The RFC
+  // states that EOS (end-of-string) symbol must not be explicitly encoded in
+  // the bit stream, but any unused bits in the final byte must be set to the
+  // prefix of the EOS symbol, which is all 1 bits. So there can be at most 7
+  // such bits.
+  // Returns true if the bit buffer is empty, or contains at most 7 bits, all
+  // of them 1. Otherwise returns false.
+  bool InputProperlyTerminated() const;
+
+  Http2String DebugString() const;
+
+ private:
+  HuffmanAccumulator accumulator_;
+  HuffmanAccumulatorBitCount count_;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const HuffmanBitBuffer& v) {
+  return out << v.DebugString();
+}
+
+class HTTP2_EXPORT_PRIVATE HpackHuffmanDecoder {
+ public:
+  HpackHuffmanDecoder();
+  ~HpackHuffmanDecoder();
+
+  // Prepare for decoding a new Huffman encoded string.
+  void Reset() { bit_buffer_.Reset(); }
+
+  // Decode the portion of a HPACK Huffman encoded string that is in |input|,
+  // appending the decoded symbols into |*output|, stopping when more bits are
+  // needed to determine the next symbol, which/ means that the input has been
+  // drained, and also that the bit_buffer_ is empty or that the bits that are
+  // in it are not a whole symbol.
+  // If |input| is the start of a string, the caller must first call Reset.
+  // If |input| includes the end of the encoded string, the caller must call
+  // InputProperlyTerminated after Decode has returned true in order to
+  // determine if the encoded string was properly terminated.
+  // Returns false if something went wrong (e.g. the encoding contains the code
+  // EOS symbol). Otherwise returns true, in which case input has been fully
+  // decoded or buffered; in particular, if the low-order bit of the final byte
+  // of the input is not the last bit of an encoded symbol, then bit_buffer_
+  // will contain the leading bits of the code for that symbol, but not the
+  // final bits of that code.
+  // Note that output should be empty, but that it is not cleared by Decode().
+  bool Decode(Http2StringPiece input, Http2String* output);
+
+  // Is what remains in the bit_buffer_ valid at the end of an encoded string?
+  // Call after passing the the final portion of a Huffman string to Decode,
+  // and getting true as the result.
+  bool InputProperlyTerminated() const {
+    return bit_buffer_.InputProperlyTerminated();
+  }
+
+  Http2String DebugString() const;
+
+ private:
+  HuffmanBitBuffer bit_buffer_;
+};
+
+inline std::ostream& operator<<(std::ostream& out,
+                                const HpackHuffmanDecoder& v) {
+  return out << v.DebugString();
+}
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_
diff --git a/http2/hpack/huffman/hpack_huffman_decoder_test.cc b/http2/hpack/huffman/hpack_huffman_decoder_test.cc
new file mode 100644
index 0000000..6c07ca5
--- /dev/null
+++ b/http2/hpack/huffman/hpack_huffman_decoder_test.cc
@@ -0,0 +1,245 @@
+// 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 "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+
+// Tests of HpackHuffmanDecoder and HuffmanBitBuffer.
+
+#include <iostream>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+TEST(HuffmanBitBufferTest, Reset) {
+  HuffmanBitBuffer bb;
+  EXPECT_TRUE(bb.IsEmpty());
+  EXPECT_TRUE(bb.InputProperlyTerminated());
+  EXPECT_EQ(bb.count(), 0u);
+  EXPECT_EQ(bb.free_count(), 64u);
+  EXPECT_EQ(bb.value(), 0u);
+}
+
+TEST(HuffmanBitBufferTest, AppendBytesAligned) {
+  Http2String s;
+  s.push_back('\x11');
+  s.push_back('\x22');
+  s.push_back('\x33');
+  Http2StringPiece sp(s);
+
+  HuffmanBitBuffer bb;
+  sp.remove_prefix(bb.AppendBytes(sp));
+  EXPECT_TRUE(sp.empty());
+  EXPECT_FALSE(bb.IsEmpty()) << bb;
+  EXPECT_FALSE(bb.InputProperlyTerminated());
+  EXPECT_EQ(bb.count(), 24u) << bb;
+  EXPECT_EQ(bb.free_count(), 40u) << bb;
+  EXPECT_EQ(bb.value(), HuffmanAccumulator(0x112233) << 40) << bb;
+
+  s.clear();
+  s.push_back('\x44');
+  sp = s;
+
+  sp.remove_prefix(bb.AppendBytes(sp));
+  EXPECT_TRUE(sp.empty());
+  EXPECT_EQ(bb.count(), 32u) << bb;
+  EXPECT_EQ(bb.free_count(), 32u) << bb;
+  EXPECT_EQ(bb.value(), HuffmanAccumulator(0x11223344) << 32) << bb;
+
+  s.clear();
+  s.push_back('\x55');
+  s.push_back('\x66');
+  s.push_back('\x77');
+  s.push_back('\x88');
+  s.push_back('\x99');
+  sp = s;
+
+  sp.remove_prefix(bb.AppendBytes(sp));
+  EXPECT_EQ(sp.size(), 1u);
+  EXPECT_EQ('\x99', sp[0]);
+  EXPECT_EQ(bb.count(), 64u) << bb;
+  EXPECT_EQ(bb.free_count(), 0u) << bb;
+  EXPECT_EQ(bb.value(), HuffmanAccumulator(0x1122334455667788LL)) << bb;
+
+  sp.remove_prefix(bb.AppendBytes(sp));
+  EXPECT_EQ(sp.size(), 1u);
+  EXPECT_EQ('\x99', sp[0]);
+  EXPECT_EQ(bb.count(), 64u) << bb;
+  EXPECT_EQ(bb.free_count(), 0u) << bb;
+  EXPECT_EQ(bb.value(), HuffmanAccumulator(0x1122334455667788LL)) << bb;
+}
+
+TEST(HuffmanBitBufferTest, ConsumeBits) {
+  Http2String s;
+  s.push_back('\x11');
+  s.push_back('\x22');
+  s.push_back('\x33');
+  Http2StringPiece sp(s);
+
+  HuffmanBitBuffer bb;
+  sp.remove_prefix(bb.AppendBytes(sp));
+  EXPECT_TRUE(sp.empty());
+
+  bb.ConsumeBits(1);
+  EXPECT_EQ(bb.count(), 23u) << bb;
+  EXPECT_EQ(bb.free_count(), 41u) << bb;
+  EXPECT_EQ(bb.value(), HuffmanAccumulator(0x112233) << 41) << bb;
+
+  bb.ConsumeBits(20);
+  EXPECT_EQ(bb.count(), 3u) << bb;
+  EXPECT_EQ(bb.free_count(), 61u) << bb;
+  EXPECT_EQ(bb.value(), HuffmanAccumulator(0x3) << 61) << bb;
+}
+
+TEST(HuffmanBitBufferTest, AppendBytesUnaligned) {
+  Http2String s;
+  s.push_back('\x11');
+  s.push_back('\x22');
+  s.push_back('\x33');
+  s.push_back('\x44');
+  s.push_back('\x55');
+  s.push_back('\x66');
+  s.push_back('\x77');
+  s.push_back('\x88');
+  s.push_back('\x99');
+  s.push_back('\xaa');
+  s.push_back('\xbb');
+  s.push_back('\xcc');
+  s.push_back('\xdd');
+  Http2StringPiece sp(s);
+
+  HuffmanBitBuffer bb;
+  sp.remove_prefix(bb.AppendBytes(sp));
+  EXPECT_EQ(sp.size(), 5u);
+  EXPECT_FALSE(bb.InputProperlyTerminated());
+
+  bb.ConsumeBits(15);
+  EXPECT_EQ(bb.count(), 49u) << bb;
+  EXPECT_EQ(bb.free_count(), 15u) << bb;
+
+  HuffmanAccumulator expected(0x1122334455667788);
+  expected <<= 15;
+  EXPECT_EQ(bb.value(), expected);
+
+  sp.remove_prefix(bb.AppendBytes(sp));
+  EXPECT_EQ(sp.size(), 4u);
+  EXPECT_EQ(bb.count(), 57u) << bb;
+  EXPECT_EQ(bb.free_count(), 7u) << bb;
+
+  expected |= (HuffmanAccumulator(0x99) << 7);
+  EXPECT_EQ(bb.value(), expected)
+      << bb << std::hex << "\n   actual: " << bb.value()
+      << "\n expected: " << expected;
+}
+
+class HpackHuffmanDecoderTest : public RandomDecoderTest {
+ protected:
+  HpackHuffmanDecoderTest() {
+    // The decoder may return true, and its accumulator may be empty, at
+    // many boundaries while decoding, and yet the whole string hasn't
+    // been decoded.
+    stop_decode_on_done_ = false;
+  }
+
+  DecodeStatus StartDecoding(DecodeBuffer* b) override {
+    input_bytes_seen_ = 0;
+    output_buffer_.clear();
+    decoder_.Reset();
+    return ResumeDecoding(b);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+    input_bytes_seen_ += b->Remaining();
+    Http2StringPiece sp(b->cursor(), b->Remaining());
+    if (decoder_.Decode(sp, &output_buffer_)) {
+      b->AdvanceCursor(b->Remaining());
+      // Successfully decoded (or buffered) the bytes in Http2StringPiece.
+      EXPECT_LE(input_bytes_seen_, input_bytes_expected_);
+      // Have we reached the end of the encoded string?
+      if (input_bytes_expected_ == input_bytes_seen_) {
+        if (decoder_.InputProperlyTerminated()) {
+          return DecodeStatus::kDecodeDone;
+        } else {
+          return DecodeStatus::kDecodeError;
+        }
+      }
+      return DecodeStatus::kDecodeInProgress;
+    }
+    return DecodeStatus::kDecodeError;
+  }
+
+  HpackHuffmanDecoder decoder_;
+  Http2String output_buffer_;
+  size_t input_bytes_seen_;
+  size_t input_bytes_expected_;
+};
+
+TEST_F(HpackHuffmanDecoderTest, SpecRequestExamples) {
+  HpackHuffmanDecoder decoder;
+  Http2String test_table[] = {
+      Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"),
+      "www.example.com",
+      Http2HexDecode("a8eb10649cbf"),
+      "no-cache",
+      Http2HexDecode("25a849e95ba97d7f"),
+      "custom-key",
+      Http2HexDecode("25a849e95bb8e8b4bf"),
+      "custom-value",
+  };
+  for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) {
+    const Http2String& huffman_encoded(test_table[i]);
+    const Http2String& plain_string(test_table[i + 1]);
+    Http2String buffer;
+    decoder.Reset();
+    EXPECT_TRUE(decoder.Decode(huffman_encoded, &buffer)) << decoder;
+    EXPECT_TRUE(decoder.InputProperlyTerminated()) << decoder;
+    EXPECT_EQ(buffer, plain_string);
+  }
+}
+
+TEST_F(HpackHuffmanDecoderTest, SpecResponseExamples) {
+  HpackHuffmanDecoder decoder;
+  // clang-format off
+  Http2String test_table[] = {
+    Http2HexDecode("6402"),
+    "302",
+    Http2HexDecode("aec3771a4b"),
+    "private",
+    Http2HexDecode("d07abe941054d444a8200595040b8166"
+            "e082a62d1bff"),
+    "Mon, 21 Oct 2013 20:13:21 GMT",
+    Http2HexDecode("9d29ad171863c78f0b97c8e9ae82ae43"
+            "d3"),
+    "https://www.example.com",
+    Http2HexDecode("94e7821dd7f2e6c7b335dfdfcd5b3960"
+            "d5af27087f3672c1ab270fb5291f9587"
+            "316065c003ed4ee5b1063d5007"),
+    "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+  };
+  // clang-format on
+  for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) {
+    const Http2String& huffman_encoded(test_table[i]);
+    const Http2String& plain_string(test_table[i + 1]);
+    Http2String buffer;
+    decoder.Reset();
+    EXPECT_TRUE(decoder.Decode(huffman_encoded, &buffer)) << decoder;
+    EXPECT_TRUE(decoder.InputProperlyTerminated()) << decoder;
+    EXPECT_EQ(buffer, plain_string);
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/huffman/hpack_huffman_encoder.cc b/http2/hpack/huffman/hpack_huffman_encoder.cc
new file mode 100644
index 0000000..9f561ee
--- /dev/null
+++ b/http2/hpack/huffman/hpack_huffman_encoder.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h"
+
+// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature.
+
+namespace http2 {
+
+size_t ExactHuffmanSize(Http2StringPiece plain) {
+  size_t bits = 0;
+  for (const uint8_t c : plain) {
+    bits += HuffmanSpecTables::kCodeLengths[c];
+  }
+  return (bits + 7) / 8;
+}
+
+size_t BoundedHuffmanSize(Http2StringPiece plain) {
+  // TODO(jamessynge): Determine whether we should set the min size for Huffman
+  // encoding much higher (i.e. if less than N, then the savings isn't worth
+  // the cost of encoding and decoding). Of course, we need to decide on a
+  // value function, which might be throughput on a full load test, or a
+  // microbenchmark of the time to encode and then decode a HEADERS frame,
+  // possibly with the cost of crypto included (i.e. crypto is going to have
+  // a fairly constant per-byte cost, so reducing the number of bytes in-transit
+  // reduces the number that must be encrypted and later decrypted).
+  if (plain.size() < 3) {
+    // Huffman encoded string can't be smaller than the plain size for very
+    // short strings.
+    return plain.size();
+  }
+  // TODO(jamessynge): Measure whether this can be done more efficiently with
+  // nested loops (e.g. make exact measurement of 8 bytes, then check if min
+  // remaining is too long).
+  // Compute the number of bits in an encoding that is shorter than the plain
+  // string (i.e. the number of bits in a string 1 byte shorter than plain),
+  // and use this as the limit of the size of the encoding.
+  const size_t limit_bits = (plain.size() - 1) * 8;
+  // The shortest code length in the Huffman table of the HPACK spec has 5 bits
+  // (e.g. for 0, 1, a and e).
+  const size_t min_code_length = 5;
+  // We can therefore say that all plain text bytes whose code length we've not
+  // yet looked up will take at least 5 bits.
+  size_t min_bits_remaining = plain.size() * min_code_length;
+  size_t bits = 0;
+  for (const uint8_t c : plain) {
+    bits += HuffmanSpecTables::kCodeLengths[c];
+    min_bits_remaining -= min_code_length;
+    // If our minimum estimate of the total number of bits won't yield an
+    // encoding shorter the plain text, let's bail.
+    const size_t minimum_bits_total = bits + min_bits_remaining;
+    if (minimum_bits_total > limit_bits) {
+      bits += min_bits_remaining;
+      break;
+    }
+  }
+  return (bits + 7) / 8;
+}
+
+void HuffmanEncode(Http2StringPiece plain, Http2String* huffman) {
+  DCHECK(huffman != nullptr);
+  huffman->clear();         // Note that this doesn't release memory.
+  uint64_t bit_buffer = 0;  // High-bit is next bit to output. Not clear if that
+                            // is more performant than having the low-bit be the
+                            // last to be output.
+  size_t bits_unused = 64;  // Number of bits available for the next code.
+  for (uint8_t c : plain) {
+    size_t code_length = HuffmanSpecTables::kCodeLengths[c];
+    if (bits_unused < code_length) {
+      // There isn't enough room in bit_buffer for the code of c.
+      // Flush until bits_unused > 56 (i.e. 64 - 8).
+      do {
+        char h = static_cast<char>(bit_buffer >> 56);
+        bit_buffer <<= 8;
+        bits_unused += 8;
+        // Perhaps would be more efficient if we populated an array of chars,
+        // so we don't have to call push_back each time. Reconsider if used
+        // for production.
+        huffman->push_back(h);
+      } while (bits_unused <= 56);
+    }
+    uint64_t code = HuffmanSpecTables::kRightCodes[c];
+    size_t shift_by = bits_unused - code_length;
+    bit_buffer |= (code << shift_by);
+    bits_unused -= code_length;
+  }
+  // bit_buffer contains (64-bits_unused) bits that still need to be flushed.
+  // Output whole bytes until we don't have any whole bytes left.
+  size_t bits_used = 64 - bits_unused;
+  while (bits_used >= 8) {
+    char h = static_cast<char>(bit_buffer >> 56);
+    bit_buffer <<= 8;
+    bits_used -= 8;
+    huffman->push_back(h);
+  }
+  if (bits_used > 0) {
+    // We have less than a byte left to output. The spec calls for padding out
+    // the final byte with the leading bits of the EOS symbol (30 1-bits).
+    constexpr uint64_t leading_eos_bits = 0b11111111;
+    bit_buffer |= (leading_eos_bits << (56 - bits_used));
+    char h = static_cast<char>(bit_buffer >> 56);
+    huffman->push_back(h);
+  }
+}
+
+}  // namespace http2
diff --git a/http2/hpack/huffman/hpack_huffman_encoder.h b/http2/hpack/huffman/hpack_huffman_encoder.h
new file mode 100644
index 0000000..bf6b1b2
--- /dev/null
+++ b/http2/hpack/huffman/hpack_huffman_encoder.h
@@ -0,0 +1,40 @@
+// 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_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_
+#define QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_
+
+// Functions supporting the encoding of strings using the HPACK-defined Huffman
+// table.
+
+#include <cstddef>  // For size_t
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+// Returns the size of the Huffman encoding of |plain|, which may be greater
+// than plain.size(). Mostly present for testing.
+HTTP2_EXPORT_PRIVATE size_t ExactHuffmanSize(Http2StringPiece plain);
+
+// Returns the size of the Huffman encoding of |plain|, unless it is greater
+// than or equal to plain.size(), in which case a value greater than or equal to
+// plain.size() is returned. The advantage of this over ExactHuffmanSize is that
+// it doesn't read as much of the input string in the event that the string is
+// not compressible by HuffmanEncode (i.e. when the encoding is longer than the
+// original string, it stops reading the input string as soon as it knows that).
+HTTP2_EXPORT_PRIVATE size_t BoundedHuffmanSize(Http2StringPiece plain);
+
+// Encode the plain text string |plain| with the Huffman encoding defined in
+// the HPACK RFC, 7541.  |*huffman| does not have to be empty, it is cleared at
+// the beginning of this function.  This allows reusing the same string object
+// across multiple invocations.
+HTTP2_EXPORT_PRIVATE void HuffmanEncode(Http2StringPiece plain,
+                                        Http2String* huffman);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_
diff --git a/http2/hpack/huffman/hpack_huffman_encoder_test.cc b/http2/hpack/huffman/hpack_huffman_encoder_test.cc
new file mode 100644
index 0000000..ccb6983
--- /dev/null
+++ b/http2/hpack/huffman/hpack_huffman_encoder_test.cc
@@ -0,0 +1,97 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h"
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+namespace {
+
+TEST(HuffmanEncoderTest, SpecRequestExamples) {
+  Http2String test_table[] = {
+      Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"),
+      "www.example.com",
+      Http2HexDecode("a8eb10649cbf"),
+      "no-cache",
+      Http2HexDecode("25a849e95ba97d7f"),
+      "custom-key",
+      Http2HexDecode("25a849e95bb8e8b4bf"),
+      "custom-value",
+  };
+  for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) {
+    const Http2String& huffman_encoded(test_table[i]);
+    const Http2String& plain_string(test_table[i + 1]);
+    EXPECT_EQ(ExactHuffmanSize(plain_string), huffman_encoded.size());
+    EXPECT_EQ(BoundedHuffmanSize(plain_string), huffman_encoded.size());
+    Http2String buffer;
+    buffer.reserve();
+    HuffmanEncode(plain_string, &buffer);
+    EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string;
+  }
+}
+
+TEST(HuffmanEncoderTest, SpecResponseExamples) {
+  // clang-format off
+  Http2String test_table[] = {
+    Http2HexDecode("6402"),
+    "302",
+    Http2HexDecode("aec3771a4b"),
+    "private",
+    Http2HexDecode("d07abe941054d444a8200595040b8166"
+            "e082a62d1bff"),
+    "Mon, 21 Oct 2013 20:13:21 GMT",
+    Http2HexDecode("9d29ad171863c78f0b97c8e9ae82ae43"
+            "d3"),
+    "https://www.example.com",
+    Http2HexDecode("94e7821dd7f2e6c7b335dfdfcd5b3960"
+            "d5af27087f3672c1ab270fb5291f9587"
+            "316065c003ed4ee5b1063d5007"),
+    "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+  };
+  // clang-format on
+  for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) {
+    const Http2String& huffman_encoded(test_table[i]);
+    const Http2String& plain_string(test_table[i + 1]);
+    EXPECT_EQ(ExactHuffmanSize(plain_string), huffman_encoded.size());
+    EXPECT_EQ(BoundedHuffmanSize(plain_string), huffman_encoded.size());
+    Http2String buffer;
+    buffer.reserve(huffman_encoded.size());
+    const size_t capacity = buffer.capacity();
+    HuffmanEncode(plain_string, &buffer);
+    EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string;
+    EXPECT_EQ(capacity, buffer.capacity());
+  }
+}
+
+TEST(HuffmanEncoderTest, EncodedSizeAgreesWithEncodeString) {
+  Http2String test_table[] = {
+      "",
+      "Mon, 21 Oct 2013 20:13:21 GMT",
+      "https://www.example.com",
+      "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+      Http2String(1, '\0'),
+      Http2String("foo\0bar", 7),
+      Http2String(256, '\0'),
+  };
+  // Modify last |test_table| entry to cover all codes.
+  for (size_t i = 0; i != 256; ++i) {
+    test_table[HTTP2_ARRAYSIZE(test_table) - 1][i] = static_cast<char>(i);
+  }
+
+  for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); ++i) {
+    const Http2String& plain_string = test_table[i];
+    Http2String huffman_encoded;
+    HuffmanEncode(plain_string, &huffman_encoded);
+    EXPECT_EQ(huffman_encoded.size(), ExactHuffmanSize(plain_string));
+    EXPECT_LE(BoundedHuffmanSize(plain_string), plain_string.size());
+    EXPECT_LE(BoundedHuffmanSize(plain_string), ExactHuffmanSize(plain_string));
+  }
+}
+
+}  // namespace
+}  // namespace http2
diff --git a/http2/hpack/huffman/hpack_huffman_transcoder_test.cc b/http2/hpack/huffman/hpack_huffman_transcoder_test.cc
new file mode 100644
index 0000000..cfd73b5
--- /dev/null
+++ b/http2/hpack/huffman/hpack_huffman_transcoder_test.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 2018 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.
+
+// A test of roundtrips through the encoder and decoder.
+
+#include <stddef.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::tuple;
+
+namespace http2 {
+namespace test {
+namespace {
+
+Http2String GenAsciiNonControlSet() {
+  Http2String s;
+  const char space = ' ';  // First character after the control characters: 0x20
+  const char del = 127;    // First character after the non-control characters.
+  for (char c = space; c < del; ++c) {
+    s.push_back(c);
+  }
+  return s;
+}
+
+class HpackHuffmanTranscoderTest : public RandomDecoderTest {
+ protected:
+  HpackHuffmanTranscoderTest()
+      : ascii_non_control_set_(GenAsciiNonControlSet()) {
+    // The decoder may return true, and its accumulator may be empty, at
+    // many boundaries while decoding, and yet the whole string hasn't
+    // been decoded.
+    stop_decode_on_done_ = false;
+  }
+
+  DecodeStatus StartDecoding(DecodeBuffer* b) override {
+    input_bytes_seen_ = 0;
+    output_buffer_.clear();
+    decoder_.Reset();
+    return ResumeDecoding(b);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+    input_bytes_seen_ += b->Remaining();
+    Http2StringPiece sp(b->cursor(), b->Remaining());
+    if (decoder_.Decode(sp, &output_buffer_)) {
+      b->AdvanceCursor(b->Remaining());
+      // Successfully decoded (or buffered) the bytes in Http2StringPiece.
+      EXPECT_LE(input_bytes_seen_, input_bytes_expected_);
+      // Have we reached the end of the encoded string?
+      if (input_bytes_expected_ == input_bytes_seen_) {
+        if (decoder_.InputProperlyTerminated()) {
+          return DecodeStatus::kDecodeDone;
+        } else {
+          return DecodeStatus::kDecodeError;
+        }
+      }
+      return DecodeStatus::kDecodeInProgress;
+    }
+    return DecodeStatus::kDecodeError;
+  }
+
+  AssertionResult TranscodeAndValidateSeveralWays(
+      Http2StringPiece plain,
+      Http2StringPiece expected_huffman) {
+    Http2String encoded;
+    HuffmanEncode(plain, &encoded);
+    if (expected_huffman.size() > 0 || plain.empty()) {
+      VERIFY_EQ(encoded, expected_huffman);
+    }
+    input_bytes_expected_ = encoded.size();
+    auto validator = [plain, this]() -> AssertionResult {
+      VERIFY_EQ(output_buffer_.size(), plain.size());
+      VERIFY_EQ(output_buffer_, plain);
+      return AssertionSuccess();
+    };
+    DecodeBuffer db(encoded);
+    bool return_non_zero_on_first = false;
+    return DecodeAndValidateSeveralWays(&db, return_non_zero_on_first,
+                                        ValidateDoneAndEmpty(validator));
+  }
+
+  AssertionResult TranscodeAndValidateSeveralWays(Http2StringPiece plain) {
+    return TranscodeAndValidateSeveralWays(plain, "");
+  }
+
+  Http2String RandomAsciiNonControlString(int length) {
+    return Random().RandStringWithAlphabet(length, ascii_non_control_set_);
+  }
+
+  Http2String RandomBytes(int length) { return Random().RandString(length); }
+
+  const Http2String ascii_non_control_set_;
+  HpackHuffmanDecoder decoder_;
+  Http2String output_buffer_;
+  size_t input_bytes_seen_;
+  size_t input_bytes_expected_;
+};
+
+TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomAsciiNonControlString) {
+  for (size_t length = 0; length != 20; length++) {
+    const Http2String s = RandomAsciiNonControlString(length);
+    ASSERT_TRUE(TranscodeAndValidateSeveralWays(s))
+        << "Unable to decode:\n\n"
+        << Http2HexDump(s) << "\n\noutput_buffer_:\n"
+        << Http2HexDump(output_buffer_);
+  }
+}
+
+TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomBytes) {
+  for (size_t length = 0; length != 20; length++) {
+    const Http2String s = RandomBytes(length);
+    ASSERT_TRUE(TranscodeAndValidateSeveralWays(s))
+        << "Unable to decode:\n\n"
+        << Http2HexDump(s) << "\n\noutput_buffer_:\n"
+        << Http2HexDump(output_buffer_);
+  }
+}
+
+// Two parameters: decoder choice, and the character to round-trip.
+class HpackHuffmanTranscoderAdjacentCharTest
+    : public HpackHuffmanTranscoderTest,
+      public ::testing::WithParamInterface<int> {
+ protected:
+  HpackHuffmanTranscoderAdjacentCharTest()
+      : c_(static_cast<char>(GetParam())) {}
+
+  const char c_;
+};
+
+INSTANTIATE_TEST_CASE_P(HpackHuffmanTranscoderAdjacentCharTest,
+                        HpackHuffmanTranscoderAdjacentCharTest,
+                        ::testing::Range(0, 256));
+
+// Test c_ adjacent to every other character, both before and after.
+TEST_P(HpackHuffmanTranscoderAdjacentCharTest, RoundTripAdjacentChar) {
+  Http2String s;
+  for (int a = 0; a < 256; ++a) {
+    s.push_back(static_cast<char>(a));
+    s.push_back(c_);
+    s.push_back(static_cast<char>(a));
+  }
+  ASSERT_TRUE(TranscodeAndValidateSeveralWays(s));
+}
+
+// Two parameters: character to repeat, number of repeats.
+class HpackHuffmanTranscoderRepeatedCharTest
+    : public HpackHuffmanTranscoderTest,
+      public ::testing::WithParamInterface<tuple<int, int>> {
+ protected:
+  HpackHuffmanTranscoderRepeatedCharTest()
+      : c_(static_cast<char>(::testing::get<0>(GetParam()))),
+        length_(::testing::get<1>(GetParam())) {}
+  Http2String MakeString() { return Http2String(length_, c_); }
+
+ private:
+  const char c_;
+  const size_t length_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+    HpackHuffmanTranscoderRepeatedCharTest,
+    HpackHuffmanTranscoderRepeatedCharTest,
+    ::testing::Combine(::testing::Range(0, 256),
+                       ::testing::Values(1, 2, 3, 4, 8, 16, 32)));
+
+TEST_P(HpackHuffmanTranscoderRepeatedCharTest, RoundTripRepeatedChar) {
+  ASSERT_TRUE(TranscodeAndValidateSeveralWays(MakeString()));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/huffman/huffman_spec_tables.cc b/http2/hpack/huffman/huffman_spec_tables.cc
new file mode 100644
index 0000000..27707b9
--- /dev/null
+++ b/http2/hpack/huffman/huffman_spec_tables.cc
@@ -0,0 +1,578 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h"
+
+namespace http2 {
+
+// clang-format off
+// static
+const uint8_t HuffmanSpecTables::kCodeLengths[] = {
+    13, 23, 28, 28, 28, 28, 28, 28,  //   0 -   7
+    28, 24, 30, 28, 28, 30, 28, 28,  //   8 -  15
+    28, 28, 28, 28, 28, 28, 30, 28,  //  16 -  23
+    28, 28, 28, 28, 28, 28, 28, 28,  //  24 -  31
+     6, 10, 10, 12, 13,  6,  8, 11,  //  32 -  39
+    10, 10,  8, 11,  8,  6,  6,  6,  //  40 -  47
+     5,  5,  5,  6,  6,  6,  6,  6,  //  48 -  55
+     6,  6,  7,  8, 15,  6, 12, 10,  //  56 -  63
+    13,  6,  7,  7,  7,  7,  7,  7,  //  64 -  71
+     7,  7,  7,  7,  7,  7,  7,  7,  //  72 -  79
+     7,  7,  7,  7,  7,  7,  7,  7,  //  80 -  87
+     8,  7,  8, 13, 19, 13, 14,  6,  //  88 -  95
+    15,  5,  6,  5,  6,  5,  6,  6,  //  96 - 103
+     6,  5,  7,  7,  6,  6,  6,  5,  // 104 - 111
+     6,  7,  6,  5,  5,  6,  7,  7,  // 112 - 119
+     7,  7,  7, 15, 11, 14, 13, 28,  // 120 - 127
+    20, 22, 20, 20, 22, 22, 22, 23,  // 128 - 135
+    22, 23, 23, 23, 23, 23, 24, 23,  // 136 - 143
+    24, 24, 22, 23, 24, 23, 23, 23,  // 144 - 151
+    23, 21, 22, 23, 22, 23, 23, 24,  // 152 - 159
+    22, 21, 20, 22, 22, 23, 23, 21,  // 160 - 167
+    23, 22, 22, 24, 21, 22, 23, 23,  // 168 - 175
+    21, 21, 22, 21, 23, 22, 23, 23,  // 176 - 183
+    20, 22, 22, 22, 23, 22, 22, 23,  // 184 - 191
+    26, 26, 20, 19, 22, 23, 22, 25,  // 192 - 199
+    26, 26, 26, 27, 27, 26, 24, 25,  // 200 - 207
+    19, 21, 26, 27, 27, 26, 27, 24,  // 208 - 215
+    21, 21, 26, 26, 28, 27, 27, 27,  // 216 - 223
+    20, 24, 20, 21, 22, 21, 21, 23,  // 224 - 231
+    22, 22, 25, 25, 24, 24, 26, 23,  // 232 - 239
+    26, 27, 26, 26, 27, 27, 27, 27,  // 240 - 247
+    27, 28, 27, 27, 27, 27, 27, 26,  // 248 - 255
+    30,                              // 256
+};
+
+// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature.
+
+// Uncomment these codes if needed for generating Huffman output, as opposed
+// to decoding Huffman input.
+/*
+// The encoding of each symbol, left justified (as printed), which means that
+// the first bit of the encoding is the high-order bit of the uint32.
+// static
+const uint32_t HuffmanSpecTables::kLeftCodes[] = {
+    0b11111111110000000000000000000000,  // 0x00
+    0b11111111111111111011000000000000,  // 0x01
+    0b11111111111111111111111000100000,  // 0x02
+    0b11111111111111111111111000110000,  // 0x03
+    0b11111111111111111111111001000000,  // 0x04
+    0b11111111111111111111111001010000,  // 0x05
+    0b11111111111111111111111001100000,  // 0x06
+    0b11111111111111111111111001110000,  // 0x07
+    0b11111111111111111111111010000000,  // 0x08
+    0b11111111111111111110101000000000,  // 0x09
+    0b11111111111111111111111111110000,  // 0x0a
+    0b11111111111111111111111010010000,  // 0x0b
+    0b11111111111111111111111010100000,  // 0x0c
+    0b11111111111111111111111111110100,  // 0x0d
+    0b11111111111111111111111010110000,  // 0x0e
+    0b11111111111111111111111011000000,  // 0x0f
+    0b11111111111111111111111011010000,  // 0x10
+    0b11111111111111111111111011100000,  // 0x11
+    0b11111111111111111111111011110000,  // 0x12
+    0b11111111111111111111111100000000,  // 0x13
+    0b11111111111111111111111100010000,  // 0x14
+    0b11111111111111111111111100100000,  // 0x15
+    0b11111111111111111111111111111000,  // 0x16
+    0b11111111111111111111111100110000,  // 0x17
+    0b11111111111111111111111101000000,  // 0x18
+    0b11111111111111111111111101010000,  // 0x19
+    0b11111111111111111111111101100000,  // 0x1a
+    0b11111111111111111111111101110000,  // 0x1b
+    0b11111111111111111111111110000000,  // 0x1c
+    0b11111111111111111111111110010000,  // 0x1d
+    0b11111111111111111111111110100000,  // 0x1e
+    0b11111111111111111111111110110000,  // 0x1f
+    0b01010000000000000000000000000000,  // 0x20
+    0b11111110000000000000000000000000,  // '!'
+    0b11111110010000000000000000000000,  // '\"'
+    0b11111111101000000000000000000000,  // '#'
+    0b11111111110010000000000000000000,  // '$'
+    0b01010100000000000000000000000000,  // '%'
+    0b11111000000000000000000000000000,  // '&'
+    0b11111111010000000000000000000000,  // '\''
+    0b11111110100000000000000000000000,  // '('
+    0b11111110110000000000000000000000,  // ')'
+    0b11111001000000000000000000000000,  // '*'
+    0b11111111011000000000000000000000,  // '+'
+    0b11111010000000000000000000000000,  // ','
+    0b01011000000000000000000000000000,  // '-'
+    0b01011100000000000000000000000000,  // '.'
+    0b01100000000000000000000000000000,  // '/'
+    0b00000000000000000000000000000000,  // '0'
+    0b00001000000000000000000000000000,  // '1'
+    0b00010000000000000000000000000000,  // '2'
+    0b01100100000000000000000000000000,  // '3'
+    0b01101000000000000000000000000000,  // '4'
+    0b01101100000000000000000000000000,  // '5'
+    0b01110000000000000000000000000000,  // '6'
+    0b01110100000000000000000000000000,  // '7'
+    0b01111000000000000000000000000000,  // '8'
+    0b01111100000000000000000000000000,  // '9'
+    0b10111000000000000000000000000000,  // ':'
+    0b11111011000000000000000000000000,  // ';'
+    0b11111111111110000000000000000000,  // '<'
+    0b10000000000000000000000000000000,  // '='
+    0b11111111101100000000000000000000,  // '>'
+    0b11111111000000000000000000000000,  // '?'
+    0b11111111110100000000000000000000,  // '@'
+    0b10000100000000000000000000000000,  // 'A'
+    0b10111010000000000000000000000000,  // 'B'
+    0b10111100000000000000000000000000,  // 'C'
+    0b10111110000000000000000000000000,  // 'D'
+    0b11000000000000000000000000000000,  // 'E'
+    0b11000010000000000000000000000000,  // 'F'
+    0b11000100000000000000000000000000,  // 'G'
+    0b11000110000000000000000000000000,  // 'H'
+    0b11001000000000000000000000000000,  // 'I'
+    0b11001010000000000000000000000000,  // 'J'
+    0b11001100000000000000000000000000,  // 'K'
+    0b11001110000000000000000000000000,  // 'L'
+    0b11010000000000000000000000000000,  // 'M'
+    0b11010010000000000000000000000000,  // 'N'
+    0b11010100000000000000000000000000,  // 'O'
+    0b11010110000000000000000000000000,  // 'P'
+    0b11011000000000000000000000000000,  // 'Q'
+    0b11011010000000000000000000000000,  // 'R'
+    0b11011100000000000000000000000000,  // 'S'
+    0b11011110000000000000000000000000,  // 'T'
+    0b11100000000000000000000000000000,  // 'U'
+    0b11100010000000000000000000000000,  // 'V'
+    0b11100100000000000000000000000000,  // 'W'
+    0b11111100000000000000000000000000,  // 'X'
+    0b11100110000000000000000000000000,  // 'Y'
+    0b11111101000000000000000000000000,  // 'Z'
+    0b11111111110110000000000000000000,  // '['
+    0b11111111111111100000000000000000,  // '\\'
+    0b11111111111000000000000000000000,  // ']'
+    0b11111111111100000000000000000000,  // '^'
+    0b10001000000000000000000000000000,  // '_'
+    0b11111111111110100000000000000000,  // '`'
+    0b00011000000000000000000000000000,  // 'a'
+    0b10001100000000000000000000000000,  // 'b'
+    0b00100000000000000000000000000000,  // 'c'
+    0b10010000000000000000000000000000,  // 'd'
+    0b00101000000000000000000000000000,  // 'e'
+    0b10010100000000000000000000000000,  // 'f'
+    0b10011000000000000000000000000000,  // 'g'
+    0b10011100000000000000000000000000,  // 'h'
+    0b00110000000000000000000000000000,  // 'i'
+    0b11101000000000000000000000000000,  // 'j'
+    0b11101010000000000000000000000000,  // 'k'
+    0b10100000000000000000000000000000,  // 'l'
+    0b10100100000000000000000000000000,  // 'm'
+    0b10101000000000000000000000000000,  // 'n'
+    0b00111000000000000000000000000000,  // 'o'
+    0b10101100000000000000000000000000,  // 'p'
+    0b11101100000000000000000000000000,  // 'q'
+    0b10110000000000000000000000000000,  // 'r'
+    0b01000000000000000000000000000000,  // 's'
+    0b01001000000000000000000000000000,  // 't'
+    0b10110100000000000000000000000000,  // 'u'
+    0b11101110000000000000000000000000,  // 'v'
+    0b11110000000000000000000000000000,  // 'w'
+    0b11110010000000000000000000000000,  // 'x'
+    0b11110100000000000000000000000000,  // 'y'
+    0b11110110000000000000000000000000,  // 'z'
+    0b11111111111111000000000000000000,  // '{'
+    0b11111111100000000000000000000000,  // '|'
+    0b11111111111101000000000000000000,  // '}'
+    0b11111111111010000000000000000000,  // '~'
+    0b11111111111111111111111111000000,  // 0x7f
+    0b11111111111111100110000000000000,  // 0x80
+    0b11111111111111110100100000000000,  // 0x81
+    0b11111111111111100111000000000000,  // 0x82
+    0b11111111111111101000000000000000,  // 0x83
+    0b11111111111111110100110000000000,  // 0x84
+    0b11111111111111110101000000000000,  // 0x85
+    0b11111111111111110101010000000000,  // 0x86
+    0b11111111111111111011001000000000,  // 0x87
+    0b11111111111111110101100000000000,  // 0x88
+    0b11111111111111111011010000000000,  // 0x89
+    0b11111111111111111011011000000000,  // 0x8a
+    0b11111111111111111011100000000000,  // 0x8b
+    0b11111111111111111011101000000000,  // 0x8c
+    0b11111111111111111011110000000000,  // 0x8d
+    0b11111111111111111110101100000000,  // 0x8e
+    0b11111111111111111011111000000000,  // 0x8f
+    0b11111111111111111110110000000000,  // 0x90
+    0b11111111111111111110110100000000,  // 0x91
+    0b11111111111111110101110000000000,  // 0x92
+    0b11111111111111111100000000000000,  // 0x93
+    0b11111111111111111110111000000000,  // 0x94
+    0b11111111111111111100001000000000,  // 0x95
+    0b11111111111111111100010000000000,  // 0x96
+    0b11111111111111111100011000000000,  // 0x97
+    0b11111111111111111100100000000000,  // 0x98
+    0b11111111111111101110000000000000,  // 0x99
+    0b11111111111111110110000000000000,  // 0x9a
+    0b11111111111111111100101000000000,  // 0x9b
+    0b11111111111111110110010000000000,  // 0x9c
+    0b11111111111111111100110000000000,  // 0x9d
+    0b11111111111111111100111000000000,  // 0x9e
+    0b11111111111111111110111100000000,  // 0x9f
+    0b11111111111111110110100000000000,  // 0xa0
+    0b11111111111111101110100000000000,  // 0xa1
+    0b11111111111111101001000000000000,  // 0xa2
+    0b11111111111111110110110000000000,  // 0xa3
+    0b11111111111111110111000000000000,  // 0xa4
+    0b11111111111111111101000000000000,  // 0xa5
+    0b11111111111111111101001000000000,  // 0xa6
+    0b11111111111111101111000000000000,  // 0xa7
+    0b11111111111111111101010000000000,  // 0xa8
+    0b11111111111111110111010000000000,  // 0xa9
+    0b11111111111111110111100000000000,  // 0xaa
+    0b11111111111111111111000000000000,  // 0xab
+    0b11111111111111101111100000000000,  // 0xac
+    0b11111111111111110111110000000000,  // 0xad
+    0b11111111111111111101011000000000,  // 0xae
+    0b11111111111111111101100000000000,  // 0xaf
+    0b11111111111111110000000000000000,  // 0xb0
+    0b11111111111111110000100000000000,  // 0xb1
+    0b11111111111111111000000000000000,  // 0xb2
+    0b11111111111111110001000000000000,  // 0xb3
+    0b11111111111111111101101000000000,  // 0xb4
+    0b11111111111111111000010000000000,  // 0xb5
+    0b11111111111111111101110000000000,  // 0xb6
+    0b11111111111111111101111000000000,  // 0xb7
+    0b11111111111111101010000000000000,  // 0xb8
+    0b11111111111111111000100000000000,  // 0xb9
+    0b11111111111111111000110000000000,  // 0xba
+    0b11111111111111111001000000000000,  // 0xbb
+    0b11111111111111111110000000000000,  // 0xbc
+    0b11111111111111111001010000000000,  // 0xbd
+    0b11111111111111111001100000000000,  // 0xbe
+    0b11111111111111111110001000000000,  // 0xbf
+    0b11111111111111111111100000000000,  // 0xc0
+    0b11111111111111111111100001000000,  // 0xc1
+    0b11111111111111101011000000000000,  // 0xc2
+    0b11111111111111100010000000000000,  // 0xc3
+    0b11111111111111111001110000000000,  // 0xc4
+    0b11111111111111111110010000000000,  // 0xc5
+    0b11111111111111111010000000000000,  // 0xc6
+    0b11111111111111111111011000000000,  // 0xc7
+    0b11111111111111111111100010000000,  // 0xc8
+    0b11111111111111111111100011000000,  // 0xc9
+    0b11111111111111111111100100000000,  // 0xca
+    0b11111111111111111111101111000000,  // 0xcb
+    0b11111111111111111111101111100000,  // 0xcc
+    0b11111111111111111111100101000000,  // 0xcd
+    0b11111111111111111111000100000000,  // 0xce
+    0b11111111111111111111011010000000,  // 0xcf
+    0b11111111111111100100000000000000,  // 0xd0
+    0b11111111111111110001100000000000,  // 0xd1
+    0b11111111111111111111100110000000,  // 0xd2
+    0b11111111111111111111110000000000,  // 0xd3
+    0b11111111111111111111110000100000,  // 0xd4
+    0b11111111111111111111100111000000,  // 0xd5
+    0b11111111111111111111110001000000,  // 0xd6
+    0b11111111111111111111001000000000,  // 0xd7
+    0b11111111111111110010000000000000,  // 0xd8
+    0b11111111111111110010100000000000,  // 0xd9
+    0b11111111111111111111101000000000,  // 0xda
+    0b11111111111111111111101001000000,  // 0xdb
+    0b11111111111111111111111111010000,  // 0xdc
+    0b11111111111111111111110001100000,  // 0xdd
+    0b11111111111111111111110010000000,  // 0xde
+    0b11111111111111111111110010100000,  // 0xdf
+    0b11111111111111101100000000000000,  // 0xe0
+    0b11111111111111111111001100000000,  // 0xe1
+    0b11111111111111101101000000000000,  // 0xe2
+    0b11111111111111110011000000000000,  // 0xe3
+    0b11111111111111111010010000000000,  // 0xe4
+    0b11111111111111110011100000000000,  // 0xe5
+    0b11111111111111110100000000000000,  // 0xe6
+    0b11111111111111111110011000000000,  // 0xe7
+    0b11111111111111111010100000000000,  // 0xe8
+    0b11111111111111111010110000000000,  // 0xe9
+    0b11111111111111111111011100000000,  // 0xea
+    0b11111111111111111111011110000000,  // 0xeb
+    0b11111111111111111111010000000000,  // 0xec
+    0b11111111111111111111010100000000,  // 0xed
+    0b11111111111111111111101010000000,  // 0xee
+    0b11111111111111111110100000000000,  // 0xef
+    0b11111111111111111111101011000000,  // 0xf0
+    0b11111111111111111111110011000000,  // 0xf1
+    0b11111111111111111111101100000000,  // 0xf2
+    0b11111111111111111111101101000000,  // 0xf3
+    0b11111111111111111111110011100000,  // 0xf4
+    0b11111111111111111111110100000000,  // 0xf5
+    0b11111111111111111111110100100000,  // 0xf6
+    0b11111111111111111111110101000000,  // 0xf7
+    0b11111111111111111111110101100000,  // 0xf8
+    0b11111111111111111111111111100000,  // 0xf9
+    0b11111111111111111111110110000000,  // 0xfa
+    0b11111111111111111111110110100000,  // 0xfb
+    0b11111111111111111111110111000000,  // 0xfc
+    0b11111111111111111111110111100000,  // 0xfd
+    0b11111111111111111111111000000000,  // 0xfe
+    0b11111111111111111111101110000000,  // 0xff
+    0b11111111111111111111111111111100,  // 0x100
+};
+*/
+
+// static
+const uint32_t HuffmanSpecTables::kRightCodes[] = {
+    0b00000000000000000001111111111000,  // 0x00
+    0b00000000011111111111111111011000,  // 0x01
+    0b00001111111111111111111111100010,  // 0x02
+    0b00001111111111111111111111100011,  // 0x03
+    0b00001111111111111111111111100100,  // 0x04
+    0b00001111111111111111111111100101,  // 0x05
+    0b00001111111111111111111111100110,  // 0x06
+    0b00001111111111111111111111100111,  // 0x07
+    0b00001111111111111111111111101000,  // 0x08
+    0b00000000111111111111111111101010,  // 0x09
+    0b00111111111111111111111111111100,  // 0x0a
+    0b00001111111111111111111111101001,  // 0x0b
+    0b00001111111111111111111111101010,  // 0x0c
+    0b00111111111111111111111111111101,  // 0x0d
+    0b00001111111111111111111111101011,  // 0x0e
+    0b00001111111111111111111111101100,  // 0x0f
+    0b00001111111111111111111111101101,  // 0x10
+    0b00001111111111111111111111101110,  // 0x11
+    0b00001111111111111111111111101111,  // 0x12
+    0b00001111111111111111111111110000,  // 0x13
+    0b00001111111111111111111111110001,  // 0x14
+    0b00001111111111111111111111110010,  // 0x15
+    0b00111111111111111111111111111110,  // 0x16
+    0b00001111111111111111111111110011,  // 0x17
+    0b00001111111111111111111111110100,  // 0x18
+    0b00001111111111111111111111110101,  // 0x19
+    0b00001111111111111111111111110110,  // 0x1a
+    0b00001111111111111111111111110111,  // 0x1b
+    0b00001111111111111111111111111000,  // 0x1c
+    0b00001111111111111111111111111001,  // 0x1d
+    0b00001111111111111111111111111010,  // 0x1e
+    0b00001111111111111111111111111011,  // 0x1f
+    0b00000000000000000000000000010100,  // 0x20
+    0b00000000000000000000001111111000,  // '!'
+    0b00000000000000000000001111111001,  // '\"'
+    0b00000000000000000000111111111010,  // '#'
+    0b00000000000000000001111111111001,  // '$'
+    0b00000000000000000000000000010101,  // '%'
+    0b00000000000000000000000011111000,  // '&'
+    0b00000000000000000000011111111010,  // '\''
+    0b00000000000000000000001111111010,  // '('
+    0b00000000000000000000001111111011,  // ')'
+    0b00000000000000000000000011111001,  // '*'
+    0b00000000000000000000011111111011,  // '+'
+    0b00000000000000000000000011111010,  // ','
+    0b00000000000000000000000000010110,  // '-'
+    0b00000000000000000000000000010111,  // '.'
+    0b00000000000000000000000000011000,  // '/'
+    0b00000000000000000000000000000000,  // '0'
+    0b00000000000000000000000000000001,  // '1'
+    0b00000000000000000000000000000010,  // '2'
+    0b00000000000000000000000000011001,  // '3'
+    0b00000000000000000000000000011010,  // '4'
+    0b00000000000000000000000000011011,  // '5'
+    0b00000000000000000000000000011100,  // '6'
+    0b00000000000000000000000000011101,  // '7'
+    0b00000000000000000000000000011110,  // '8'
+    0b00000000000000000000000000011111,  // '9'
+    0b00000000000000000000000001011100,  // ':'
+    0b00000000000000000000000011111011,  // ';'
+    0b00000000000000000111111111111100,  // '<'
+    0b00000000000000000000000000100000,  // '='
+    0b00000000000000000000111111111011,  // '>'
+    0b00000000000000000000001111111100,  // '?'
+    0b00000000000000000001111111111010,  // '@'
+    0b00000000000000000000000000100001,  // 'A'
+    0b00000000000000000000000001011101,  // 'B'
+    0b00000000000000000000000001011110,  // 'C'
+    0b00000000000000000000000001011111,  // 'D'
+    0b00000000000000000000000001100000,  // 'E'
+    0b00000000000000000000000001100001,  // 'F'
+    0b00000000000000000000000001100010,  // 'G'
+    0b00000000000000000000000001100011,  // 'H'
+    0b00000000000000000000000001100100,  // 'I'
+    0b00000000000000000000000001100101,  // 'J'
+    0b00000000000000000000000001100110,  // 'K'
+    0b00000000000000000000000001100111,  // 'L'
+    0b00000000000000000000000001101000,  // 'M'
+    0b00000000000000000000000001101001,  // 'N'
+    0b00000000000000000000000001101010,  // 'O'
+    0b00000000000000000000000001101011,  // 'P'
+    0b00000000000000000000000001101100,  // 'Q'
+    0b00000000000000000000000001101101,  // 'R'
+    0b00000000000000000000000001101110,  // 'S'
+    0b00000000000000000000000001101111,  // 'T'
+    0b00000000000000000000000001110000,  // 'U'
+    0b00000000000000000000000001110001,  // 'V'
+    0b00000000000000000000000001110010,  // 'W'
+    0b00000000000000000000000011111100,  // 'X'
+    0b00000000000000000000000001110011,  // 'Y'
+    0b00000000000000000000000011111101,  // 'Z'
+    0b00000000000000000001111111111011,  // '['
+    0b00000000000001111111111111110000,  // '\\'
+    0b00000000000000000001111111111100,  // ']'
+    0b00000000000000000011111111111100,  // '^'
+    0b00000000000000000000000000100010,  // '_'
+    0b00000000000000000111111111111101,  // '`'
+    0b00000000000000000000000000000011,  // 'a'
+    0b00000000000000000000000000100011,  // 'b'
+    0b00000000000000000000000000000100,  // 'c'
+    0b00000000000000000000000000100100,  // 'd'
+    0b00000000000000000000000000000101,  // 'e'
+    0b00000000000000000000000000100101,  // 'f'
+    0b00000000000000000000000000100110,  // 'g'
+    0b00000000000000000000000000100111,  // 'h'
+    0b00000000000000000000000000000110,  // 'i'
+    0b00000000000000000000000001110100,  // 'j'
+    0b00000000000000000000000001110101,  // 'k'
+    0b00000000000000000000000000101000,  // 'l'
+    0b00000000000000000000000000101001,  // 'm'
+    0b00000000000000000000000000101010,  // 'n'
+    0b00000000000000000000000000000111,  // 'o'
+    0b00000000000000000000000000101011,  // 'p'
+    0b00000000000000000000000001110110,  // 'q'
+    0b00000000000000000000000000101100,  // 'r'
+    0b00000000000000000000000000001000,  // 's'
+    0b00000000000000000000000000001001,  // 't'
+    0b00000000000000000000000000101101,  // 'u'
+    0b00000000000000000000000001110111,  // 'v'
+    0b00000000000000000000000001111000,  // 'w'
+    0b00000000000000000000000001111001,  // 'x'
+    0b00000000000000000000000001111010,  // 'y'
+    0b00000000000000000000000001111011,  // 'z'
+    0b00000000000000000111111111111110,  // '{'
+    0b00000000000000000000011111111100,  // '|'
+    0b00000000000000000011111111111101,  // '}'
+    0b00000000000000000001111111111101,  // '~'
+    0b00001111111111111111111111111100,  // 0x7f
+    0b00000000000011111111111111100110,  // 0x80
+    0b00000000001111111111111111010010,  // 0x81
+    0b00000000000011111111111111100111,  // 0x82
+    0b00000000000011111111111111101000,  // 0x83
+    0b00000000001111111111111111010011,  // 0x84
+    0b00000000001111111111111111010100,  // 0x85
+    0b00000000001111111111111111010101,  // 0x86
+    0b00000000011111111111111111011001,  // 0x87
+    0b00000000001111111111111111010110,  // 0x88
+    0b00000000011111111111111111011010,  // 0x89
+    0b00000000011111111111111111011011,  // 0x8a
+    0b00000000011111111111111111011100,  // 0x8b
+    0b00000000011111111111111111011101,  // 0x8c
+    0b00000000011111111111111111011110,  // 0x8d
+    0b00000000111111111111111111101011,  // 0x8e
+    0b00000000011111111111111111011111,  // 0x8f
+    0b00000000111111111111111111101100,  // 0x90
+    0b00000000111111111111111111101101,  // 0x91
+    0b00000000001111111111111111010111,  // 0x92
+    0b00000000011111111111111111100000,  // 0x93
+    0b00000000111111111111111111101110,  // 0x94
+    0b00000000011111111111111111100001,  // 0x95
+    0b00000000011111111111111111100010,  // 0x96
+    0b00000000011111111111111111100011,  // 0x97
+    0b00000000011111111111111111100100,  // 0x98
+    0b00000000000111111111111111011100,  // 0x99
+    0b00000000001111111111111111011000,  // 0x9a
+    0b00000000011111111111111111100101,  // 0x9b
+    0b00000000001111111111111111011001,  // 0x9c
+    0b00000000011111111111111111100110,  // 0x9d
+    0b00000000011111111111111111100111,  // 0x9e
+    0b00000000111111111111111111101111,  // 0x9f
+    0b00000000001111111111111111011010,  // 0xa0
+    0b00000000000111111111111111011101,  // 0xa1
+    0b00000000000011111111111111101001,  // 0xa2
+    0b00000000001111111111111111011011,  // 0xa3
+    0b00000000001111111111111111011100,  // 0xa4
+    0b00000000011111111111111111101000,  // 0xa5
+    0b00000000011111111111111111101001,  // 0xa6
+    0b00000000000111111111111111011110,  // 0xa7
+    0b00000000011111111111111111101010,  // 0xa8
+    0b00000000001111111111111111011101,  // 0xa9
+    0b00000000001111111111111111011110,  // 0xaa
+    0b00000000111111111111111111110000,  // 0xab
+    0b00000000000111111111111111011111,  // 0xac
+    0b00000000001111111111111111011111,  // 0xad
+    0b00000000011111111111111111101011,  // 0xae
+    0b00000000011111111111111111101100,  // 0xaf
+    0b00000000000111111111111111100000,  // 0xb0
+    0b00000000000111111111111111100001,  // 0xb1
+    0b00000000001111111111111111100000,  // 0xb2
+    0b00000000000111111111111111100010,  // 0xb3
+    0b00000000011111111111111111101101,  // 0xb4
+    0b00000000001111111111111111100001,  // 0xb5
+    0b00000000011111111111111111101110,  // 0xb6
+    0b00000000011111111111111111101111,  // 0xb7
+    0b00000000000011111111111111101010,  // 0xb8
+    0b00000000001111111111111111100010,  // 0xb9
+    0b00000000001111111111111111100011,  // 0xba
+    0b00000000001111111111111111100100,  // 0xbb
+    0b00000000011111111111111111110000,  // 0xbc
+    0b00000000001111111111111111100101,  // 0xbd
+    0b00000000001111111111111111100110,  // 0xbe
+    0b00000000011111111111111111110001,  // 0xbf
+    0b00000011111111111111111111100000,  // 0xc0
+    0b00000011111111111111111111100001,  // 0xc1
+    0b00000000000011111111111111101011,  // 0xc2
+    0b00000000000001111111111111110001,  // 0xc3
+    0b00000000001111111111111111100111,  // 0xc4
+    0b00000000011111111111111111110010,  // 0xc5
+    0b00000000001111111111111111101000,  // 0xc6
+    0b00000001111111111111111111101100,  // 0xc7
+    0b00000011111111111111111111100010,  // 0xc8
+    0b00000011111111111111111111100011,  // 0xc9
+    0b00000011111111111111111111100100,  // 0xca
+    0b00000111111111111111111111011110,  // 0xcb
+    0b00000111111111111111111111011111,  // 0xcc
+    0b00000011111111111111111111100101,  // 0xcd
+    0b00000000111111111111111111110001,  // 0xce
+    0b00000001111111111111111111101101,  // 0xcf
+    0b00000000000001111111111111110010,  // 0xd0
+    0b00000000000111111111111111100011,  // 0xd1
+    0b00000011111111111111111111100110,  // 0xd2
+    0b00000111111111111111111111100000,  // 0xd3
+    0b00000111111111111111111111100001,  // 0xd4
+    0b00000011111111111111111111100111,  // 0xd5
+    0b00000111111111111111111111100010,  // 0xd6
+    0b00000000111111111111111111110010,  // 0xd7
+    0b00000000000111111111111111100100,  // 0xd8
+    0b00000000000111111111111111100101,  // 0xd9
+    0b00000011111111111111111111101000,  // 0xda
+    0b00000011111111111111111111101001,  // 0xdb
+    0b00001111111111111111111111111101,  // 0xdc
+    0b00000111111111111111111111100011,  // 0xdd
+    0b00000111111111111111111111100100,  // 0xde
+    0b00000111111111111111111111100101,  // 0xdf
+    0b00000000000011111111111111101100,  // 0xe0
+    0b00000000111111111111111111110011,  // 0xe1
+    0b00000000000011111111111111101101,  // 0xe2
+    0b00000000000111111111111111100110,  // 0xe3
+    0b00000000001111111111111111101001,  // 0xe4
+    0b00000000000111111111111111100111,  // 0xe5
+    0b00000000000111111111111111101000,  // 0xe6
+    0b00000000011111111111111111110011,  // 0xe7
+    0b00000000001111111111111111101010,  // 0xe8
+    0b00000000001111111111111111101011,  // 0xe9
+    0b00000001111111111111111111101110,  // 0xea
+    0b00000001111111111111111111101111,  // 0xeb
+    0b00000000111111111111111111110100,  // 0xec
+    0b00000000111111111111111111110101,  // 0xed
+    0b00000011111111111111111111101010,  // 0xee
+    0b00000000011111111111111111110100,  // 0xef
+    0b00000011111111111111111111101011,  // 0xf0
+    0b00000111111111111111111111100110,  // 0xf1
+    0b00000011111111111111111111101100,  // 0xf2
+    0b00000011111111111111111111101101,  // 0xf3
+    0b00000111111111111111111111100111,  // 0xf4
+    0b00000111111111111111111111101000,  // 0xf5
+    0b00000111111111111111111111101001,  // 0xf6
+    0b00000111111111111111111111101010,  // 0xf7
+    0b00000111111111111111111111101011,  // 0xf8
+    0b00001111111111111111111111111110,  // 0xf9
+    0b00000111111111111111111111101100,  // 0xfa
+    0b00000111111111111111111111101101,  // 0xfb
+    0b00000111111111111111111111101110,  // 0xfc
+    0b00000111111111111111111111101111,  // 0xfd
+    0b00000111111111111111111111110000,  // 0xfe
+    0b00000011111111111111111111101110,  // 0xff
+    0b00111111111111111111111111111111,  // 0x100
+};
+// clang-format off
+
+}  // namespace http2
diff --git a/http2/hpack/huffman/huffman_spec_tables.h b/http2/hpack/huffman/huffman_spec_tables.h
new file mode 100644
index 0000000..6cd8726
--- /dev/null
+++ b/http2/hpack/huffman/huffman_spec_tables.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2018 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_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_
+#define QUICHE_HTTP2_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_
+
+// Tables describing the Huffman encoding of bytes as specified by RFC7541.
+
+#include <cstdint>
+
+namespace http2 {
+
+struct HuffmanSpecTables {
+  // Number of bits in the encoding of each symbol (byte).
+  static const uint8_t kCodeLengths[257];
+
+  // The encoding of each symbol, right justified (as printed), which means that
+  // the last bit of the encoding is the low-order bit of the uint32.
+  static const uint32_t kRightCodes[257];
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_
diff --git a/http2/hpack/tools/hpack_block_builder.cc b/http2/hpack/tools/hpack_block_builder.cc
new file mode 100644
index 0000000..7a7d348
--- /dev/null
+++ b/http2/hpack/tools/hpack_block_builder.cc
@@ -0,0 +1,80 @@
+// 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 "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.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, 7);
+
+  HpackVarintEncoder varint_encoder;
+
+  unsigned char c =
+      varint_encoder.StartEncoding(high_bits, prefix_length, varint);
+  buffer_.push_back(c);
+
+  if (!varint_encoder.IsEncodingInProgress()) {
+    return;
+  }
+
+  // After the prefix, at most 63 bits can remain to be encoded.
+  // Each octet holds 7 bits, so at most 9 octets are necessary.
+  // TODO(bnc): Move this into a constant in HpackVarintEncoder.
+  varint_encoder.ResumeEncoding(/* max_encoded_bytes = */ 10, &buffer_);
+  DCHECK(!varint_encoder.IsEncodingInProgress());
+}
+
+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:
+      HTTP2_BUG << "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,
+                                     Http2StringPiece 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/http2/hpack/tools/hpack_block_builder.h b/http2/hpack/tools/hpack_block_builder.h
new file mode 100644
index 0000000..560953c
--- /dev/null
+++ b/http2/hpack/tools/hpack_block_builder.h
@@ -0,0 +1,96 @@
+// 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_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_
+#define QUICHE_HTTP2_HPACK_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 DCHECKs. And of course
+// the support for very large varints will not be needed in production code.
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+class HpackBlockBuilder {
+ public:
+  explicit HpackBlockBuilder(Http2StringPiece initial_contents)
+      : buffer_(initial_contents.data(), initial_contents.size()) {}
+  HpackBlockBuilder() {}
+  ~HpackBlockBuilder() {}
+
+  size_t size() const { return buffer_.size(); }
+  const Http2String& 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,
+                                      Http2StringPiece 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,
+                                 Http2StringPiece name,
+                                 bool value_is_huffman_encoded,
+                                 Http2StringPiece 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, Http2StringPiece str);
+
+ private:
+  Http2String buffer_;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_
diff --git a/http2/hpack/tools/hpack_block_builder_test.cc b/http2/hpack/tools/hpack_block_builder_test.cc
new file mode 100644
index 0000000..a7b8064
--- /dev/null
+++ b/http2/hpack/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 "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.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 Http2String expected =
+        Http2HexDecode("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,
+        Http2StringPiece(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 Http2String expected =
+        Http2HexDecode("828684418cf1e3c2e5f23a6ba0ab90f4ff");
+    EXPECT_EQ(expected, b.buffer());
+  }
+}
+
+TEST(HpackBlockBuilderTest, DynamicTableSizeUpdate) {
+  {
+    HpackBlockBuilder b;
+    b.AppendDynamicTableSizeUpdate(0);
+    EXPECT_EQ(1u, b.size());
+
+    const char kData[] = {'\x20'};
+    Http2StringPiece 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'};
+    Http2StringPiece 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'};
+    Http2StringPiece expected(kData, sizeof kData);
+    EXPECT_EQ(expected, b.buffer());
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/tools/hpack_example.cc b/http2/hpack/tools/hpack_example.cc
new file mode 100644
index 0000000..d20eb35
--- /dev/null
+++ b/http2/hpack/tools/hpack_example.cc
@@ -0,0 +1,58 @@
+// 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 "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h"
+
+#include <ctype.h>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+void HpackExampleToStringOrDie(Http2StringPiece example, Http2String* output) {
+  while (!example.empty()) {
+    const char c0 = example[0];
+    if (isxdigit(c0)) {
+      CHECK_GT(example.size(), 1u) << "Truncated hex byte?";
+      const char c1 = example[1];
+      CHECK(isxdigit(c1)) << "Found half a byte?";
+      *output += Http2HexDecode(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 == Http2StringPiece::npos) {
+        // End of input.
+        break;
+      }
+      example.remove_prefix(pos + 1);
+      continue;
+    }
+    HTTP2_BUG << "Can't parse byte " << static_cast<int>(c0)
+              << Http2StrCat(" (0x", Http2Hex(c0), ")")
+              << "\nExample: " << example;
+  }
+  CHECK_LT(0u, output->size()) << "Example is empty.";
+}
+
+}  // namespace
+
+Http2String HpackExampleToStringOrDie(Http2StringPiece example) {
+  Http2String output;
+  HpackExampleToStringOrDie(example, &output);
+  return output;
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/tools/hpack_example.h b/http2/hpack/tools/hpack_example.h
new file mode 100644
index 0000000..ddb22a3
--- /dev/null
+++ b/http2/hpack/tools/hpack_example.h
@@ -0,0 +1,31 @@
+// 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_HPACK_TOOLS_HPACK_EXAMPLE_H_
+#define QUICHE_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.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 {
+
+Http2String HpackExampleToStringOrDie(Http2StringPiece example);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_
diff --git a/http2/hpack/varint/hpack_varint_decoder.cc b/http2/hpack/varint/hpack_varint_decoder.cc
new file mode 100644
index 0000000..1f9b6f6
--- /dev/null
+++ b/http2/hpack/varint/hpack_varint_decoder.cc
@@ -0,0 +1,172 @@
+// 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 "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+DecodeStatus HpackVarintDecoder::Start(uint8_t prefix_value,
+                                       uint8_t prefix_length,
+                                       DecodeBuffer* db) {
+  DCHECK_LE(3u, prefix_length);
+  DCHECK_LE(prefix_length, 7u);
+
+  // |prefix_mask| defines the sequence of low-order bits of the first byte
+  // that encode the prefix of the value. It is also the marker in those bits
+  // of the first byte indicating that at least one extension byte is needed.
+  const uint8_t prefix_mask = (1 << prefix_length) - 1;
+
+  // Ignore the bits that aren't a part of the prefix of the varint.
+  value_ = prefix_value & prefix_mask;
+
+  if (value_ < prefix_mask) {
+    MarkDone();
+    return DecodeStatus::kDecodeDone;
+  }
+
+  offset_ = 0;
+  return Resume(db);
+}
+
+DecodeStatus HpackVarintDecoder::StartExtended(uint8_t prefix_length,
+                                               DecodeBuffer* db) {
+  DCHECK_LE(3u, prefix_length);
+  DCHECK_LE(prefix_length, 7u);
+
+  value_ = (1 << prefix_length) - 1;
+  offset_ = 0;
+  return Resume(db);
+}
+
+DecodeStatus HpackVarintDecoder::Resume(DecodeBuffer* db) {
+  if (decode_64_bits_) {
+    HTTP2_RELOADABLE_FLAG_COUNT(http2_varint_decode_64_bits);
+    // There can be at most 10 continuation bytes.  Offset is zero for the
+    // first one and increases by 7 for each subsequent one.
+    const uint8_t kMaxOffset = 63;
+    CheckNotDone();
+
+    // Process most extension bytes without the need for overflow checking.
+    while (offset_ < kMaxOffset) {
+      if (db->Empty()) {
+        return DecodeStatus::kDecodeInProgress;
+      }
+
+      uint8_t byte = db->DecodeUInt8();
+      uint64_t summand = byte & 0x7f;
+
+      // Shifting a 7 bit value to the left by at most 56 places can never
+      // overflow on uint64_t.
+      DCHECK_LE(offset_, 56);
+      DCHECK_LE(summand, std::numeric_limits<uint64_t>::max() >> offset_);
+
+      summand <<= offset_;
+
+      // At this point,
+      // |value_| is at most (2^prefix_length - 1) + (2^49 - 1), and
+      // |summand| is at most 255 << 56 (which is smaller than 2^63),
+      // so adding them can never overflow on uint64_t.
+      DCHECK_LE(value_, std::numeric_limits<uint64_t>::max() - summand);
+
+      value_ += summand;
+
+      // Decoding ends if continuation flag is not set.
+      if ((byte & 0x80) == 0) {
+        MarkDone();
+        return DecodeStatus::kDecodeDone;
+      }
+
+      offset_ += 7;
+    }
+
+    if (db->Empty()) {
+      return DecodeStatus::kDecodeInProgress;
+    }
+
+    DCHECK_EQ(kMaxOffset, offset_);
+
+    uint8_t byte = db->DecodeUInt8();
+    // No more extension bytes are allowed after this.
+    if ((byte & 0x80) == 0) {
+      uint64_t summand = byte & 0x7f;
+      // Check for overflow in left shift.
+      if (summand <= std::numeric_limits<uint64_t>::max() >> offset_) {
+        summand <<= offset_;
+        // Check for overflow in addition.
+        if (value_ <= std::numeric_limits<uint64_t>::max() - summand) {
+          value_ += summand;
+          MarkDone();
+          return DecodeStatus::kDecodeDone;
+        }
+      }
+    }
+
+    // Signal error if value is too large or there are too many extension bytes.
+    DLOG(WARNING) << "Variable length int encoding is too large or too long. "
+                  << DebugString();
+    MarkDone();
+    return DecodeStatus::kDecodeError;
+  }
+
+  // Old code path.  TODO(bnc): remove.
+  DCHECK(!decode_64_bits_);
+  const uint8_t kMaxOffset = 28;
+  CheckNotDone();
+  do {
+    if (db->Empty()) {
+      return DecodeStatus::kDecodeInProgress;
+    }
+    uint8_t byte = db->DecodeUInt8();
+    if (offset_ == kMaxOffset && byte != 0)
+      break;
+    DCHECK(offset_ <= kMaxOffset - 7 || byte == 0);
+    // Shifting a 7 bit value to the left by at most 21 places can never
+    // overflow on uint32_t.  Shifting 0 to the left cannot overflow either.
+    value_ += (byte & 0x7f) << offset_;
+    if ((byte & 0x80) == 0) {
+      MarkDone();
+      return DecodeStatus::kDecodeDone;
+    }
+    offset_ += 7;
+  } while (offset_ <= kMaxOffset);
+  DLOG(WARNING) << "Variable length int encoding is too large or too long. "
+                << DebugString();
+  MarkDone();
+  return DecodeStatus::kDecodeError;
+}
+
+uint64_t HpackVarintDecoder::value() const {
+  CheckDone();
+  return value_;
+}
+
+void HpackVarintDecoder::set_value(uint64_t v) {
+  MarkDone();
+  value_ = v;
+}
+
+Http2String HpackVarintDecoder::DebugString() const {
+  return Http2StrCat("HpackVarintDecoder(value=", value_, ", offset=", offset_,
+                     ")");
+}
+
+DecodeStatus HpackVarintDecoder::StartForTest(uint8_t prefix_value,
+                                              uint8_t prefix_length,
+                                              DecodeBuffer* db) {
+  return Start(prefix_value, prefix_length, db);
+}
+
+DecodeStatus HpackVarintDecoder::StartExtendedForTest(uint8_t prefix_length,
+                                                      DecodeBuffer* db) {
+  return StartExtended(prefix_length, db);
+}
+
+DecodeStatus HpackVarintDecoder::ResumeForTest(DecodeBuffer* db) {
+  return Resume(db);
+}
+
+}  // namespace http2
diff --git a/http2/hpack/varint/hpack_varint_decoder.h b/http2/hpack/varint/hpack_varint_decoder.h
new file mode 100644
index 0000000..ac8118d
--- /dev/null
+++ b/http2/hpack/varint/hpack_varint_decoder.h
@@ -0,0 +1,144 @@
+// 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.
+
+// HpackVarintDecoder decodes HPACK variable length unsigned integers. In HPACK,
+// these integers are used to identify static or dynamic table index entries, to
+// specify string lengths, and to update the size limit of the dynamic table.
+// In QPACK, in addition to these uses, these integers also identify streams.
+//
+// The caller will need to validate that the decoded value is in an acceptable
+// range.
+//
+// For details of the encoding, see:
+//        http://httpwg.org/specs/rfc7541.html#integer.representation
+//
+// If GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is true, then this
+// decoder supports decoding any integer that can be represented on uint64_t,
+// thereby exceeding the requirements for QPACK: "QPACK implementations MUST be
+// able to decode integers up to 62 bits long."  See
+// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#rfc.section.5.1.1
+//
+// If GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is false, then this
+// decoder supports decoding integers up to 2^28 + 2^prefix_length - 2.
+//
+// This decoder supports at most 10 extension bytes (bytes following the prefix,
+// also called continuation bytes) if
+// GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is true, and at most 5
+// extension bytes if GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is
+// false.  An encoder is allowed to zero pad the encoded integer on the left,
+// thereby increasing the number of extension bytes.  If an encoder uses so much
+// padding that the number of extension bytes exceeds the limit, then this
+// decoder signals an error.
+
+#ifndef QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_
+#define QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_
+
+#include <cstdint>
+#include <limits>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_flags.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+// Sentinel value for |HpackVarintDecoder::offset_| to signify that decoding is
+// completed.  Only used in debug builds.
+#ifndef NDEBUG
+const uint8_t kHpackVarintDecoderOffsetDone =
+    std::numeric_limits<uint8_t>::max();
+#endif
+
+// Decodes an HPACK variable length unsigned integer, in a resumable fashion
+// so it can handle running out of input in the DecodeBuffer. Call Start or
+// StartExtended the first time (when decoding the byte that contains the
+// prefix), then call Resume later if it is necessary to resume. When done,
+// call value() to retrieve the decoded value.
+//
+// No constructor or destructor. Holds no resources, so destruction isn't
+// needed. Start and StartExtended handles the initialization of member
+// variables. This is necessary in order for HpackVarintDecoder to be part
+// of a union.
+class HTTP2_EXPORT_PRIVATE HpackVarintDecoder {
+ public:
+  // |prefix_value| is the first byte of the encoded varint.
+  // |prefix_length| is number of bits in the first byte that are used for
+  // encoding the integer.  |db| is the rest of the buffer,  that is, not
+  // including the first byte.
+  DecodeStatus Start(uint8_t prefix_value,
+                     uint8_t prefix_length,
+                     DecodeBuffer* db);
+
+  // The caller has already determined that the encoding requires multiple
+  // bytes, i.e. that the 3 to 7 low-order bits (the number determined by
+  // |prefix_length|) of the first byte are are all 1.  |db| is the rest of the
+  // buffer,  that is, not including the first byte.
+  DecodeStatus StartExtended(uint8_t prefix_length, DecodeBuffer* db);
+
+  // Resume decoding a variable length integer after an earlier
+  // call to Start or StartExtended returned kDecodeInProgress.
+  DecodeStatus Resume(DecodeBuffer* db);
+
+  uint64_t value() const;
+
+  // This supports optimizations for the case of a varint with zero extension
+  // bytes, where the handling of the prefix is done by the caller.
+  void set_value(uint64_t v);
+
+  // All the public methods below are for supporting assertions and tests.
+
+  Http2String DebugString() const;
+
+  // For benchmarking, these methods ensure the decoder
+  // is NOT inlined into the caller.
+  DecodeStatus StartForTest(uint8_t prefix_value,
+                            uint8_t prefix_length,
+                            DecodeBuffer* db);
+  DecodeStatus StartExtendedForTest(uint8_t prefix_length, DecodeBuffer* db);
+  DecodeStatus ResumeForTest(DecodeBuffer* db);
+
+ private:
+  // Protection in case Resume is called when it shouldn't be.
+  void MarkDone() {
+#ifndef NDEBUG
+    offset_ = kHpackVarintDecoderOffsetDone;
+#endif
+  }
+  void CheckNotDone() const {
+#ifndef NDEBUG
+    DCHECK_NE(kHpackVarintDecoderOffsetDone, offset_);
+#endif
+  }
+  void CheckDone() const {
+#ifndef NDEBUG
+    DCHECK_EQ(kHpackVarintDecoderOffsetDone, offset_);
+#endif
+  }
+
+  // If true, decode integers up to 2^64 - 1, and accept at most 10 extension
+  // bytes (some of which might be padding).
+  // If false, decode integers up to 2^28 + 2^prefix_length - 2, and accept at
+  // most 5 extension bytes (some of which might be padding).
+  bool decode_64_bits_ = GetHttp2ReloadableFlag(http2_varint_decode_64_bits);
+
+  // These fields are initialized just to keep ASAN happy about reading
+  // them from DebugString().
+
+  // The encoded integer is being accumulated in |value_|.  When decoding is
+  // complete, |value_| holds the result.
+  uint64_t value_ = 0;
+
+  // Each extension byte encodes in its lowest 7 bits a segment of the integer.
+  // |offset_| is the number of places this segment has to be shifted to the
+  // left for decoding.  It is zero for the first extension byte, and increases
+  // by 7 for each subsequent extension byte.
+  uint8_t offset_ = 0;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_
diff --git a/http2/hpack/varint/hpack_varint_decoder_test.cc b/http2/hpack/varint/hpack_varint_decoder_test.cc
new file mode 100644
index 0000000..761087b
--- /dev/null
+++ b/http2/hpack/varint/hpack_varint_decoder_test.cc
@@ -0,0 +1,464 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+
+// Test HpackVarintDecoder against hardcoded data.
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+
+// Save previous value of flag and restore on destruction.
+class FlagSaver {
+ public:
+  FlagSaver() = delete;
+  explicit FlagSaver(bool decode_64_bits)
+      : saved_value_(GetHttp2ReloadableFlag(http2_varint_decode_64_bits)) {
+    SetHttp2ReloadableFlag(http2_varint_decode_64_bits, decode_64_bits);
+  }
+  ~FlagSaver() {
+    SetHttp2ReloadableFlag(http2_varint_decode_64_bits, saved_value_);
+  }
+
+ private:
+  const bool saved_value_;
+};
+
+class HpackVarintDecoderTest
+    : public RandomDecoderTest,
+      public ::testing::WithParamInterface<
+          ::testing::tuple<bool, uint8_t, const char*>> {
+ protected:
+  HpackVarintDecoderTest()
+      : decode_64_bits_(::testing::get<0>(GetParam())),
+        high_bits_(::testing::get<1>(GetParam())),
+        suffix_(Http2HexDecode(::testing::get<2>(GetParam()))),
+        flag_saver_(decode_64_bits_),
+        prefix_length_(0) {}
+
+  void DecodeExpectSuccess(Http2StringPiece data,
+                           uint32_t prefix_length,
+                           uint64_t expected_value) {
+    Validator validator = [expected_value, this](
+                              const DecodeBuffer& db,
+                              DecodeStatus status) -> AssertionResult {
+      VERIFY_EQ(expected_value, decoder_.value())
+          << "Value doesn't match expected: " << decoder_.value()
+          << " != " << expected_value;
+      return AssertionSuccess();
+    };
+
+    // First validate that decoding is done and that we've advanced the cursor
+    // the expected amount.
+    validator = ValidateDoneAndOffset(/* offset = */ data.size(), validator);
+
+    EXPECT_TRUE(Decode(data, prefix_length, validator));
+
+    EXPECT_EQ(expected_value, decoder_.value());
+  }
+
+  void DecodeExpectError(Http2StringPiece data, uint32_t prefix_length) {
+    Validator validator = [](const DecodeBuffer& db,
+                             DecodeStatus status) -> AssertionResult {
+      VERIFY_EQ(DecodeStatus::kDecodeError, status);
+      return AssertionSuccess();
+    };
+
+    EXPECT_TRUE(Decode(data, prefix_length, validator));
+  }
+
+  bool decode_64_bits() const { return decode_64_bits_; }
+
+ private:
+  AssertionResult Decode(Http2StringPiece data,
+                         uint32_t prefix_length,
+                         const Validator validator) {
+    prefix_length_ = prefix_length;
+
+    // Copy |data| so that it can be modified.
+    Http2String data_copy(data);
+
+    // Bits of the first byte not part of the prefix should be ignored.
+    uint8_t high_bits_mask = 0b11111111 << prefix_length_;
+    data_copy[0] |= (high_bits_mask & high_bits_);
+
+    // Extra bytes appended to the input should be ignored.
+    data_copy.append(suffix_);
+
+    DecodeBuffer b(data_copy);
+
+    // StartDecoding, above, requires the DecodeBuffer be non-empty so that it
+    // can call Start with the prefix byte.
+    bool return_non_zero_on_first = true;
+
+    return DecodeAndValidateSeveralWays(&b, return_non_zero_on_first,
+                                        validator);
+  }
+
+  DecodeStatus StartDecoding(DecodeBuffer* b) override {
+    CHECK_LT(0u, b->Remaining());
+    uint8_t prefix = b->DecodeUInt8();
+    return decoder_.Start(prefix, prefix_length_, b);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+    return decoder_.Resume(b);
+  }
+
+  // Test new or old behavior.
+  const bool decode_64_bits_;
+  // Bits of the first byte not part of the prefix.
+  const uint8_t high_bits_;
+  // Extra bytes appended to the input.
+  const Http2String suffix_;
+
+  // |flag_saver_| must preceed |decoder_| so that the flag is already set when
+  // |decoder_| is constructed.
+  FlagSaver flag_saver_;
+  HpackVarintDecoder decoder_;
+  uint8_t prefix_length_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+    HpackVarintDecoderTest,
+    HpackVarintDecoderTest,
+    ::testing::Combine(
+        // Test both the new version (supporting 64 bit integers) and the old
+        // one (only supporting up to 2^28 + 2^prefix_length - 2.
+        ::testing::Bool(),
+        // Bits of the first byte not part of the prefix should be ignored.
+        ::testing::Values(0b00000000, 0b11111111, 0b10101010),
+        // Extra bytes appended to the input should be ignored.
+        ::testing::Values("", "00", "666f6f")));
+
+// Test data used when decode_64_bits() == true.
+struct {
+  const char* data;
+  uint32_t prefix_length;
+  uint64_t expected_value;
+} kSuccessTestData[] = {
+    // Zero value with different prefix lengths.
+    {"00", 3, 0},
+    {"00", 4, 0},
+    {"00", 5, 0},
+    {"00", 6, 0},
+    {"00", 7, 0},
+    // Small values that fit in the prefix.
+    {"06", 3, 6},
+    {"0d", 4, 13},
+    {"10", 5, 16},
+    {"29", 6, 41},
+    {"56", 7, 86},
+    // Values of 2^n-1, which have an all-zero extension byte.
+    {"0700", 3, 7},
+    {"0f00", 4, 15},
+    {"1f00", 5, 31},
+    {"3f00", 6, 63},
+    {"7f00", 7, 127},
+    // Values of 2^n-1, plus one extra byte of padding.
+    {"078000", 3, 7},
+    {"0f8000", 4, 15},
+    {"1f8000", 5, 31},
+    {"3f8000", 6, 63},
+    {"7f8000", 7, 127},
+    // Values requiring one extension byte.
+    {"0760", 3, 103},
+    {"0f2a", 4, 57},
+    {"1f7f", 5, 158},
+    {"3f02", 6, 65},
+    {"7f49", 7, 200},
+    // Values requiring one extension byte, plus one byte of padding.
+    {"07e000", 3, 103},
+    {"0faa00", 4, 57},
+    {"1fff00", 5, 158},
+    {"3f8200", 6, 65},
+    {"7fc900", 7, 200},
+    // Values requiring one extension byte, plus two bytes of padding.
+    {"07e08000", 3, 103},
+    {"0faa8000", 4, 57},
+    {"1fff8000", 5, 158},
+    {"3f828000", 6, 65},
+    {"7fc98000", 7, 200},
+    // Values requiring one extension byte, plus the maximum amount of padding.
+    {"07e0808080808080808000", 3, 103},
+    {"0faa808080808080808000", 4, 57},
+    {"1fff808080808080808000", 5, 158},
+    {"3f82808080808080808000", 6, 65},
+    {"7fc9808080808080808000", 7, 200},
+    // Values requiring two extension bytes.
+    {"07b260", 3, 12345},
+    {"0f8a2a", 4, 5401},
+    {"1fa87f", 5, 16327},
+    {"3fd002", 6, 399},
+    {"7fff49", 7, 9598},
+    // Values requiring two extension bytes, plus one byte of padding.
+    {"07b2e000", 3, 12345},
+    {"0f8aaa00", 4, 5401},
+    {"1fa8ff00", 5, 16327},
+    {"3fd08200", 6, 399},
+    {"7fffc900", 7, 9598},
+    // Values requiring two extension bytes, plus the maximum amount of padding.
+    {"07b2e080808080808000", 3, 12345},
+    {"0f8aaa80808080808000", 4, 5401},
+    {"1fa8ff80808080808000", 5, 16327},
+    {"3fd08280808080808000", 6, 399},
+    {"7fffc980808080808000", 7, 9598},
+    // Values requiring three extension bytes.
+    {"078ab260", 3, 1579281},
+    {"0fc18a2a", 4, 689488},
+    {"1fada87f", 5, 2085964},
+    {"3fa0d002", 6, 43103},
+    {"7ffeff49", 7, 1212541},
+    // Values requiring three extension bytes, plus one byte of padding.
+    {"078ab2e000", 3, 1579281},
+    {"0fc18aaa00", 4, 689488},
+    {"1fada8ff00", 5, 2085964},
+    {"3fa0d08200", 6, 43103},
+    {"7ffeffc900", 7, 1212541},
+    // Values requiring four extension bytes.
+    {"079f8ab260", 3, 202147110},
+    {"0fa2c18a2a", 4, 88252593},
+    {"1fd0ada87f", 5, 266999535},
+    {"3ff9a0d002", 6, 5509304},
+    {"7f9efeff49", 7, 155189149},
+    // Values requiring four extension bytes, plus one byte of padding.
+    {"079f8ab2e000", 3, 202147110},
+    {"0fa2c18aaa00", 4, 88252593},
+    {"1fd0ada8ff00", 5, 266999535},
+    {"3ff9a0d08200", 6, 5509304},
+    {"7f9efeffc900", 7, 155189149},
+    // Values requiring six extension bytes.
+    {"0783aa9f8ab260", 3, 3311978140938},
+    {"0ff0b0a2c18a2a", 4, 1445930244223},
+    {"1fda84d0ada87f", 5, 4374519874169},
+    {"3fb5fbf9a0d002", 6, 90263420404},
+    {"7fcff19efeff49", 7, 2542616951118},
+    // Values requiring eight extension bytes.
+    {"07f19883aa9f8ab260", 3, 54263449861016696},
+    {"0f84fdf0b0a2c18a2a", 4, 23690121121119891},
+    {"1fa0dfda84d0ada87f", 5, 71672133617889215},
+    {"3f9ff0b5fbf9a0d002", 6, 1478875878881374},
+    {"7ffbc1cff19efeff49", 7, 41658236125045114},
+    // Values requiring ten extension bytes.
+    {"0794f1f19883aa9f8ab201", 3, 12832019021693745307u},
+    {"0fa08f84fdf0b0a2c18a01", 4, 9980690937382242223u},
+    {"1fbfdda0dfda84d0ada801", 5, 12131360551794650846u},
+    {"3f9dc79ff0b5fbf9a0d001", 6, 15006530362736632796u},
+    {"7f8790fbc1cff19efeff01", 7, 18445754019193211014u},
+    // Maximum value: 2^64-1.
+    {"07f8ffffffffffffffff01", 3, 18446744073709551615u},
+    {"0ff0ffffffffffffffff01", 4, 18446744073709551615u},
+    {"1fe0ffffffffffffffff01", 5, 18446744073709551615u},
+    {"3fc0ffffffffffffffff01", 6, 18446744073709551615u},
+    {"7f80ffffffffffffffff01", 7, 18446744073709551615u},
+    // Examples from RFC7541 C.1.
+    {"0a", 5, 10},
+    {"1f9a0a", 5, 1337},
+};
+
+// Test data used when decode_64_bits() == false.
+struct {
+  const char* data;
+  uint32_t prefix_length;
+  uint64_t expected_value;
+} kSuccessTestDataOld[] = {
+    // Zero value with different prefix lengths.
+    {"00", 3, 0},
+    {"00", 4, 0},
+    {"00", 5, 0},
+    {"00", 6, 0},
+    {"00", 7, 0},
+    // Small values that fit in the prefix.
+    {"06", 3, 6},
+    {"0d", 4, 13},
+    {"10", 5, 16},
+    {"29", 6, 41},
+    {"56", 7, 86},
+    // Values of 2^n-1, which have an all-zero extension byte.
+    {"0700", 3, 7},
+    {"0f00", 4, 15},
+    {"1f00", 5, 31},
+    {"3f00", 6, 63},
+    {"7f00", 7, 127},
+    // Values of 2^n-1, plus one extra byte of padding.
+    {"078000", 3, 7},
+    {"0f8000", 4, 15},
+    {"1f8000", 5, 31},
+    {"3f8000", 6, 63},
+    {"7f8000", 7, 127},
+    // Values requiring one extension byte.
+    {"0760", 3, 103},
+    {"0f2a", 4, 57},
+    {"1f7f", 5, 158},
+    {"3f02", 6, 65},
+    {"7f49", 7, 200},
+    // Values requiring one extension byte, plus one byte of padding.
+    {"07e000", 3, 103},
+    {"0faa00", 4, 57},
+    {"1fff00", 5, 158},
+    {"3f8200", 6, 65},
+    {"7fc900", 7, 200},
+    // Values requiring one extension byte, plus two bytes of padding.
+    {"07e08000", 3, 103},
+    {"0faa8000", 4, 57},
+    {"1fff8000", 5, 158},
+    {"3f828000", 6, 65},
+    {"7fc98000", 7, 200},
+    // Values requiring one extension byte, plus the maximum amount of padding.
+    {"07e080808000", 3, 103},
+    {"0faa80808000", 4, 57},
+    {"1fff80808000", 5, 158},
+    {"3f8280808000", 6, 65},
+    {"7fc980808000", 7, 200},
+    // Values requiring two extension bytes.
+    {"07b260", 3, 12345},
+    {"0f8a2a", 4, 5401},
+    {"1fa87f", 5, 16327},
+    {"3fd002", 6, 399},
+    {"7fff49", 7, 9598},
+    // Values requiring two extension bytes, plus one byte of padding.
+    {"07b2e000", 3, 12345},
+    {"0f8aaa00", 4, 5401},
+    {"1fa8ff00", 5, 16327},
+    {"3fd08200", 6, 399},
+    {"7fffc900", 7, 9598},
+    // Values requiring two extension bytes, plus the maximum amount of padding.
+    {"07b2e0808000", 3, 12345},
+    {"0f8aaa808000", 4, 5401},
+    {"1fa8ff808000", 5, 16327},
+    {"3fd082808000", 6, 399},
+    {"7fffc9808000", 7, 9598},
+    // Values requiring three extension bytes.
+    {"078ab260", 3, 1579281},
+    {"0fc18a2a", 4, 689488},
+    {"1fada87f", 5, 2085964},
+    {"3fa0d002", 6, 43103},
+    {"7ffeff49", 7, 1212541},
+    // Values requiring three extension bytes, plus one byte of padding.
+    {"078ab2e000", 3, 1579281},
+    {"0fc18aaa00", 4, 689488},
+    {"1fada8ff00", 5, 2085964},
+    {"3fa0d08200", 6, 43103},
+    {"7ffeffc900", 7, 1212541},
+    // Values requiring four extension bytes.
+    {"079f8ab260", 3, 202147110},
+    {"0fa2c18a2a", 4, 88252593},
+    {"1fd0ada87f", 5, 266999535},
+    {"3ff9a0d002", 6, 5509304},
+    {"7f9efeff49", 7, 155189149},
+    // Values requiring four extension bytes, plus one byte of padding.
+    {"079f8ab2e000", 3, 202147110},
+    {"0fa2c18aaa00", 4, 88252593},
+    {"1fd0ada8ff00", 5, 266999535},
+    {"3ff9a0d08200", 6, 5509304},
+    {"7f9efeffc900", 7, 155189149},
+    // Examples from RFC7541 C.1.
+    {"0a", 5, 10},
+    {"1f9a0a", 5, 1337},
+};
+
+TEST_P(HpackVarintDecoderTest, Success) {
+  if (decode_64_bits()) {
+    for (size_t i = 0; i < HTTP2_ARRAYSIZE(kSuccessTestData); ++i) {
+      DecodeExpectSuccess(Http2HexDecode(kSuccessTestData[i].data),
+                          kSuccessTestData[i].prefix_length,
+                          kSuccessTestData[i].expected_value);
+    }
+  } else {
+    for (size_t i = 0; i < HTTP2_ARRAYSIZE(kSuccessTestDataOld); ++i) {
+      DecodeExpectSuccess(Http2HexDecode(kSuccessTestDataOld[i].data),
+                          kSuccessTestDataOld[i].prefix_length,
+                          kSuccessTestDataOld[i].expected_value);
+    }
+  }
+}
+
+// Test data used when decode_64_bits() == true.
+struct {
+  const char* data;
+  uint32_t prefix_length;
+} kErrorTestData[] = {
+    // Too many extension bytes, all 0s (except for extension bit in each byte).
+    {"0780808080808080808080", 3},
+    {"0f80808080808080808080", 4},
+    {"1f80808080808080808080", 5},
+    {"3f80808080808080808080", 6},
+    {"7f80808080808080808080", 7},
+    // Too many extension bytes, all 1s.
+    {"07ffffffffffffffffffff", 3},
+    {"0fffffffffffffffffffff", 4},
+    {"1fffffffffffffffffffff", 5},
+    {"3fffffffffffffffffffff", 6},
+    {"7fffffffffffffffffffff", 7},
+    // Value of 2^64, one higher than maximum of 2^64-1.
+    {"07f9ffffffffffffffff01", 3},
+    {"0ff1ffffffffffffffff01", 4},
+    {"1fe1ffffffffffffffff01", 5},
+    {"3fc1ffffffffffffffff01", 6},
+    {"7f81ffffffffffffffff01", 7},
+    // Maximum value: 2^64-1, with one byte of padding.
+    {"07f8ffffffffffffffff8100", 3},
+    {"0ff0ffffffffffffffff8100", 4},
+    {"1fe0ffffffffffffffff8100", 5},
+    {"3fc0ffffffffffffffff8100", 6},
+    {"7f80ffffffffffffffff8100", 7},
+};
+
+// Test data used when decode_64_bits() == false.
+// In this mode, HpackVarintDecoder allows at most five extension bytes,
+// and fifth extension byte must be zero.
+struct {
+  const char* data;
+  uint32_t prefix_length;
+} kErrorTestDataOld[] = {
+    // Maximum number of extension bytes but last byte is non-zero.
+    {"078080808001", 3},
+    {"0f8080808001", 4},
+    {"1f8080808001", 5},
+    {"3f8080808001", 6},
+    {"7f8080808001", 7},
+    // Too many extension bytes, all 0s (except for extension bit in each byte).
+    {"078080808080", 3},
+    {"0f8080808080", 4},
+    {"1f8080808080", 5},
+    {"3f8080808080", 6},
+    {"7f8080808080", 7},
+    // Too many extension bytes, all 1s.
+    {"07ffffffffff", 3},
+    {"0fffffffffff", 4},
+    {"1fffffffffff", 5},
+    {"3fffffffffff", 6},
+    {"7fffffffffff", 7},
+};
+
+TEST_P(HpackVarintDecoderTest, Error) {
+  if (decode_64_bits()) {
+    for (size_t i = 0; i < HTTP2_ARRAYSIZE(kErrorTestData); ++i) {
+      DecodeExpectError(Http2HexDecode(kErrorTestData[i].data),
+                        kErrorTestData[i].prefix_length);
+    }
+  } else {
+    for (size_t i = 0; i < HTTP2_ARRAYSIZE(kErrorTestDataOld); ++i) {
+      DecodeExpectError(Http2HexDecode(kErrorTestDataOld[i].data),
+                        kErrorTestDataOld[i].prefix_length);
+    }
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/varint/hpack_varint_encoder.cc b/http2/hpack/varint/hpack_varint_encoder.cc
new file mode 100644
index 0000000..5872a0e
--- /dev/null
+++ b/http2/hpack/varint/hpack_varint_encoder.cc
@@ -0,0 +1,65 @@
+// 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 "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h"
+
+#include "base/logging.h"
+
+namespace http2 {
+
+HpackVarintEncoder::HpackVarintEncoder()
+    : varint_(0), encoding_in_progress_(false) {}
+
+unsigned char HpackVarintEncoder::StartEncoding(uint8_t high_bits,
+                                                uint8_t prefix_length,
+                                                uint64_t varint) {
+  DCHECK(!encoding_in_progress_);
+  DCHECK_EQ(0u, varint_);
+  DCHECK_LE(1u, prefix_length);
+  DCHECK_LE(prefix_length, 7u);
+
+  // prefix_mask defines the sequence of low-order bits of the first byte
+  // that encode the prefix of the value. It is also the marker in those bits
+  // of the first byte indicating that at least one extension byte is needed.
+  const uint8_t prefix_mask = (1 << prefix_length) - 1;
+  DCHECK_EQ(0, high_bits & prefix_mask);
+
+  if (varint < prefix_mask) {
+    // The integer fits into the prefix in its entirety.
+    return high_bits | static_cast<unsigned char>(varint);
+  }
+
+  // We need extension bytes.
+  varint_ = varint - prefix_mask;
+  encoding_in_progress_ = true;
+  return high_bits | prefix_mask;
+}
+
+size_t HpackVarintEncoder::ResumeEncoding(size_t max_encoded_bytes,
+                                          Http2String* output) {
+  DCHECK(encoding_in_progress_);
+  DCHECK_NE(0u, max_encoded_bytes);
+
+  size_t encoded_bytes = 0;
+  while (encoded_bytes < max_encoded_bytes) {
+    ++encoded_bytes;
+    if (varint_ < 128) {
+      // Encode final seven bits, with continuation bit set to zero.
+      output->push_back(varint_);
+      varint_ = 0;
+      encoding_in_progress_ = false;
+      break;
+    }
+    // Encode the next seven bits, with continuation bit set to one.
+    output->push_back(0b10000000 | (varint_ % 128));
+    varint_ >>= 7;
+  }
+  return encoded_bytes;
+}
+
+bool HpackVarintEncoder::IsEncodingInProgress() const {
+  return encoding_in_progress_;
+}
+
+}  // namespace http2
diff --git a/http2/hpack/varint/hpack_varint_encoder.h b/http2/hpack/varint/hpack_varint_encoder.h
new file mode 100644
index 0000000..68f7474
--- /dev/null
+++ b/http2/hpack/varint/hpack_varint_encoder.h
@@ -0,0 +1,51 @@
+// 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_HPACK_VARINT_HPACK_VARINT_ENCODER_H_
+#define QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_ENCODER_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+// HPACK integer encoder class implementing variable length integer
+// representation defined in RFC7541, Section 5.1:
+// https://httpwg.org/specs/rfc7541.html#integer.representation
+class HTTP2_EXPORT_PRIVATE HpackVarintEncoder {
+ public:
+  HpackVarintEncoder();
+
+  // Start encoding an integer.  Return the first encoded byte (composed of
+  // optional high bits and 1 to 8 bit long prefix).  It is possible that this
+  // completes the encoding.  Must not be called when previously started
+  // encoding is still in progress.
+  unsigned char StartEncoding(uint8_t high_bits,
+                              uint8_t prefix_length,
+                              uint64_t varint);
+
+  // Continue encoding the integer |varint| passed in to StartEncoding().
+  // Append the next at most |max_encoded_bytes| encoded octets to |output|.
+  // Returns the number of encoded octets.  Must not be called unless a
+  // previously started encoding is still in progress.
+  size_t ResumeEncoding(size_t max_encoded_bytes, Http2String* output);
+
+  // Returns true if encoding an integer has started and is not completed yet.
+  bool IsEncodingInProgress() const;
+
+ private:
+  // The original integer shifted to the right by the number of bits already
+  // encoded.  The lower bits shifted away have already been encoded, and
+  // |varint_| has the higher bits that remain to be encoded.
+  uint64_t varint_;
+
+  // True when encoding an integer has started and is not completed yet.
+  bool encoding_in_progress_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_ENCODER_H_
diff --git a/http2/hpack/varint/hpack_varint_encoder_test.cc b/http2/hpack/varint/hpack_varint_encoder_test.cc
new file mode 100644
index 0000000..d230af3
--- /dev/null
+++ b/http2/hpack/varint/hpack_varint_encoder_test.cc
@@ -0,0 +1,178 @@
+// 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 "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h"
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+// Freshly constructed encoder is not in the process of encoding.
+TEST(HpackVarintEncoderTest, Done) {
+  HpackVarintEncoder varint_encoder;
+  EXPECT_FALSE(varint_encoder.IsEncodingInProgress());
+}
+
+struct {
+  uint8_t high_bits;
+  uint8_t prefix_length;
+  uint64_t value;
+  uint8_t expected_encoding;
+} kShortTestData[] = {{0b10110010, 1, 0, 0b10110010},
+                      {0b10101100, 2, 2, 0b10101110},
+                      {0b10100000, 3, 6, 0b10100110},
+                      {0b10110000, 4, 13, 0b10111101},
+                      {0b10100000, 5, 8, 0b10101000},
+                      {0b11000000, 6, 48, 0b11110000},
+                      {0b10000000, 7, 99, 0b11100011},
+                      // Example from RFC7541 C.1.
+                      {0b00000000, 5, 10, 0b00001010}};
+
+// Encode integers that fit in the prefix.
+TEST(HpackVarintEncoderTest, Short) {
+  HpackVarintEncoder varint_encoder;
+
+  for (size_t i = 0; i < HTTP2_ARRAYSIZE(kShortTestData); ++i) {
+    EXPECT_EQ(kShortTestData[i].expected_encoding,
+              varint_encoder.StartEncoding(kShortTestData[i].high_bits,
+                                           kShortTestData[i].prefix_length,
+                                           kShortTestData[i].value));
+    EXPECT_FALSE(varint_encoder.IsEncodingInProgress());
+  }
+}
+
+struct {
+  uint8_t high_bits;
+  uint8_t prefix_length;
+  uint64_t value;
+  const char* expected_encoding;
+} kLongTestData[] = {
+    // One extension byte.
+    {0b10011000, 3, 103, "9f60"},
+    {0b10010000, 4, 57, "9f2a"},
+    {0b11000000, 5, 158, "df7f"},
+    {0b01000000, 6, 65, "7f02"},
+    {0b00000000, 7, 200, "7f49"},
+    // Two extension bytes.
+    {0b10011000, 3, 12345, "9fb260"},
+    {0b10010000, 4, 5401, "9f8a2a"},
+    {0b11000000, 5, 16327, "dfa87f"},
+    {0b01000000, 6, 399, "7fd002"},
+    {0b00000000, 7, 9598, "7fff49"},
+    // Three extension bytes.
+    {0b10011000, 3, 1579281, "9f8ab260"},
+    {0b10010000, 4, 689488, "9fc18a2a"},
+    {0b11000000, 5, 2085964, "dfada87f"},
+    {0b01000000, 6, 43103, "7fa0d002"},
+    {0b00000000, 7, 1212541, "7ffeff49"},
+    // Four extension bytes.
+    {0b10011000, 3, 202147110, "9f9f8ab260"},
+    {0b10010000, 4, 88252593, "9fa2c18a2a"},
+    {0b11000000, 5, 266999535, "dfd0ada87f"},
+    {0b01000000, 6, 5509304, "7ff9a0d002"},
+    {0b00000000, 7, 155189149, "7f9efeff49"},
+    // Six extension bytes.
+    {0b10011000, 3, 3311978140938, "9f83aa9f8ab260"},
+    {0b10010000, 4, 1445930244223, "9ff0b0a2c18a2a"},
+    {0b11000000, 5, 4374519874169, "dfda84d0ada87f"},
+    {0b01000000, 6, 90263420404, "7fb5fbf9a0d002"},
+    {0b00000000, 7, 2542616951118, "7fcff19efeff49"},
+    // Eight extension bytes.
+    {0b10011000, 3, 54263449861016696, "9ff19883aa9f8ab260"},
+    {0b10010000, 4, 23690121121119891, "9f84fdf0b0a2c18a2a"},
+    {0b11000000, 5, 71672133617889215, "dfa0dfda84d0ada87f"},
+    {0b01000000, 6, 1478875878881374, "7f9ff0b5fbf9a0d002"},
+    {0b00000000, 7, 41658236125045114, "7ffbc1cff19efeff49"},
+    // Ten extension bytes.
+    {0b10011000, 3, 12832019021693745307u, "9f94f1f19883aa9f8ab201"},
+    {0b10010000, 4, 9980690937382242223u, "9fa08f84fdf0b0a2c18a01"},
+    {0b11000000, 5, 12131360551794650846u, "dfbfdda0dfda84d0ada801"},
+    {0b01000000, 6, 15006530362736632796u, "7f9dc79ff0b5fbf9a0d001"},
+    {0b00000000, 7, 18445754019193211014u, "7f8790fbc1cff19efeff01"},
+    // Maximum value: 2^64-1.
+    {0b10011000, 3, 18446744073709551615u, "9ff8ffffffffffffffff01"},
+    {0b10010000, 4, 18446744073709551615u, "9ff0ffffffffffffffff01"},
+    {0b11000000, 5, 18446744073709551615u, "dfe0ffffffffffffffff01"},
+    {0b01000000, 6, 18446744073709551615u, "7fc0ffffffffffffffff01"},
+    {0b00000000, 7, 18446744073709551615u, "7f80ffffffffffffffff01"},
+    // Example from RFC7541 C.1.
+    {0b00000000, 5, 1337, "1f9a0a"},
+};
+
+// Encode integers that do not fit in the prefix.
+TEST(HpackVarintEncoderTest, Long) {
+  HpackVarintEncoder varint_encoder;
+
+  // Test encoding byte by byte, also test encoding in
+  // a single ResumeEncoding() call.
+  for (bool byte_by_byte : {true, false}) {
+    for (size_t i = 0; i < HTTP2_ARRAYSIZE(kLongTestData); ++i) {
+      Http2String expected_encoding =
+          Http2HexDecode(kLongTestData[i].expected_encoding);
+      ASSERT_FALSE(expected_encoding.empty());
+
+      EXPECT_EQ(static_cast<unsigned char>(expected_encoding[0]),
+                varint_encoder.StartEncoding(kLongTestData[i].high_bits,
+                                             kLongTestData[i].prefix_length,
+                                             kLongTestData[i].value));
+      EXPECT_TRUE(varint_encoder.IsEncodingInProgress());
+
+      Http2String output;
+      if (byte_by_byte) {
+        while (varint_encoder.IsEncodingInProgress()) {
+          EXPECT_EQ(1u, varint_encoder.ResumeEncoding(1, &output));
+        }
+      } else {
+        // TODO(bnc): Factor out maximum number of extension bytes into a
+        // constant in HpackVarintEncoder.
+        EXPECT_EQ(expected_encoding.size() - 1,
+                  varint_encoder.ResumeEncoding(10, &output));
+        EXPECT_FALSE(varint_encoder.IsEncodingInProgress());
+      }
+      EXPECT_EQ(expected_encoding.size() - 1, output.size());
+      EXPECT_EQ(expected_encoding.substr(1), output);
+    }
+  }
+}
+
+struct {
+  uint8_t high_bits;
+  uint8_t prefix_length;
+  uint64_t value;
+  uint8_t expected_encoding_first_byte;
+} kLastByteIsZeroTestData[] = {
+    {0b10110010, 1, 1, 0b10110011},  {0b10101100, 2, 3, 0b10101111},
+    {0b10101000, 3, 7, 0b10101111},  {0b10110000, 4, 15, 0b10111111},
+    {0b10100000, 5, 31, 0b10111111}, {0b11000000, 6, 63, 0b11111111},
+    {0b10000000, 7, 127, 0b11111111}};
+
+// Make sure that the encoder outputs the last byte even when it is zero.  This
+// happens exactly when encoding  the value 2^prefix_length - 1.
+TEST(HpackVarintEncoderTest, LastByteIsZero) {
+  HpackVarintEncoder varint_encoder;
+
+  for (size_t i = 0; i < HTTP2_ARRAYSIZE(kLastByteIsZeroTestData); ++i) {
+    EXPECT_EQ(
+        kLastByteIsZeroTestData[i].expected_encoding_first_byte,
+        varint_encoder.StartEncoding(kLastByteIsZeroTestData[i].high_bits,
+                                     kLastByteIsZeroTestData[i].prefix_length,
+                                     kLastByteIsZeroTestData[i].value));
+    EXPECT_TRUE(varint_encoder.IsEncodingInProgress());
+
+    Http2String output;
+    EXPECT_EQ(1u, varint_encoder.ResumeEncoding(1, &output));
+    ASSERT_EQ(1u, output.size());
+    EXPECT_EQ(0b00000000, output[0]);
+    EXPECT_FALSE(varint_encoder.IsEncodingInProgress());
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/varint/hpack_varint_round_trip_test.cc b/http2/hpack/varint/hpack_varint_round_trip_test.cc
new file mode 100644
index 0000000..e67b8df
--- /dev/null
+++ b/http2/hpack/varint/hpack_varint_round_trip_test.cc
@@ -0,0 +1,512 @@
+// 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 "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+
+// Test HpackVarintDecoder against data encoded via HpackBlockBuilder,
+// which uses HpackVarintEncoder under the hood.
+
+#include <stddef.h>
+
+#include <iterator>
+#include <set>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionSuccess;
+using ::testing::Bool;
+using ::testing::WithParamInterface;
+
+namespace http2 {
+namespace test {
+namespace {
+
+// Save previous value of flag and restore on destruction.
+class FlagSaver {
+ public:
+  FlagSaver() = delete;
+  explicit FlagSaver(bool decode_64_bits)
+      : saved_value_(GetHttp2ReloadableFlag(http2_varint_decode_64_bits)) {
+    SetHttp2ReloadableFlag(http2_varint_decode_64_bits, decode_64_bits);
+  }
+  ~FlagSaver() {
+    SetHttp2ReloadableFlag(http2_varint_decode_64_bits, saved_value_);
+  }
+
+ private:
+  const bool saved_value_;
+};
+
+// Returns the highest value with the specified number of extension bytes
+// and the specified prefix length (bits).
+uint64_t HiValueOfExtensionBytes(uint32_t extension_bytes,
+                                 uint32_t prefix_length) {
+  return (1 << prefix_length) - 2 +
+         (extension_bytes == 0 ? 0 : (1LLU << (extension_bytes * 7)));
+}
+
+class HpackVarintRoundTripTest : public RandomDecoderTest,
+                                 public WithParamInterface<bool> {
+ protected:
+  HpackVarintRoundTripTest()
+      : decode_64_bits_(GetParam()),
+        flag_saver_(decode_64_bits_),
+        prefix_length_(0) {}
+
+  DecodeStatus StartDecoding(DecodeBuffer* b) override {
+    CHECK_LT(0u, b->Remaining());
+    uint8_t prefix = b->DecodeUInt8();
+    return decoder_.Start(prefix, prefix_length_, b);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+    return decoder_.Resume(b);
+  }
+
+  void DecodeSeveralWays(uint64_t expected_value, uint32_t expected_offset) {
+    // The validator is called after each of the several times that the input
+    // DecodeBuffer is decoded, each with a different segmentation of the input.
+    // Validate that decoder_.value() matches the expected value.
+    Validator validator = [expected_value, this](
+                              const DecodeBuffer& db,
+                              DecodeStatus status) -> AssertionResult {
+      if (decoder_.value() != expected_value) {
+        return AssertionFailure()
+               << "Value doesn't match expected: " << decoder_.value()
+               << " != " << expected_value;
+      }
+      return AssertionSuccess();
+    };
+
+    // First validate that decoding is done and that we've advanced the cursor
+    // the expected amount.
+    validator = ValidateDoneAndOffset(expected_offset, validator);
+
+    // StartDecoding, above, requires the DecodeBuffer be non-empty so that it
+    // can call Start with the prefix byte.
+    bool return_non_zero_on_first = true;
+
+    DecodeBuffer b(buffer_);
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, validator));
+
+    EXPECT_EQ(expected_value, decoder_.value());
+    EXPECT_EQ(expected_offset, b.Offset());
+  }
+
+  void EncodeNoRandom(uint64_t value, uint8_t prefix_length) {
+    DCHECK_LE(3, prefix_length);
+    DCHECK_LE(prefix_length, 7);
+    prefix_length_ = prefix_length;
+
+    HpackBlockBuilder bb;
+    bb.AppendHighBitsAndVarint(0, prefix_length_, value);
+    buffer_ = bb.buffer();
+    ASSERT_LT(0u, buffer_.size());
+
+    const uint8_t prefix_mask = (1 << prefix_length_) - 1;
+    ASSERT_EQ(buffer_[0], buffer_[0] & prefix_mask);
+  }
+
+  void Encode(uint64_t value, uint8_t prefix_length) {
+    EncodeNoRandom(value, prefix_length);
+    // Add some random bits to the prefix (the first byte) above the mask.
+    uint8_t prefix = buffer_[0];
+    buffer_[0] = prefix | (Random().Rand8() << prefix_length);
+    const uint8_t prefix_mask = (1 << prefix_length_) - 1;
+    ASSERT_EQ(prefix, buffer_[0] & prefix_mask);
+  }
+
+  // This is really a test of HpackBlockBuilder, making sure that the input to
+  // HpackVarintDecoder is as expected, which also acts as confirmation that
+  // my thinking about the encodings being used by the tests, i.e. cover the
+  // range desired.
+  void ValidateEncoding(uint64_t value,
+                        uint64_t minimum,
+                        uint64_t maximum,
+                        size_t expected_bytes) {
+    ASSERT_EQ(expected_bytes, buffer_.size());
+    if (expected_bytes > 1) {
+      const uint8_t prefix_mask = (1 << prefix_length_) - 1;
+      EXPECT_EQ(prefix_mask, buffer_[0] & prefix_mask);
+      size_t last = expected_bytes - 1;
+      for (size_t ndx = 1; ndx < last; ++ndx) {
+        // Before the last extension byte, we expect the high-bit set.
+        uint8_t byte = buffer_[ndx];
+        if (value == minimum) {
+          EXPECT_EQ(0x80, byte) << "ndx=" << ndx;
+        } else if (value == maximum) {
+          if (expected_bytes < 11) {
+            EXPECT_EQ(0xff, byte) << "ndx=" << ndx;
+          }
+        } else {
+          EXPECT_EQ(0x80, byte & 0x80) << "ndx=" << ndx;
+        }
+      }
+      // The last extension byte should not have the high-bit set.
+      uint8_t byte = buffer_[last];
+      if (value == minimum) {
+        if (expected_bytes == 2) {
+          EXPECT_EQ(0x00, byte);
+        } else {
+          EXPECT_EQ(0x01, byte);
+        }
+      } else if (value == maximum) {
+        if (expected_bytes < 11) {
+          EXPECT_EQ(0x7f, byte);
+        }
+      } else {
+        EXPECT_EQ(0x00, byte & 0x80);
+      }
+    } else {
+      const uint8_t prefix_mask = (1 << prefix_length_) - 1;
+      EXPECT_EQ(value, static_cast<uint32_t>(buffer_[0] & prefix_mask));
+      EXPECT_LT(value, prefix_mask);
+    }
+  }
+
+  void EncodeAndDecodeValues(const std::set<uint64_t>& values,
+                             uint8_t prefix_length,
+                             size_t expected_bytes) {
+    CHECK(!values.empty());
+    const uint64_t minimum = *values.begin();
+    const uint64_t maximum = *values.rbegin();
+    for (const uint64_t value : values) {
+      Encode(value, prefix_length);  // Sets buffer_.
+
+      Http2String msg = Http2StrCat("value=", value, " (0x", Http2Hex(value),
+                                    "), prefix_length=", prefix_length,
+                                    ", expected_bytes=", expected_bytes, "\n",
+                                    Http2HexDump(buffer_));
+
+      if (value == minimum) {
+        LOG(INFO) << "Checking minimum; " << msg;
+      } else if (value == maximum) {
+        LOG(INFO) << "Checking maximum; " << msg;
+      }
+
+      SCOPED_TRACE(msg);
+      ValidateEncoding(value, minimum, maximum, expected_bytes);
+      DecodeSeveralWays(value, expected_bytes);
+
+      // Append some random data to the end of buffer_ and repeat. That random
+      // data should be ignored.
+      buffer_.append(Random().RandString(1 + Random().Uniform(10)));
+      DecodeSeveralWays(value, expected_bytes);
+
+      // If possible, add extension bytes that don't change the value.
+      if (1 < expected_bytes) {
+        buffer_.resize(expected_bytes);
+        for (uint8_t total_bytes = expected_bytes + 1; total_bytes <= 6;
+             ++total_bytes) {
+          // Mark the current last byte as not being the last one.
+          EXPECT_EQ(0x00, 0x80 & buffer_.back());
+          buffer_.back() |= 0x80;
+          buffer_.push_back('\0');
+          DecodeSeveralWays(value, total_bytes);
+        }
+      }
+    }
+  }
+
+  // Encode values (all or some of it) in [start, start+range).  Check
+  // that |start| is the smallest value and |start+range-1| is the largest value
+  // corresponding to |expected_bytes|, except if |expected_bytes| is maximal.
+  void EncodeAndDecodeValuesInRange(uint64_t start,
+                                    uint64_t range,
+                                    uint8_t prefix_length,
+                                    size_t expected_bytes) {
+    const uint8_t prefix_mask = (1 << prefix_length) - 1;
+    const uint64_t beyond = start + range;
+
+    LOG(INFO) << "############################################################";
+    LOG(INFO) << "prefix_length=" << static_cast<int>(prefix_length);
+    LOG(INFO) << "prefix_mask=" << std::hex << static_cast<int>(prefix_mask);
+    LOG(INFO) << "start=" << start << " (" << std::hex << start << ")";
+    LOG(INFO) << "range=" << range << " (" << std::hex << range << ")";
+    LOG(INFO) << "beyond=" << beyond << " (" << std::hex << beyond << ")";
+    LOG(INFO) << "expected_bytes=" << expected_bytes;
+
+    if (expected_bytes < 11) {
+      // Confirm the claim that beyond requires more bytes.
+      Encode(beyond, prefix_length);
+      EXPECT_EQ(expected_bytes + 1, buffer_.size()) << Http2HexDump(buffer_);
+    }
+
+    std::set<uint64_t> values;
+    if (range < 200) {
+      // Select all values in the range.
+      for (uint64_t offset = 0; offset < range; ++offset) {
+        values.insert(start + offset);
+      }
+    } else {
+      // Select some values in this range, including the minimum and maximum
+      // values that require exactly |expected_bytes| extension bytes.
+      values.insert({start, start + 1, beyond - 2, beyond - 1});
+      while (values.size() < 100) {
+        values.insert(Random().UniformInRange(start, beyond - 1));
+      }
+    }
+
+    EncodeAndDecodeValues(values, prefix_length, expected_bytes);
+  }
+
+  // |flag_saver_| must preceed |decoder_| so that the flag is already set when
+  // |decoder_| is constructed.
+  const bool decode_64_bits_;
+  FlagSaver flag_saver_;
+  HpackVarintDecoder decoder_;
+  Http2String buffer_;
+  uint8_t prefix_length_;
+};
+
+INSTANTIATE_TEST_CASE_P(HpackVarintRoundTripTest,
+                        HpackVarintRoundTripTest,
+                        Bool());
+
+// To help me and future debuggers of varint encodings, this LOGs out the
+// transition points where a new extension byte is added.
+TEST_P(HpackVarintRoundTripTest, Encode) {
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint64_t a = HiValueOfExtensionBytes(0, prefix_length);
+    const uint64_t b = HiValueOfExtensionBytes(1, prefix_length);
+    const uint64_t c = HiValueOfExtensionBytes(2, prefix_length);
+    const uint64_t d = HiValueOfExtensionBytes(3, prefix_length);
+    const uint64_t e = HiValueOfExtensionBytes(4, prefix_length);
+    const uint64_t f = HiValueOfExtensionBytes(5, prefix_length);
+    const uint64_t g = HiValueOfExtensionBytes(6, prefix_length);
+    const uint64_t h = HiValueOfExtensionBytes(7, prefix_length);
+    const uint64_t i = HiValueOfExtensionBytes(8, prefix_length);
+    const uint64_t j = HiValueOfExtensionBytes(9, prefix_length);
+
+    LOG(INFO) << "############################################################";
+    LOG(INFO) << "prefix_length=" << prefix_length << "   a=" << a
+              << "   b=" << b << "   c=" << c << "   d=" << d << "   e=" << e
+              << "   f=" << f << "   g=" << g << "   h=" << h << "   i=" << i
+              << "   j=" << j;
+
+    std::vector<uint64_t> values = {
+        0,     1,                       // Force line break.
+        a - 1, a, a + 1, a + 2, a + 3,  // Force line break.
+        b - 1, b, b + 1, b + 2, b + 3,  // Force line break.
+        c - 1, c, c + 1, c + 2, c + 3,  // Force line break.
+        d - 1, d, d + 1, d + 2, d + 3,  // Force line break.
+        e - 1, e, e + 1, e + 2, e + 3   // Force line break.
+    };
+    if (decode_64_bits_) {
+      for (auto value : {
+               f - 1, f, f + 1, f + 2, f + 3,  // Force line break.
+               g - 1, g, g + 1, g + 2, g + 3,  // Force line break.
+               h - 1, h, h + 1, h + 2, h + 3,  // Force line break.
+               i - 1, i, i + 1, i + 2, i + 3,  // Force line break.
+               j - 1, j, j + 1, j + 2, j + 3,  // Force line break.
+           }) {
+        values.push_back(value);
+      }
+    }
+
+    for (uint64_t value : values) {
+      EncodeNoRandom(value, prefix_length);
+      Http2String dump = Http2HexDump(buffer_);
+      LOG(INFO) << Http2StringPrintf("%10llu %0#18x ", value, value)
+                << Http2HexDump(buffer_).substr(7);
+    }
+  }
+}
+
+TEST_P(HpackVarintRoundTripTest, FromSpec1337) {
+  DecodeBuffer b(Http2StringPiece("\x1f\x9a\x0a"));
+  uint32_t prefix_length = 5;
+  uint8_t p = b.DecodeUInt8();
+  EXPECT_EQ(1u, b.Offset());
+  EXPECT_EQ(DecodeStatus::kDecodeDone, decoder_.Start(p, prefix_length, &b));
+  EXPECT_EQ(3u, b.Offset());
+  EXPECT_EQ(1337u, decoder_.value());
+
+  EncodeNoRandom(1337, prefix_length);
+  EXPECT_EQ(3u, buffer_.size());
+  EXPECT_EQ('\x1f', buffer_[0]);
+  EXPECT_EQ('\x9a', buffer_[1]);
+  EXPECT_EQ('\x0a', buffer_[2]);
+}
+
+// Test all the values that fit into the prefix (one less than the mask).
+TEST_P(HpackVarintRoundTripTest, ValidatePrefixOnly) {
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint8_t prefix_mask = (1 << prefix_length) - 1;
+    EncodeAndDecodeValuesInRange(0, prefix_mask, prefix_length, 1);
+  }
+}
+
+// Test all values that require exactly 1 extension byte.
+TEST_P(HpackVarintRoundTripTest, ValidateOneExtensionByte) {
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint64_t start = HiValueOfExtensionBytes(0, prefix_length) + 1;
+    EncodeAndDecodeValuesInRange(start, 128, prefix_length, 2);
+  }
+}
+
+// Test *some* values that require exactly 2 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateTwoExtensionBytes) {
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint64_t start = HiValueOfExtensionBytes(1, prefix_length) + 1;
+    const uint64_t range = 127 << 7;
+
+    EncodeAndDecodeValuesInRange(start, range, prefix_length, 3);
+  }
+}
+
+// Test *some* values that require 3 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateThreeExtensionBytes) {
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint64_t start = HiValueOfExtensionBytes(2, prefix_length) + 1;
+    const uint64_t range = 127 << 14;
+
+    EncodeAndDecodeValuesInRange(start, range, prefix_length, 4);
+  }
+}
+
+// Test *some* values that require 4 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateFourExtensionBytes) {
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint64_t start = HiValueOfExtensionBytes(3, prefix_length) + 1;
+    const uint64_t range = 127 << 21;
+
+    EncodeAndDecodeValuesInRange(start, range, prefix_length, 5);
+  }
+}
+
+// Test *some* values that require 5 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateFiveExtensionBytes) {
+  if (!decode_64_bits_) {
+    return;
+  }
+
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint64_t start = HiValueOfExtensionBytes(4, prefix_length) + 1;
+    const uint64_t range = 127llu << 28;
+
+    EncodeAndDecodeValuesInRange(start, range, prefix_length, 6);
+  }
+}
+
+// Test *some* values that require 6 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateSixExtensionBytes) {
+  if (!decode_64_bits_) {
+    return;
+  }
+
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint64_t start = HiValueOfExtensionBytes(5, prefix_length) + 1;
+    const uint64_t range = 127llu << 35;
+
+    EncodeAndDecodeValuesInRange(start, range, prefix_length, 7);
+  }
+}
+
+// Test *some* values that require 7 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateSevenExtensionBytes) {
+  if (!decode_64_bits_) {
+    return;
+  }
+
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint64_t start = HiValueOfExtensionBytes(6, prefix_length) + 1;
+    const uint64_t range = 127llu << 42;
+
+    EncodeAndDecodeValuesInRange(start, range, prefix_length, 8);
+  }
+}
+
+// Test *some* values that require 8 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateEightExtensionBytes) {
+  if (!decode_64_bits_) {
+    return;
+  }
+
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint64_t start = HiValueOfExtensionBytes(7, prefix_length) + 1;
+    const uint64_t range = 127llu << 49;
+
+    EncodeAndDecodeValuesInRange(start, range, prefix_length, 9);
+  }
+}
+
+// Test *some* values that require 9 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateNineExtensionBytes) {
+  if (!decode_64_bits_) {
+    return;
+  }
+
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint64_t start = HiValueOfExtensionBytes(8, prefix_length) + 1;
+    const uint64_t range = 127llu << 56;
+
+    EncodeAndDecodeValuesInRange(start, range, prefix_length, 10);
+  }
+}
+
+// Test *some* values that require 10 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateTenExtensionBytes) {
+  if (!decode_64_bits_) {
+    return;
+  }
+
+  for (int prefix_length = 3; prefix_length <= 7; ++prefix_length) {
+    const uint64_t start = HiValueOfExtensionBytes(9, prefix_length) + 1;
+    const uint64_t range = std::numeric_limits<uint64_t>::max() - start;
+
+    EncodeAndDecodeValuesInRange(start, range, prefix_length, 11);
+  }
+}
+
+// Test the value one larger than the largest that can be decoded.
+TEST_P(HpackVarintRoundTripTest, ValueTooLarge) {
+  // New implementation can decode any integer that HpackVarintEncoder can
+  // encode.
+  if (decode_64_bits_) {
+    return;
+  }
+
+  for (prefix_length_ = 3; prefix_length_ <= 7; ++prefix_length_) {
+    const uint64_t too_large = (1 << 28) + (1 << prefix_length_) - 1;
+    const uint32_t expected_offset = 6;
+    HpackBlockBuilder bb;
+    bb.AppendHighBitsAndVarint(0, prefix_length_, too_large);
+    buffer_ = bb.buffer();
+
+    // The validator is called after each of the several times that the input
+    // DecodeBuffer is decoded, each with a different segmentation of the input.
+    // Validate that decoder_.value() matches the expected value.
+    bool validated = false;
+    Validator validator = [&validated, expected_offset](
+                              const DecodeBuffer& db,
+                              DecodeStatus status) -> AssertionResult {
+      validated = true;
+      VERIFY_EQ(DecodeStatus::kDecodeError, status);
+      VERIFY_EQ(expected_offset, db.Offset());
+      return AssertionSuccess();
+    };
+
+    // StartDecoding, above, requires the DecodeBuffer be non-empty so that it
+    // can call Start with the prefix byte.
+    bool return_non_zero_on_first = true;
+    DecodeBuffer b(buffer_);
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, validator));
+    EXPECT_EQ(expected_offset, b.Offset());
+    EXPECT_TRUE(validated);
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/http2_constants.cc b/http2/http2_constants.cc
new file mode 100644
index 0000000..d4d59a5
--- /dev/null
+++ b/http2/http2_constants.cc
@@ -0,0 +1,150 @@
+// 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 "net/third_party/quiche/src/http2/http2_constants.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+Http2String Http2FrameTypeToString(Http2FrameType v) {
+  switch (v) {
+    case Http2FrameType::DATA:
+      return "DATA";
+    case Http2FrameType::HEADERS:
+      return "HEADERS";
+    case Http2FrameType::PRIORITY:
+      return "PRIORITY";
+    case Http2FrameType::RST_STREAM:
+      return "RST_STREAM";
+    case Http2FrameType::SETTINGS:
+      return "SETTINGS";
+    case Http2FrameType::PUSH_PROMISE:
+      return "PUSH_PROMISE";
+    case Http2FrameType::PING:
+      return "PING";
+    case Http2FrameType::GOAWAY:
+      return "GOAWAY";
+    case Http2FrameType::WINDOW_UPDATE:
+      return "WINDOW_UPDATE";
+    case Http2FrameType::CONTINUATION:
+      return "CONTINUATION";
+    case Http2FrameType::ALTSVC:
+      return "ALTSVC";
+  }
+  return Http2StrCat("UnknownFrameType(", static_cast<int>(v), ")");
+}
+
+Http2String Http2FrameTypeToString(uint8_t v) {
+  return Http2FrameTypeToString(static_cast<Http2FrameType>(v));
+}
+
+Http2String Http2FrameFlagsToString(Http2FrameType type, uint8_t flags) {
+  Http2String s;
+  // Closure to append flag name |v| to the Http2String |s|,
+  // and to clear |bit| from |flags|.
+  auto append_and_clear = [&s, &flags](Http2StringPiece v, uint8_t bit) {
+    if (!s.empty()) {
+      s.push_back('|');
+    }
+    Http2StrAppend(&s, v);
+    flags ^= bit;
+  };
+  if (flags & 0x01) {
+    if (type == Http2FrameType::DATA || type == Http2FrameType::HEADERS) {
+      append_and_clear("END_STREAM", Http2FrameFlag::END_STREAM);
+    } else if (type == Http2FrameType::SETTINGS ||
+               type == Http2FrameType::PING) {
+      append_and_clear("ACK", Http2FrameFlag::ACK);
+    }
+  }
+  if (flags & 0x04) {
+    if (type == Http2FrameType::HEADERS ||
+        type == Http2FrameType::PUSH_PROMISE ||
+        type == Http2FrameType::CONTINUATION) {
+      append_and_clear("END_HEADERS", Http2FrameFlag::END_HEADERS);
+    }
+  }
+  if (flags & 0x08) {
+    if (type == Http2FrameType::DATA || type == Http2FrameType::HEADERS ||
+        type == Http2FrameType::PUSH_PROMISE) {
+      append_and_clear("PADDED", Http2FrameFlag::PADDED);
+    }
+  }
+  if (flags & 0x20) {
+    if (type == Http2FrameType::HEADERS) {
+      append_and_clear("PRIORITY", Http2FrameFlag::PRIORITY);
+    }
+  }
+  if (flags != 0) {
+    append_and_clear(Http2StringPrintf("0x%02x", flags), flags);
+  }
+  DCHECK_EQ(0, flags);
+  return s;
+}
+Http2String Http2FrameFlagsToString(uint8_t type, uint8_t flags) {
+  return Http2FrameFlagsToString(static_cast<Http2FrameType>(type), flags);
+}
+
+Http2String Http2ErrorCodeToString(uint32_t v) {
+  switch (v) {
+    case 0x0:
+      return "NO_ERROR";
+    case 0x1:
+      return "PROTOCOL_ERROR";
+    case 0x2:
+      return "INTERNAL_ERROR";
+    case 0x3:
+      return "FLOW_CONTROL_ERROR";
+    case 0x4:
+      return "SETTINGS_TIMEOUT";
+    case 0x5:
+      return "STREAM_CLOSED";
+    case 0x6:
+      return "FRAME_SIZE_ERROR";
+    case 0x7:
+      return "REFUSED_STREAM";
+    case 0x8:
+      return "CANCEL";
+    case 0x9:
+      return "COMPRESSION_ERROR";
+    case 0xa:
+      return "CONNECT_ERROR";
+    case 0xb:
+      return "ENHANCE_YOUR_CALM";
+    case 0xc:
+      return "INADEQUATE_SECURITY";
+    case 0xd:
+      return "HTTP_1_1_REQUIRED";
+  }
+  return Http2StrCat("UnknownErrorCode(0x", Http2Hex(v), ")");
+}
+Http2String Http2ErrorCodeToString(Http2ErrorCode v) {
+  return Http2ErrorCodeToString(static_cast<uint32_t>(v));
+}
+
+Http2String Http2SettingsParameterToString(uint32_t v) {
+  switch (v) {
+    case 0x1:
+      return "HEADER_TABLE_SIZE";
+    case 0x2:
+      return "ENABLE_PUSH";
+    case 0x3:
+      return "MAX_CONCURRENT_STREAMS";
+    case 0x4:
+      return "INITIAL_WINDOW_SIZE";
+    case 0x5:
+      return "MAX_FRAME_SIZE";
+    case 0x6:
+      return "MAX_HEADER_LIST_SIZE";
+  }
+  return Http2StrCat("UnknownSettingsParameter(0x", Http2Hex(v), ")");
+}
+Http2String Http2SettingsParameterToString(Http2SettingsParameter v) {
+  return Http2SettingsParameterToString(static_cast<uint32_t>(v));
+}
+
+}  // namespace http2
diff --git a/http2/http2_constants.h b/http2/http2_constants.h
new file mode 100644
index 0000000..9392006
--- /dev/null
+++ b/http2/http2_constants.h
@@ -0,0 +1,261 @@
+// 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_HTTP2_CONSTANTS_H_
+#define QUICHE_HTTP2_HTTP2_CONSTANTS_H_
+
+// Constants from the HTTP/2 spec, RFC 7540, and associated helper functions.
+
+#include <cstdint>
+#include <iosfwd>
+#include <ostream>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+// TODO(jamessynge): create http2_simple_types for types similar to
+// SpdyStreamId, but not for structures like Http2FrameHeader. Then will be
+// able to move these stream id functions there.
+constexpr uint32_t UInt31Mask() {
+  return 0x7fffffff;
+}
+constexpr uint32_t StreamIdMask() {
+  return UInt31Mask();
+}
+
+// The value used to identify types of frames. Upper case to match the RFC.
+// The comments indicate which flags are valid for that frame type.
+// ALTSVC is defined in http://httpwg.org/http-extensions/alt-svc.html
+// (not yet final standard as of March 2016, but close).
+enum class Http2FrameType : uint8_t {
+  DATA = 0,           // END_STREAM | PADDED
+  HEADERS = 1,        // END_STREAM | END_HEADERS | PADDED | PRIORITY
+  PRIORITY = 2,       //
+  RST_STREAM = 3,     //
+  SETTINGS = 4,       // ACK
+  PUSH_PROMISE = 5,   // END_HEADERS | PADDED
+  PING = 6,           // ACK
+  GOAWAY = 7,         //
+  WINDOW_UPDATE = 8,  //
+  CONTINUATION = 9,   // END_HEADERS
+  ALTSVC = 10,        //
+};
+
+// Is the frame type known/supported?
+inline bool IsSupportedHttp2FrameType(uint32_t v) {
+  return v <= static_cast<uint32_t>(Http2FrameType::ALTSVC);
+}
+inline bool IsSupportedHttp2FrameType(Http2FrameType v) {
+  return IsSupportedHttp2FrameType(static_cast<uint32_t>(v));
+}
+
+// The return type is 'Http2String' so that they can generate a unique string
+// for each unsupported value. Since these are just used for debugging/error
+// messages, that isn't a cost to we need to worry about. The same applies to
+// the functions later in this file.
+HTTP2_EXPORT_PRIVATE Http2String Http2FrameTypeToString(Http2FrameType v);
+HTTP2_EXPORT_PRIVATE Http2String Http2FrameTypeToString(uint8_t v);
+HTTP2_EXPORT_PRIVATE inline std::ostream& operator<<(std::ostream& out,
+                                                     Http2FrameType v) {
+  return out << Http2FrameTypeToString(v);
+}
+
+// Flags that appear in supported frame types. These are treated as bit masks.
+// The comments indicate for which frame types the flag is valid.
+enum Http2FrameFlag {
+  END_STREAM = 0x01,   // DATA, HEADERS
+  ACK = 0x01,          // SETTINGS, PING
+  END_HEADERS = 0x04,  // HEADERS, PUSH_PROMISE, CONTINUATION
+  PADDED = 0x08,       // DATA, HEADERS, PUSH_PROMISE
+  PRIORITY = 0x20,     // HEADERS
+};
+
+// Formats zero or more flags for the specified type of frame. Returns an
+// empty string if flags==0.
+HTTP2_EXPORT_PRIVATE Http2String Http2FrameFlagsToString(Http2FrameType type,
+                                                         uint8_t flags);
+HTTP2_EXPORT_PRIVATE Http2String Http2FrameFlagsToString(uint8_t type,
+                                                         uint8_t flags);
+
+// Error codes for GOAWAY and RST_STREAM frames.
+enum class Http2ErrorCode : uint32_t {
+  // The associated condition is not a result of an error. For example, a GOAWAY
+  // might include this code to indicate graceful shutdown of a connection.
+  HTTP2_NO_ERROR = 0x0,
+
+  // The endpoint detected an unspecific protocol error. This error is for use
+  // when a more specific error code is not available.
+  PROTOCOL_ERROR = 0x1,
+
+  // The endpoint encountered an unexpected internal error.
+  INTERNAL_ERROR = 0x2,
+
+  // The endpoint detected that its peer violated the flow-control protocol.
+  FLOW_CONTROL_ERROR = 0x3,
+
+  // The endpoint sent a SETTINGS frame but did not receive a response in a
+  // timely manner. See Section 6.5.3 ("Settings Synchronization").
+  SETTINGS_TIMEOUT = 0x4,
+
+  // The endpoint received a frame after a stream was half-closed.
+  STREAM_CLOSED = 0x5,
+
+  // The endpoint received a frame with an invalid size.
+  FRAME_SIZE_ERROR = 0x6,
+
+  // The endpoint refused the stream prior to performing any application
+  // processing (see Section 8.1.4 for details).
+  REFUSED_STREAM = 0x7,
+
+  // Used by the endpoint to indicate that the stream is no longer needed.
+  CANCEL = 0x8,
+
+  // The endpoint is unable to maintain the header compression context
+  // for the connection.
+  COMPRESSION_ERROR = 0x9,
+
+  // The connection established in response to a CONNECT request (Section 8.3)
+  // was reset or abnormally closed.
+  CONNECT_ERROR = 0xa,
+
+  // The endpoint detected that its peer is exhibiting a behavior that might
+  // be generating excessive load.
+  ENHANCE_YOUR_CALM = 0xb,
+
+  // The underlying transport has properties that do not meet minimum
+  // security requirements (see Section 9.2).
+  INADEQUATE_SECURITY = 0xc,
+
+  // The endpoint requires that HTTP/1.1 be used instead of HTTP/2.
+  HTTP_1_1_REQUIRED = 0xd,
+};
+
+// Is the error code supported? (So far that means it is in RFC 7540.)
+inline bool IsSupportedHttp2ErrorCode(uint32_t v) {
+  return v <= static_cast<uint32_t>(Http2ErrorCode::HTTP_1_1_REQUIRED);
+}
+inline bool IsSupportedHttp2ErrorCode(Http2ErrorCode v) {
+  return IsSupportedHttp2ErrorCode(static_cast<uint32_t>(v));
+}
+
+// Format the specified error code.
+HTTP2_EXPORT_PRIVATE Http2String Http2ErrorCodeToString(uint32_t v);
+HTTP2_EXPORT_PRIVATE Http2String Http2ErrorCodeToString(Http2ErrorCode v);
+HTTP2_EXPORT_PRIVATE inline std::ostream& operator<<(std::ostream& out,
+                                                     Http2ErrorCode v) {
+  return out << Http2ErrorCodeToString(v);
+}
+
+// Supported parameters in SETTINGS frames; so far just those in RFC 7540.
+enum class Http2SettingsParameter : uint16_t {
+  // Allows the sender to inform the remote endpoint of the maximum size of the
+  // header compression table used to decode header blocks, in octets. The
+  // encoder can select any size equal to or less than this value by using
+  // signaling specific to the header compression format inside a header block
+  // (see [COMPRESSION]). The initial value is 4,096 octets.
+  HEADER_TABLE_SIZE = 0x1,
+
+  // This setting can be used to disable server push (Section 8.2). An endpoint
+  // MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a
+  // value of 0. An endpoint that has both set this parameter to 0 and had it
+  // acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a connection
+  // error (Section 5.4.1) of type PROTOCOL_ERROR.
+  //
+  // The initial value is 1, which indicates that server push is permitted. Any
+  // value other than 0 or 1 MUST be treated as a connection error (Section
+  // 5.4.1) of type PROTOCOL_ERROR.
+  ENABLE_PUSH = 0x2,
+
+  // Indicates the maximum number of concurrent streams that the sender will
+  // allow. This limit is directional: it applies to the number of streams that
+  // the sender permits the receiver to create. Initially, there is no limit to
+  // this value. It is recommended that this value be no smaller than 100, so as
+  // to not unnecessarily limit parallelism.
+  //
+  // A value of 0 for MAX_CONCURRENT_STREAMS SHOULD NOT be treated as
+  // special by endpoints. A zero value does prevent the creation of new
+  // streams; however, this can also happen for any limit that is exhausted with
+  // active streams. Servers SHOULD only set a zero value for short durations;
+  // if a server does not wish to accept requests, closing the connection is
+  // more appropriate.
+  MAX_CONCURRENT_STREAMS = 0x3,
+
+  // Indicates the sender's initial window size (in octets) for stream-level
+  // flow control. The initial value is 2^16-1 (65,535) octets.
+  //
+  // This setting affects the window size of all streams (see Section 6.9.2).
+  //
+  // Values above the maximum flow-control window size of 2^31-1 MUST be treated
+  // as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR.
+  INITIAL_WINDOW_SIZE = 0x4,
+
+  // Indicates the size of the largest frame payload that the sender is willing
+  // to receive, in octets.
+  //
+  // The initial value is 2^14 (16,384) octets. The value advertised by an
+  // endpoint MUST be between this initial value and the maximum allowed frame
+  // size (2^24-1 or 16,777,215 octets), inclusive. Values outside this range
+  // MUST be treated as a connection error (Section 5.4.1) of type
+  // PROTOCOL_ERROR.
+  MAX_FRAME_SIZE = 0x5,
+
+  // This advisory setting informs a peer of the maximum size of header list
+  // that the sender is prepared to accept, in octets. The value is based on the
+  // uncompressed size of header fields, including the length of the name and
+  // value in octets plus an overhead of 32 octets for each header field.
+  //
+  // For any given request, a lower limit than what is advertised MAY be
+  // enforced. The initial value of this setting is unlimited.
+  MAX_HEADER_LIST_SIZE = 0x6,
+};
+
+// Is the settings parameter supported (so far that means it is in RFC 7540)?
+inline bool IsSupportedHttp2SettingsParameter(uint32_t v) {
+  return 0 < v && v <= static_cast<uint32_t>(
+                           Http2SettingsParameter::MAX_HEADER_LIST_SIZE);
+}
+inline bool IsSupportedHttp2SettingsParameter(Http2SettingsParameter v) {
+  return IsSupportedHttp2SettingsParameter(static_cast<uint32_t>(v));
+}
+
+// Format the specified settings parameter.
+HTTP2_EXPORT_PRIVATE Http2String Http2SettingsParameterToString(uint32_t v);
+HTTP2_EXPORT_PRIVATE Http2String
+Http2SettingsParameterToString(Http2SettingsParameter v);
+inline std::ostream& operator<<(std::ostream& out, Http2SettingsParameter v) {
+  return out << Http2SettingsParameterToString(v);
+}
+
+// Information about the initial, minimum and maximum value of settings (not
+// applicable to all settings parameters).
+class Http2SettingsInfo {
+ public:
+  // Default value for HEADER_TABLE_SIZE.
+  static constexpr uint32_t DefaultHeaderTableSize() { return 4096; }
+
+  // Default value for ENABLE_PUSH.
+  static constexpr bool DefaultEnablePush() { return true; }
+
+  // Default value for INITIAL_WINDOW_SIZE.
+  static constexpr uint32_t DefaultInitialWindowSize() { return 65535; }
+
+  // Maximum value for INITIAL_WINDOW_SIZE, and for the connection flow control
+  // window, and for each stream flow control window.
+  static constexpr uint32_t MaximumWindowSize() { return UInt31Mask(); }
+
+  // Default value for MAX_FRAME_SIZE.
+  static constexpr uint32_t DefaultMaxFrameSize() { return 16384; }
+
+  // Minimum value for MAX_FRAME_SIZE.
+  static constexpr uint32_t MinimumMaxFrameSize() { return 16384; }
+
+  // Maximum value for MAX_FRAME_SIZE.
+  static constexpr uint32_t MaximumMaxFrameSize() { return (1 << 24) - 1; }
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HTTP2_CONSTANTS_H_
diff --git a/http2/http2_constants_test.cc b/http2/http2_constants_test.cc
new file mode 100644
index 0000000..223082a
--- /dev/null
+++ b/http2/http2_constants_test.cc
@@ -0,0 +1,271 @@
+// 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 "net/third_party/quiche/src/http2/http2_constants.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+class Http2ConstantsTest : public testing::Test {};
+
+TEST(Http2ConstantsTest, Http2FrameType) {
+  EXPECT_EQ(Http2FrameType::DATA, static_cast<Http2FrameType>(0));
+  EXPECT_EQ(Http2FrameType::HEADERS, static_cast<Http2FrameType>(1));
+  EXPECT_EQ(Http2FrameType::PRIORITY, static_cast<Http2FrameType>(2));
+  EXPECT_EQ(Http2FrameType::RST_STREAM, static_cast<Http2FrameType>(3));
+  EXPECT_EQ(Http2FrameType::SETTINGS, static_cast<Http2FrameType>(4));
+  EXPECT_EQ(Http2FrameType::PUSH_PROMISE, static_cast<Http2FrameType>(5));
+  EXPECT_EQ(Http2FrameType::PING, static_cast<Http2FrameType>(6));
+  EXPECT_EQ(Http2FrameType::GOAWAY, static_cast<Http2FrameType>(7));
+  EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, static_cast<Http2FrameType>(8));
+  EXPECT_EQ(Http2FrameType::CONTINUATION, static_cast<Http2FrameType>(9));
+  EXPECT_EQ(Http2FrameType::ALTSVC, static_cast<Http2FrameType>(10));
+}
+
+TEST(Http2ConstantsTest, Http2FrameTypeToString) {
+  EXPECT_EQ("DATA", Http2FrameTypeToString(Http2FrameType::DATA));
+  EXPECT_EQ("HEADERS", Http2FrameTypeToString(Http2FrameType::HEADERS));
+  EXPECT_EQ("PRIORITY", Http2FrameTypeToString(Http2FrameType::PRIORITY));
+  EXPECT_EQ("RST_STREAM", Http2FrameTypeToString(Http2FrameType::RST_STREAM));
+  EXPECT_EQ("SETTINGS", Http2FrameTypeToString(Http2FrameType::SETTINGS));
+  EXPECT_EQ("PUSH_PROMISE",
+            Http2FrameTypeToString(Http2FrameType::PUSH_PROMISE));
+  EXPECT_EQ("PING", Http2FrameTypeToString(Http2FrameType::PING));
+  EXPECT_EQ("GOAWAY", Http2FrameTypeToString(Http2FrameType::GOAWAY));
+  EXPECT_EQ("WINDOW_UPDATE",
+            Http2FrameTypeToString(Http2FrameType::WINDOW_UPDATE));
+  EXPECT_EQ("CONTINUATION",
+            Http2FrameTypeToString(Http2FrameType::CONTINUATION));
+  EXPECT_EQ("ALTSVC", Http2FrameTypeToString(Http2FrameType::ALTSVC));
+
+  EXPECT_EQ("DATA", Http2FrameTypeToString(0));
+  EXPECT_EQ("HEADERS", Http2FrameTypeToString(1));
+  EXPECT_EQ("PRIORITY", Http2FrameTypeToString(2));
+  EXPECT_EQ("RST_STREAM", Http2FrameTypeToString(3));
+  EXPECT_EQ("SETTINGS", Http2FrameTypeToString(4));
+  EXPECT_EQ("PUSH_PROMISE", Http2FrameTypeToString(5));
+  EXPECT_EQ("PING", Http2FrameTypeToString(6));
+  EXPECT_EQ("GOAWAY", Http2FrameTypeToString(7));
+  EXPECT_EQ("WINDOW_UPDATE", Http2FrameTypeToString(8));
+  EXPECT_EQ("CONTINUATION", Http2FrameTypeToString(9));
+  EXPECT_EQ("ALTSVC", Http2FrameTypeToString(10));
+
+  EXPECT_EQ("UnknownFrameType(99)", Http2FrameTypeToString(99));
+}
+
+TEST(Http2ConstantsTest, Http2FrameFlag) {
+  EXPECT_EQ(Http2FrameFlag::END_STREAM, static_cast<Http2FrameFlag>(0x01));
+  EXPECT_EQ(Http2FrameFlag::ACK, static_cast<Http2FrameFlag>(0x01));
+  EXPECT_EQ(Http2FrameFlag::END_HEADERS, static_cast<Http2FrameFlag>(0x04));
+  EXPECT_EQ(Http2FrameFlag::PADDED, static_cast<Http2FrameFlag>(0x08));
+  EXPECT_EQ(Http2FrameFlag::PRIORITY, static_cast<Http2FrameFlag>(0x20));
+
+  EXPECT_EQ(Http2FrameFlag::END_STREAM, 0x01);
+  EXPECT_EQ(Http2FrameFlag::ACK, 0x01);
+  EXPECT_EQ(Http2FrameFlag::END_HEADERS, 0x04);
+  EXPECT_EQ(Http2FrameFlag::PADDED, 0x08);
+  EXPECT_EQ(Http2FrameFlag::PRIORITY, 0x20);
+}
+
+TEST(Http2ConstantsTest, Http2FrameFlagsToString) {
+  // Single flags...
+
+  // 0b00000001
+  EXPECT_EQ("END_STREAM", Http2FrameFlagsToString(Http2FrameType::DATA,
+                                                  Http2FrameFlag::END_STREAM));
+  EXPECT_EQ("END_STREAM",
+            Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x01));
+  EXPECT_EQ("ACK", Http2FrameFlagsToString(Http2FrameType::SETTINGS,
+                                           Http2FrameFlag::ACK));
+  EXPECT_EQ("ACK", Http2FrameFlagsToString(Http2FrameType::PING, 0x01));
+
+  // 0b00000010
+  EXPECT_EQ("0x02", Http2FrameFlagsToString(0xff, 0x02));
+
+  // 0b00000100
+  EXPECT_EQ("END_HEADERS",
+            Http2FrameFlagsToString(Http2FrameType::HEADERS,
+                                    Http2FrameFlag::END_HEADERS));
+  EXPECT_EQ("END_HEADERS",
+            Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0x04));
+  EXPECT_EQ("END_HEADERS", Http2FrameFlagsToString(0x09, 0x04));
+  EXPECT_EQ("0x04", Http2FrameFlagsToString(0xff, 0x04));
+
+  // 0b00001000
+  EXPECT_EQ("PADDED", Http2FrameFlagsToString(Http2FrameType::DATA,
+                                              Http2FrameFlag::PADDED));
+  EXPECT_EQ("PADDED", Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x08));
+  EXPECT_EQ("PADDED", Http2FrameFlagsToString(0x05, 0x08));
+  EXPECT_EQ("0x08", Http2FrameFlagsToString(0xff, Http2FrameFlag::PADDED));
+
+  // 0b00010000
+  EXPECT_EQ("0x10", Http2FrameFlagsToString(Http2FrameType::SETTINGS, 0x10));
+
+  // 0b00100000
+  EXPECT_EQ("PRIORITY", Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x20));
+  EXPECT_EQ("0x20",
+            Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0x20));
+
+  // 0b01000000
+  EXPECT_EQ("0x40", Http2FrameFlagsToString(0xff, 0x40));
+
+  // 0b10000000
+  EXPECT_EQ("0x80", Http2FrameFlagsToString(0xff, 0x80));
+
+  // Combined flags...
+
+  EXPECT_EQ("END_STREAM|PADDED|0xf6",
+            Http2FrameFlagsToString(Http2FrameType::DATA, 0xff));
+  EXPECT_EQ("END_STREAM|END_HEADERS|PADDED|PRIORITY|0xd2",
+            Http2FrameFlagsToString(Http2FrameType::HEADERS, 0xff));
+  EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::PRIORITY, 0xff));
+  EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::RST_STREAM, 0xff));
+  EXPECT_EQ("ACK|0xfe",
+            Http2FrameFlagsToString(Http2FrameType::SETTINGS, 0xff));
+  EXPECT_EQ("END_HEADERS|PADDED|0xf3",
+            Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0xff));
+  EXPECT_EQ("ACK|0xfe", Http2FrameFlagsToString(Http2FrameType::PING, 0xff));
+  EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::GOAWAY, 0xff));
+  EXPECT_EQ("0xff",
+            Http2FrameFlagsToString(Http2FrameType::WINDOW_UPDATE, 0xff));
+  EXPECT_EQ("END_HEADERS|0xfb",
+            Http2FrameFlagsToString(Http2FrameType::CONTINUATION, 0xff));
+  EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::ALTSVC, 0xff));
+  EXPECT_EQ("0xff", Http2FrameFlagsToString(0xff, 0xff));
+}
+
+TEST(Http2ConstantsTest, Http2ErrorCode) {
+  EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, static_cast<Http2ErrorCode>(0x0));
+  EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, static_cast<Http2ErrorCode>(0x1));
+  EXPECT_EQ(Http2ErrorCode::INTERNAL_ERROR, static_cast<Http2ErrorCode>(0x2));
+  EXPECT_EQ(Http2ErrorCode::FLOW_CONTROL_ERROR,
+            static_cast<Http2ErrorCode>(0x3));
+  EXPECT_EQ(Http2ErrorCode::SETTINGS_TIMEOUT, static_cast<Http2ErrorCode>(0x4));
+  EXPECT_EQ(Http2ErrorCode::STREAM_CLOSED, static_cast<Http2ErrorCode>(0x5));
+  EXPECT_EQ(Http2ErrorCode::FRAME_SIZE_ERROR, static_cast<Http2ErrorCode>(0x6));
+  EXPECT_EQ(Http2ErrorCode::REFUSED_STREAM, static_cast<Http2ErrorCode>(0x7));
+  EXPECT_EQ(Http2ErrorCode::CANCEL, static_cast<Http2ErrorCode>(0x8));
+  EXPECT_EQ(Http2ErrorCode::COMPRESSION_ERROR,
+            static_cast<Http2ErrorCode>(0x9));
+  EXPECT_EQ(Http2ErrorCode::CONNECT_ERROR, static_cast<Http2ErrorCode>(0xa));
+  EXPECT_EQ(Http2ErrorCode::ENHANCE_YOUR_CALM,
+            static_cast<Http2ErrorCode>(0xb));
+  EXPECT_EQ(Http2ErrorCode::INADEQUATE_SECURITY,
+            static_cast<Http2ErrorCode>(0xc));
+  EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED,
+            static_cast<Http2ErrorCode>(0xd));
+}
+
+TEST(Http2ConstantsTest, Http2ErrorCodeToString) {
+  EXPECT_EQ("NO_ERROR", Http2ErrorCodeToString(Http2ErrorCode::HTTP2_NO_ERROR));
+  EXPECT_EQ("NO_ERROR", Http2ErrorCodeToString(0x0));
+  EXPECT_EQ("PROTOCOL_ERROR",
+            Http2ErrorCodeToString(Http2ErrorCode::PROTOCOL_ERROR));
+  EXPECT_EQ("PROTOCOL_ERROR", Http2ErrorCodeToString(0x1));
+  EXPECT_EQ("INTERNAL_ERROR",
+            Http2ErrorCodeToString(Http2ErrorCode::INTERNAL_ERROR));
+  EXPECT_EQ("INTERNAL_ERROR", Http2ErrorCodeToString(0x2));
+  EXPECT_EQ("FLOW_CONTROL_ERROR",
+            Http2ErrorCodeToString(Http2ErrorCode::FLOW_CONTROL_ERROR));
+  EXPECT_EQ("FLOW_CONTROL_ERROR", Http2ErrorCodeToString(0x3));
+  EXPECT_EQ("SETTINGS_TIMEOUT",
+            Http2ErrorCodeToString(Http2ErrorCode::SETTINGS_TIMEOUT));
+  EXPECT_EQ("SETTINGS_TIMEOUT", Http2ErrorCodeToString(0x4));
+  EXPECT_EQ("STREAM_CLOSED",
+            Http2ErrorCodeToString(Http2ErrorCode::STREAM_CLOSED));
+  EXPECT_EQ("STREAM_CLOSED", Http2ErrorCodeToString(0x5));
+  EXPECT_EQ("FRAME_SIZE_ERROR",
+            Http2ErrorCodeToString(Http2ErrorCode::FRAME_SIZE_ERROR));
+  EXPECT_EQ("FRAME_SIZE_ERROR", Http2ErrorCodeToString(0x6));
+  EXPECT_EQ("REFUSED_STREAM",
+            Http2ErrorCodeToString(Http2ErrorCode::REFUSED_STREAM));
+  EXPECT_EQ("REFUSED_STREAM", Http2ErrorCodeToString(0x7));
+  EXPECT_EQ("CANCEL", Http2ErrorCodeToString(Http2ErrorCode::CANCEL));
+  EXPECT_EQ("CANCEL", Http2ErrorCodeToString(0x8));
+  EXPECT_EQ("COMPRESSION_ERROR",
+            Http2ErrorCodeToString(Http2ErrorCode::COMPRESSION_ERROR));
+  EXPECT_EQ("COMPRESSION_ERROR", Http2ErrorCodeToString(0x9));
+  EXPECT_EQ("CONNECT_ERROR",
+            Http2ErrorCodeToString(Http2ErrorCode::CONNECT_ERROR));
+  EXPECT_EQ("CONNECT_ERROR", Http2ErrorCodeToString(0xa));
+  EXPECT_EQ("ENHANCE_YOUR_CALM",
+            Http2ErrorCodeToString(Http2ErrorCode::ENHANCE_YOUR_CALM));
+  EXPECT_EQ("ENHANCE_YOUR_CALM", Http2ErrorCodeToString(0xb));
+  EXPECT_EQ("INADEQUATE_SECURITY",
+            Http2ErrorCodeToString(Http2ErrorCode::INADEQUATE_SECURITY));
+  EXPECT_EQ("INADEQUATE_SECURITY", Http2ErrorCodeToString(0xc));
+  EXPECT_EQ("HTTP_1_1_REQUIRED",
+            Http2ErrorCodeToString(Http2ErrorCode::HTTP_1_1_REQUIRED));
+  EXPECT_EQ("HTTP_1_1_REQUIRED", Http2ErrorCodeToString(0xd));
+
+  EXPECT_EQ("UnknownErrorCode(0x123)", Http2ErrorCodeToString(0x123));
+}
+
+TEST(Http2ConstantsTest, Http2SettingsParameter) {
+  EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE,
+            static_cast<Http2SettingsParameter>(0x1));
+  EXPECT_EQ(Http2SettingsParameter::ENABLE_PUSH,
+            static_cast<Http2SettingsParameter>(0x2));
+  EXPECT_EQ(Http2SettingsParameter::MAX_CONCURRENT_STREAMS,
+            static_cast<Http2SettingsParameter>(0x3));
+  EXPECT_EQ(Http2SettingsParameter::INITIAL_WINDOW_SIZE,
+            static_cast<Http2SettingsParameter>(0x4));
+  EXPECT_EQ(Http2SettingsParameter::MAX_FRAME_SIZE,
+            static_cast<Http2SettingsParameter>(0x5));
+  EXPECT_EQ(Http2SettingsParameter::MAX_HEADER_LIST_SIZE,
+            static_cast<Http2SettingsParameter>(0x6));
+
+  EXPECT_TRUE(IsSupportedHttp2SettingsParameter(
+      Http2SettingsParameter::HEADER_TABLE_SIZE));
+  EXPECT_TRUE(
+      IsSupportedHttp2SettingsParameter(Http2SettingsParameter::ENABLE_PUSH));
+  EXPECT_TRUE(IsSupportedHttp2SettingsParameter(
+      Http2SettingsParameter::MAX_CONCURRENT_STREAMS));
+  EXPECT_TRUE(IsSupportedHttp2SettingsParameter(
+      Http2SettingsParameter::INITIAL_WINDOW_SIZE));
+  EXPECT_TRUE(IsSupportedHttp2SettingsParameter(
+      Http2SettingsParameter::MAX_FRAME_SIZE));
+  EXPECT_TRUE(IsSupportedHttp2SettingsParameter(
+      Http2SettingsParameter::MAX_HEADER_LIST_SIZE));
+
+  EXPECT_FALSE(IsSupportedHttp2SettingsParameter(
+      static_cast<Http2SettingsParameter>(0)));
+  EXPECT_FALSE(IsSupportedHttp2SettingsParameter(
+      static_cast<Http2SettingsParameter>(7)));
+}
+
+TEST(Http2ConstantsTest, Http2SettingsParameterToString) {
+  EXPECT_EQ("HEADER_TABLE_SIZE",
+            Http2SettingsParameterToString(
+                Http2SettingsParameter::HEADER_TABLE_SIZE));
+  EXPECT_EQ("HEADER_TABLE_SIZE", Http2SettingsParameterToString(0x1));
+  EXPECT_EQ("ENABLE_PUSH", Http2SettingsParameterToString(
+                               Http2SettingsParameter::ENABLE_PUSH));
+  EXPECT_EQ("ENABLE_PUSH", Http2SettingsParameterToString(0x2));
+  EXPECT_EQ("MAX_CONCURRENT_STREAMS",
+            Http2SettingsParameterToString(
+                Http2SettingsParameter::MAX_CONCURRENT_STREAMS));
+  EXPECT_EQ("MAX_CONCURRENT_STREAMS", Http2SettingsParameterToString(0x3));
+  EXPECT_EQ("INITIAL_WINDOW_SIZE",
+            Http2SettingsParameterToString(
+                Http2SettingsParameter::INITIAL_WINDOW_SIZE));
+  EXPECT_EQ("INITIAL_WINDOW_SIZE", Http2SettingsParameterToString(0x4));
+  EXPECT_EQ("MAX_FRAME_SIZE", Http2SettingsParameterToString(
+                                  Http2SettingsParameter::MAX_FRAME_SIZE));
+  EXPECT_EQ("MAX_FRAME_SIZE", Http2SettingsParameterToString(0x5));
+  EXPECT_EQ("MAX_HEADER_LIST_SIZE",
+            Http2SettingsParameterToString(
+                Http2SettingsParameter::MAX_HEADER_LIST_SIZE));
+  EXPECT_EQ("MAX_HEADER_LIST_SIZE", Http2SettingsParameterToString(0x6));
+
+  EXPECT_EQ("UnknownSettingsParameter(0x123)",
+            Http2SettingsParameterToString(0x123));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/http2_constants_test_util.cc b/http2/http2_constants_test_util.cc
new file mode 100644
index 0000000..e729890
--- /dev/null
+++ b/http2/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 "net/third_party/quiche/src/http2/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/http2/http2_constants_test_util.h b/http2/http2_constants_test_util.h
new file mode 100644
index 0000000..6ddc1cd
--- /dev/null
+++ b/http2/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_HTTP2_CONSTANTS_TEST_UTIL_H_
+#define QUICHE_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "net/third_party/quiche/src/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_HTTP2_CONSTANTS_TEST_UTIL_H_
diff --git a/http2/http2_structures.cc b/http2/http2_structures.cc
new file mode 100644
index 0000000..7dbaf30
--- /dev/null
+++ b/http2/http2_structures.cc
@@ -0,0 +1,132 @@
+// 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 "net/third_party/quiche/src/http2/http2_structures.h"
+
+#include <cstring>  // For std::memcmp
+#include <sstream>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+// Http2FrameHeader:
+
+bool Http2FrameHeader::IsProbableHttpResponse() const {
+  return (payload_length == 0x485454 &&      // "HTT"
+          static_cast<char>(type) == 'P' &&  // "P"
+          flags == '/');                     // "/"
+}
+
+Http2String Http2FrameHeader::ToString() const {
+  return Http2StrCat("length=", payload_length,
+                     ", type=", Http2FrameTypeToString(type),
+                     ", flags=", FlagsToString(), ", stream=", stream_id);
+}
+
+Http2String Http2FrameHeader::FlagsToString() const {
+  return Http2FrameFlagsToString(type, flags);
+}
+
+bool operator==(const Http2FrameHeader& a, const Http2FrameHeader& b) {
+  return a.payload_length == b.payload_length && a.stream_id == b.stream_id &&
+         a.type == b.type && a.flags == b.flags;
+}
+
+std::ostream& operator<<(std::ostream& out, const Http2FrameHeader& v) {
+  return out << v.ToString();
+}
+
+// Http2PriorityFields:
+
+bool operator==(const Http2PriorityFields& a, const Http2PriorityFields& b) {
+  return a.stream_dependency == b.stream_dependency && a.weight == b.weight;
+}
+
+Http2String Http2PriorityFields::ToString() const {
+  std::stringstream ss;
+  ss << "E=" << (is_exclusive ? "true" : "false")
+     << ", stream=" << stream_dependency
+     << ", weight=" << static_cast<uint32_t>(weight);
+  return ss.str();
+}
+
+std::ostream& operator<<(std::ostream& out, const Http2PriorityFields& v) {
+  return out << v.ToString();
+}
+
+// Http2RstStreamFields:
+
+bool operator==(const Http2RstStreamFields& a, const Http2RstStreamFields& b) {
+  return a.error_code == b.error_code;
+}
+
+std::ostream& operator<<(std::ostream& out, const Http2RstStreamFields& v) {
+  return out << "error_code=" << v.error_code;
+}
+
+// Http2SettingFields:
+
+bool operator==(const Http2SettingFields& a, const Http2SettingFields& b) {
+  return a.parameter == b.parameter && a.value == b.value;
+}
+std::ostream& operator<<(std::ostream& out, const Http2SettingFields& v) {
+  return out << "parameter=" << v.parameter << ", value=" << v.value;
+}
+
+// Http2PushPromiseFields:
+
+bool operator==(const Http2PushPromiseFields& a,
+                const Http2PushPromiseFields& b) {
+  return a.promised_stream_id == b.promised_stream_id;
+}
+
+std::ostream& operator<<(std::ostream& out, const Http2PushPromiseFields& v) {
+  return out << "promised_stream_id=" << v.promised_stream_id;
+}
+
+// Http2PingFields:
+
+bool operator==(const Http2PingFields& a, const Http2PingFields& b) {
+  static_assert((sizeof a.opaque_bytes) == Http2PingFields::EncodedSize(),
+                "Why not the same size?");
+  return 0 ==
+         std::memcmp(a.opaque_bytes, b.opaque_bytes, sizeof a.opaque_bytes);
+}
+
+std::ostream& operator<<(std::ostream& out, const Http2PingFields& v) {
+  return out << "opaque_bytes=0x"
+             << Http2HexEncode(v.opaque_bytes, sizeof v.opaque_bytes);
+}
+
+// Http2GoAwayFields:
+
+bool operator==(const Http2GoAwayFields& a, const Http2GoAwayFields& b) {
+  return a.last_stream_id == b.last_stream_id && a.error_code == b.error_code;
+}
+std::ostream& operator<<(std::ostream& out, const Http2GoAwayFields& v) {
+  return out << "last_stream_id=" << v.last_stream_id
+             << ", error_code=" << v.error_code;
+}
+
+// Http2WindowUpdateFields:
+
+bool operator==(const Http2WindowUpdateFields& a,
+                const Http2WindowUpdateFields& b) {
+  return a.window_size_increment == b.window_size_increment;
+}
+std::ostream& operator<<(std::ostream& out, const Http2WindowUpdateFields& v) {
+  return out << "window_size_increment=" << v.window_size_increment;
+}
+
+// Http2AltSvcFields:
+
+bool operator==(const Http2AltSvcFields& a, const Http2AltSvcFields& b) {
+  return a.origin_length == b.origin_length;
+}
+std::ostream& operator<<(std::ostream& out, const Http2AltSvcFields& v) {
+  return out << "origin_length=" << v.origin_length;
+}
+
+}  // namespace http2
diff --git a/http2/http2_structures.h b/http2/http2_structures.h
new file mode 100644
index 0000000..01c011e
--- /dev/null
+++ b/http2/http2_structures.h
@@ -0,0 +1,325 @@
+// 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_HTTP2_STRUCTURES_H_
+#define QUICHE_HTTP2_HTTP2_STRUCTURES_H_
+
+// Defines structs for various fixed sized structures in HTTP/2.
+//
+// Those structs with multiple fields have constructors that take arguments in
+// the same order as their encoding (which may be different from their order
+// in the struct). For single field structs, use aggregate initialization if
+// desired, e.g.:
+//
+//   Http2RstStreamFields var{Http2ErrorCode::ENHANCE_YOUR_CALM};
+// or:
+//   SomeFunc(Http2RstStreamFields{Http2ErrorCode::ENHANCE_YOUR_CALM});
+//
+// Each struct includes a static method EncodedSize which returns the number
+// of bytes of the encoding.
+//
+// With the exception of Http2FrameHeader, all the types are named
+// Http2<X>Fields, where X is the title-case form of the frame which always
+// includes the fields; the "always" is to cover the case of the PRIORITY frame;
+// its fields optionally appear in the HEADERS frame, but the struct is called
+// Http2PriorityFields.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <ostream>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+struct HTTP2_EXPORT_PRIVATE Http2FrameHeader {
+  Http2FrameHeader() {}
+  Http2FrameHeader(uint32_t payload_length,
+                   Http2FrameType type,
+                   uint8_t flags,
+                   uint32_t stream_id)
+      : payload_length(payload_length),
+        stream_id(stream_id),
+        type(type),
+        flags(static_cast<Http2FrameFlag>(flags)) {
+    DCHECK_LT(payload_length, static_cast<uint32_t>(1 << 24))
+        << "Payload Length is only a 24 bit field\n"
+        << ToString();
+  }
+
+  static constexpr size_t EncodedSize() { return 9; }
+
+  // Keep the current value of those flags that are in
+  // valid_flags, and clear all the others.
+  void RetainFlags(uint8_t valid_flags) {
+    flags = static_cast<Http2FrameFlag>(flags & valid_flags);
+  }
+
+  // Returns true if any of the flags in flag_mask are set,
+  // otherwise false.
+  bool HasAnyFlags(uint8_t flag_mask) const { return 0 != (flags & flag_mask); }
+
+  // Is the END_STREAM flag set?
+  bool IsEndStream() const {
+    DCHECK(type == Http2FrameType::DATA || type == Http2FrameType::HEADERS)
+        << ToString();
+    return (flags & Http2FrameFlag::END_STREAM) != 0;
+  }
+
+  // Is the ACK flag set?
+  bool IsAck() const {
+    DCHECK(type == Http2FrameType::SETTINGS || type == Http2FrameType::PING)
+        << ToString();
+    return (flags & Http2FrameFlag::ACK) != 0;
+  }
+
+  // Is the END_HEADERS flag set?
+  bool IsEndHeaders() const {
+    DCHECK(type == Http2FrameType::HEADERS ||
+           type == Http2FrameType::PUSH_PROMISE ||
+           type == Http2FrameType::CONTINUATION)
+        << ToString();
+    return (flags & Http2FrameFlag::END_HEADERS) != 0;
+  }
+
+  // Is the PADDED flag set?
+  bool IsPadded() const {
+    DCHECK(type == Http2FrameType::DATA || type == Http2FrameType::HEADERS ||
+           type == Http2FrameType::PUSH_PROMISE)
+        << ToString();
+    return (flags & Http2FrameFlag::PADDED) != 0;
+  }
+
+  // Is the PRIORITY flag set?
+  bool HasPriority() const {
+    DCHECK_EQ(type, Http2FrameType::HEADERS) << ToString();
+    return (flags & Http2FrameFlag::PRIORITY) != 0;
+  }
+
+  // Does the encoding of this header start with "HTTP/", indicating that it
+  // might be from a non-HTTP/2 server.
+  bool IsProbableHttpResponse() const;
+
+  // Produce strings useful for debugging/logging messages.
+  Http2String ToString() const;
+  Http2String FlagsToString() const;
+
+  // 24 bit length of the payload after the header, including any padding.
+  // First field in encoding.
+  uint32_t payload_length;  // 24 bits
+
+  // 31 bit stream id, with high bit (32nd bit) reserved (must be zero),
+  // and is cleared during decoding.
+  // Fourth field in encoding.
+  uint32_t stream_id;
+
+  // Type of the frame.
+  // Second field in encoding.
+  Http2FrameType type;
+
+  // Flag bits, with interpretations that depend upon the frame type.
+  // Flag bits not used by the frame type are cleared.
+  // Third field in encoding.
+  Http2FrameFlag flags;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2FrameHeader& a,
+                                     const Http2FrameHeader& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2FrameHeader& a,
+                                            const Http2FrameHeader& b) {
+  return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const Http2FrameHeader& v);
+
+// Http2PriorityFields:
+
+struct HTTP2_EXPORT_PRIVATE Http2PriorityFields {
+  Http2PriorityFields() {}
+  Http2PriorityFields(uint32_t stream_dependency,
+                      uint32_t weight,
+                      bool is_exclusive)
+      : stream_dependency(stream_dependency),
+        weight(weight),
+        is_exclusive(is_exclusive) {
+    // Can't have the high-bit set in the stream id because we need to use
+    // that for the EXCLUSIVE flag bit.
+    DCHECK_EQ(stream_dependency, stream_dependency & StreamIdMask())
+        << "Stream Dependency is only a 31-bit field.\n"
+        << ToString();
+    DCHECK_LE(1u, weight) << "Weight is too small.";
+    DCHECK_LE(weight, 256u) << "Weight is too large.";
+  }
+  static constexpr size_t EncodedSize() { return 5; }
+
+  // Produce strings useful for debugging/logging messages.
+  Http2String ToString() const;
+
+  // A 31-bit stream identifier for the stream that this stream depends on.
+  uint32_t stream_dependency;
+
+  // Weight (1 to 256) is encoded as a byte in the range 0 to 255, so we
+  // add one when decoding, and store it in a field larger than a byte.
+  uint32_t weight;
+
+  // A single-bit flag indicating that the stream dependency is exclusive;
+  // extracted from high bit of stream dependency field during decoding.
+  bool is_exclusive;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2PriorityFields& a,
+                                     const Http2PriorityFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PriorityFields& a,
+                                            const Http2PriorityFields& b) {
+  return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const Http2PriorityFields& v);
+
+// Http2RstStreamFields:
+
+struct Http2RstStreamFields {
+  static constexpr size_t EncodedSize() { return 4; }
+  bool IsSupportedErrorCode() const {
+    return IsSupportedHttp2ErrorCode(error_code);
+  }
+
+  Http2ErrorCode error_code;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2RstStreamFields& a,
+                                     const Http2RstStreamFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2RstStreamFields& a,
+                                            const Http2RstStreamFields& b) {
+  return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const Http2RstStreamFields& v);
+
+// Http2SettingFields:
+
+struct Http2SettingFields {
+  Http2SettingFields() {}
+  Http2SettingFields(Http2SettingsParameter parameter, uint32_t value)
+      : parameter(parameter), value(value) {}
+  static constexpr size_t EncodedSize() { return 6; }
+  bool IsSupportedParameter() const {
+    return IsSupportedHttp2SettingsParameter(parameter);
+  }
+
+  Http2SettingsParameter parameter;
+  uint32_t value;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2SettingFields& a,
+                                     const Http2SettingFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2SettingFields& a,
+                                            const Http2SettingFields& b) {
+  return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const Http2SettingFields& v);
+
+// Http2PushPromiseFields:
+
+struct Http2PushPromiseFields {
+  static constexpr size_t EncodedSize() { return 4; }
+
+  uint32_t promised_stream_id;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2PushPromiseFields& a,
+                                     const Http2PushPromiseFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PushPromiseFields& a,
+                                            const Http2PushPromiseFields& b) {
+  return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const Http2PushPromiseFields& v);
+
+// Http2PingFields:
+
+struct Http2PingFields {
+  static constexpr size_t EncodedSize() { return 8; }
+
+  uint8_t opaque_bytes[8];
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2PingFields& a,
+                                     const Http2PingFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PingFields& a,
+                                            const Http2PingFields& b) {
+  return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const Http2PingFields& v);
+
+// Http2GoAwayFields:
+
+struct Http2GoAwayFields {
+  Http2GoAwayFields() {}
+  Http2GoAwayFields(uint32_t last_stream_id, Http2ErrorCode error_code)
+      : last_stream_id(last_stream_id), error_code(error_code) {}
+  static constexpr size_t EncodedSize() { return 8; }
+  bool IsSupportedErrorCode() const {
+    return IsSupportedHttp2ErrorCode(error_code);
+  }
+
+  uint32_t last_stream_id;
+  Http2ErrorCode error_code;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2GoAwayFields& a,
+                                     const Http2GoAwayFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2GoAwayFields& a,
+                                            const Http2GoAwayFields& b) {
+  return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const Http2GoAwayFields& v);
+
+// Http2WindowUpdateFields:
+
+struct Http2WindowUpdateFields {
+  static constexpr size_t EncodedSize() { return 4; }
+
+  // 31-bit, unsigned increase in the window size (only positive values are
+  // allowed). The high-bit is reserved for the future.
+  uint32_t window_size_increment;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2WindowUpdateFields& a,
+                                     const Http2WindowUpdateFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2WindowUpdateFields& a,
+                                            const Http2WindowUpdateFields& b) {
+  return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const Http2WindowUpdateFields& v);
+
+// Http2AltSvcFields:
+
+struct Http2AltSvcFields {
+  static constexpr size_t EncodedSize() { return 2; }
+
+  // This is the one fixed size portion of the ALTSVC payload.
+  uint16_t origin_length;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2AltSvcFields& a,
+                                     const Http2AltSvcFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2AltSvcFields& a,
+                                            const Http2AltSvcFields& b) {
+  return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const Http2AltSvcFields& v);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HTTP2_STRUCTURES_H_
diff --git a/http2/http2_structures_test.cc b/http2/http2_structures_test.cc
new file mode 100644
index 0000000..30ebb36
--- /dev/null
+++ b/http2/http2_structures_test.cc
@@ -0,0 +1,537 @@
+// 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 "net/third_party/quiche/src/http2/http2_structures.h"
+
+// Tests are focused on Http2FrameHeader because it has by far the most
+// methods of any of the structures.
+// Note that EXPECT.*DEATH tests are slow (a fork is probably involved).
+
+// And in case you're wondering, yes, these are ridiculously thorough tests,
+// but believe it or not, I've found stupid bugs this way.
+
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <tuple>
+#include <type_traits>
+#include <vector>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::Combine;
+using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
+using ::testing::Not;
+using ::testing::Values;
+using ::testing::ValuesIn;
+
+namespace http2 {
+namespace test {
+namespace {
+
+template <typename E>
+E IncrementEnum(E e) {
+  using I = typename std::underlying_type<E>::type;
+  return static_cast<E>(1 + static_cast<I>(e));
+}
+
+template <class T>
+AssertionResult VerifyRandomCalls() {
+  T t1;
+  Http2Random seq1;
+  Randomize(&t1, &seq1);
+
+  T t2;
+  Http2Random seq2(seq1.Key());
+  Randomize(&t2, &seq2);
+
+  // The two Randomize calls should have made the same number of calls into
+  // the Http2Random implementations.
+  VERIFY_EQ(seq1.Rand64(), seq2.Rand64());
+
+  // And because Http2Random implementation is returning the same sequence, and
+  // Randomize should have been consistent in applying those results, the two
+  // Ts should have the same value.
+  VERIFY_EQ(t1, t2);
+
+  Randomize(&t2, &seq2);
+  VERIFY_NE(t1, t2);
+
+  Randomize(&t1, &seq1);
+  VERIFY_EQ(t1, t2);
+
+  VERIFY_EQ(seq1.Rand64(), seq2.Rand64());
+
+  return AssertionSuccess();
+}
+
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+std::vector<Http2FrameType> ValidFrameTypes() {
+  std::vector<Http2FrameType> valid_types{Http2FrameType::DATA};
+  while (valid_types.back() != Http2FrameType::ALTSVC) {
+    valid_types.push_back(IncrementEnum(valid_types.back()));
+  }
+  return valid_types;
+}
+#endif  // GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+
+TEST(Http2FrameHeaderTest, Constructor) {
+  Http2Random random;
+  uint8_t frame_type = 0;
+  do {
+    // Only the payload length is DCHECK'd in the constructor, so we need to
+    // make sure it is a "uint24".
+    uint32_t payload_length = random.Rand32() & 0xffffff;
+    Http2FrameType type = static_cast<Http2FrameType>(frame_type);
+    uint8_t flags = random.Rand8();
+    uint32_t stream_id = random.Rand32();
+
+    Http2FrameHeader v(payload_length, type, flags, stream_id);
+
+    EXPECT_EQ(payload_length, v.payload_length);
+    EXPECT_EQ(type, v.type);
+    EXPECT_EQ(flags, v.flags);
+    EXPECT_EQ(stream_id, v.stream_id);
+  } while (frame_type++ == 255);
+
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+  EXPECT_DEBUG_DEATH(Http2FrameHeader(0x01000000, Http2FrameType::DATA, 0, 1),
+                     "payload_length");
+#endif  // GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+}
+
+TEST(Http2FrameHeaderTest, Eq) {
+  Http2Random random;
+  uint32_t payload_length = random.Rand32() & 0xffffff;
+  Http2FrameType type = static_cast<Http2FrameType>(random.Rand8());
+
+  uint8_t flags = random.Rand8();
+  uint32_t stream_id = random.Rand32();
+
+  Http2FrameHeader v(payload_length, type, flags, stream_id);
+
+  EXPECT_EQ(payload_length, v.payload_length);
+  EXPECT_EQ(type, v.type);
+  EXPECT_EQ(flags, v.flags);
+  EXPECT_EQ(stream_id, v.stream_id);
+
+  Http2FrameHeader u(0, type, ~flags, stream_id);
+
+  EXPECT_NE(u, v);
+  EXPECT_NE(v, u);
+  EXPECT_FALSE(u == v);
+  EXPECT_FALSE(v == u);
+  EXPECT_TRUE(u != v);
+  EXPECT_TRUE(v != u);
+
+  u = v;
+
+  EXPECT_EQ(u, v);
+  EXPECT_EQ(v, u);
+  EXPECT_TRUE(u == v);
+  EXPECT_TRUE(v == u);
+  EXPECT_FALSE(u != v);
+  EXPECT_FALSE(v != u);
+
+  EXPECT_TRUE(VerifyRandomCalls<Http2FrameHeader>());
+}
+
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+// The tests of the valid frame types include EXPECT_DEBUG_DEATH, which is
+// quite slow, so using value parameterized tests in order to allow sharding.
+class Http2FrameHeaderTypeAndFlagTest
+    : public ::testing::TestWithParam<
+          std::tuple<Http2FrameType, Http2FrameFlag>> {
+ protected:
+  Http2FrameHeaderTypeAndFlagTest()
+      : type_(std::get<0>(GetParam())), flags_(std::get<1>(GetParam())) {
+    LOG(INFO) << "Frame type: " << type_;
+    LOG(INFO) << "Frame flags: " << Http2FrameFlagsToString(type_, flags_);
+  }
+
+  const Http2FrameType type_;
+  const Http2FrameFlag flags_;
+};
+
+class IsEndStreamTest : public Http2FrameHeaderTypeAndFlagTest {};
+INSTANTIATE_TEST_CASE_P(IsEndStream,
+                        IsEndStreamTest,
+                        Combine(ValuesIn(ValidFrameTypes()),
+                                Values(~Http2FrameFlag::END_STREAM, 0xff)));
+TEST_P(IsEndStreamTest, IsEndStream) {
+  const bool is_set =
+      (flags_ & Http2FrameFlag::END_STREAM) == Http2FrameFlag::END_STREAM;
+  Http2String flags_string;
+  Http2FrameHeader v(0, type_, flags_, 0);
+  switch (type_) {
+    case Http2FrameType::DATA:
+    case Http2FrameType::HEADERS:
+      EXPECT_EQ(is_set, v.IsEndStream()) << v;
+      flags_string = v.FlagsToString();
+      if (is_set) {
+        EXPECT_THAT(flags_string, MatchesRegex(".*\\|?END_STREAM\\|.*"));
+      } else {
+        EXPECT_THAT(flags_string, Not(HasSubstr("END_STREAM")));
+      }
+      v.RetainFlags(Http2FrameFlag::END_STREAM);
+      EXPECT_EQ(is_set, v.IsEndStream()) << v;
+      {
+        std::stringstream s;
+        s << v;
+        EXPECT_EQ(v.ToString(), s.str());
+        if (is_set) {
+          EXPECT_THAT(s.str(), HasSubstr("flags=END_STREAM,"));
+        } else {
+          EXPECT_THAT(s.str(), HasSubstr("flags=,"));
+        }
+      }
+      break;
+    default:
+      EXPECT_DEBUG_DEATH(v.IsEndStream(), "DATA.*HEADERS") << v;
+  }
+}
+
+class IsACKTest : public Http2FrameHeaderTypeAndFlagTest {};
+INSTANTIATE_TEST_CASE_P(IsAck,
+                        IsACKTest,
+                        Combine(ValuesIn(ValidFrameTypes()),
+                                Values(~Http2FrameFlag::ACK, 0xff)));
+TEST_P(IsACKTest, IsAck) {
+  const bool is_set = (flags_ & Http2FrameFlag::ACK) == Http2FrameFlag::ACK;
+  Http2String flags_string;
+  Http2FrameHeader v(0, type_, flags_, 0);
+  switch (type_) {
+    case Http2FrameType::SETTINGS:
+    case Http2FrameType::PING:
+      EXPECT_EQ(is_set, v.IsAck()) << v;
+      flags_string = v.FlagsToString();
+      if (is_set) {
+        EXPECT_THAT(flags_string, MatchesRegex(".*\\|?ACK\\|.*"));
+      } else {
+        EXPECT_THAT(flags_string, Not(HasSubstr("ACK")));
+      }
+      v.RetainFlags(Http2FrameFlag::ACK);
+      EXPECT_EQ(is_set, v.IsAck()) << v;
+      {
+        std::stringstream s;
+        s << v;
+        EXPECT_EQ(v.ToString(), s.str());
+        if (is_set) {
+          EXPECT_THAT(s.str(), HasSubstr("flags=ACK,"));
+        } else {
+          EXPECT_THAT(s.str(), HasSubstr("flags=,"));
+        }
+      }
+      break;
+    default:
+      EXPECT_DEBUG_DEATH(v.IsAck(), "SETTINGS.*PING") << v;
+  }
+}
+
+class IsEndHeadersTest : public Http2FrameHeaderTypeAndFlagTest {};
+INSTANTIATE_TEST_CASE_P(IsEndHeaders,
+                        IsEndHeadersTest,
+                        Combine(ValuesIn(ValidFrameTypes()),
+                                Values(~Http2FrameFlag::END_HEADERS, 0xff)));
+TEST_P(IsEndHeadersTest, IsEndHeaders) {
+  const bool is_set =
+      (flags_ & Http2FrameFlag::END_HEADERS) == Http2FrameFlag::END_HEADERS;
+  Http2String flags_string;
+  Http2FrameHeader v(0, type_, flags_, 0);
+  switch (type_) {
+    case Http2FrameType::HEADERS:
+    case Http2FrameType::PUSH_PROMISE:
+    case Http2FrameType::CONTINUATION:
+      EXPECT_EQ(is_set, v.IsEndHeaders()) << v;
+      flags_string = v.FlagsToString();
+      if (is_set) {
+        EXPECT_THAT(flags_string, MatchesRegex(".*\\|?END_HEADERS\\|.*"));
+      } else {
+        EXPECT_THAT(flags_string, Not(HasSubstr("END_HEADERS")));
+      }
+      v.RetainFlags(Http2FrameFlag::END_HEADERS);
+      EXPECT_EQ(is_set, v.IsEndHeaders()) << v;
+      {
+        std::stringstream s;
+        s << v;
+        EXPECT_EQ(v.ToString(), s.str());
+        if (is_set) {
+          EXPECT_THAT(s.str(), HasSubstr("flags=END_HEADERS,"));
+        } else {
+          EXPECT_THAT(s.str(), HasSubstr("flags=,"));
+        }
+      }
+      break;
+    default:
+      EXPECT_DEBUG_DEATH(v.IsEndHeaders(),
+                         "HEADERS.*PUSH_PROMISE.*CONTINUATION")
+          << v;
+  }
+}
+
+class IsPaddedTest : public Http2FrameHeaderTypeAndFlagTest {};
+INSTANTIATE_TEST_CASE_P(IsPadded,
+                        IsPaddedTest,
+                        Combine(ValuesIn(ValidFrameTypes()),
+                                Values(~Http2FrameFlag::PADDED, 0xff)));
+TEST_P(IsPaddedTest, IsPadded) {
+  const bool is_set =
+      (flags_ & Http2FrameFlag::PADDED) == Http2FrameFlag::PADDED;
+  Http2String flags_string;
+  Http2FrameHeader v(0, type_, flags_, 0);
+  switch (type_) {
+    case Http2FrameType::DATA:
+    case Http2FrameType::HEADERS:
+    case Http2FrameType::PUSH_PROMISE:
+      EXPECT_EQ(is_set, v.IsPadded()) << v;
+      flags_string = v.FlagsToString();
+      if (is_set) {
+        EXPECT_THAT(flags_string, MatchesRegex(".*\\|?PADDED\\|.*"));
+      } else {
+        EXPECT_THAT(flags_string, Not(HasSubstr("PADDED")));
+      }
+      v.RetainFlags(Http2FrameFlag::PADDED);
+      EXPECT_EQ(is_set, v.IsPadded()) << v;
+      {
+        std::stringstream s;
+        s << v;
+        EXPECT_EQ(v.ToString(), s.str());
+        if (is_set) {
+          EXPECT_THAT(s.str(), HasSubstr("flags=PADDED,"));
+        } else {
+          EXPECT_THAT(s.str(), HasSubstr("flags=,"));
+        }
+      }
+      break;
+    default:
+      EXPECT_DEBUG_DEATH(v.IsPadded(), "DATA.*HEADERS.*PUSH_PROMISE") << v;
+  }
+}
+
+class HasPriorityTest : public Http2FrameHeaderTypeAndFlagTest {};
+INSTANTIATE_TEST_CASE_P(HasPriority,
+                        HasPriorityTest,
+                        Combine(ValuesIn(ValidFrameTypes()),
+                                Values(~Http2FrameFlag::PRIORITY, 0xff)));
+TEST_P(HasPriorityTest, HasPriority) {
+  const bool is_set =
+      (flags_ & Http2FrameFlag::PRIORITY) == Http2FrameFlag::PRIORITY;
+  Http2String flags_string;
+  Http2FrameHeader v(0, type_, flags_, 0);
+  switch (type_) {
+    case Http2FrameType::HEADERS:
+      EXPECT_EQ(is_set, v.HasPriority()) << v;
+      flags_string = v.FlagsToString();
+      if (is_set) {
+        EXPECT_THAT(flags_string, MatchesRegex(".*\\|?PRIORITY\\|.*"));
+      } else {
+        EXPECT_THAT(flags_string, Not(HasSubstr("PRIORITY")));
+      }
+      v.RetainFlags(Http2FrameFlag::PRIORITY);
+      EXPECT_EQ(is_set, v.HasPriority()) << v;
+      {
+        std::stringstream s;
+        s << v;
+        EXPECT_EQ(v.ToString(), s.str());
+        if (is_set) {
+          EXPECT_THAT(s.str(), HasSubstr("flags=PRIORITY,"));
+        } else {
+          EXPECT_THAT(s.str(), HasSubstr("flags=,"));
+        }
+      }
+      break;
+    default:
+      EXPECT_DEBUG_DEATH(v.HasPriority(), "HEADERS") << v;
+  }
+}
+
+TEST(Http2PriorityFieldsTest, Constructor) {
+  Http2Random random;
+  uint32_t stream_dependency = random.Rand32() & StreamIdMask();
+  uint32_t weight = 1 + random.Rand8();
+  bool is_exclusive = random.OneIn(2);
+
+  Http2PriorityFields v(stream_dependency, weight, is_exclusive);
+
+  EXPECT_EQ(stream_dependency, v.stream_dependency);
+  EXPECT_EQ(weight, v.weight);
+  EXPECT_EQ(is_exclusive, v.is_exclusive);
+
+  // The high-bit must not be set on the stream id.
+  EXPECT_DEBUG_DEATH(
+      Http2PriorityFields(stream_dependency | 0x80000000, weight, is_exclusive),
+      "31-bit");
+
+  // The weight must be in the range 1-256.
+  EXPECT_DEBUG_DEATH(Http2PriorityFields(stream_dependency, 0, is_exclusive),
+                     "too small");
+  EXPECT_DEBUG_DEATH(
+      Http2PriorityFields(stream_dependency, weight + 256, is_exclusive),
+      "too large");
+
+  EXPECT_TRUE(VerifyRandomCalls<Http2PriorityFields>());
+}
+#endif  // GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+
+TEST(Http2RstStreamFieldsTest, IsSupported) {
+  Http2RstStreamFields v{Http2ErrorCode::HTTP2_NO_ERROR};
+  EXPECT_TRUE(v.IsSupportedErrorCode()) << v;
+
+  Http2RstStreamFields u{static_cast<Http2ErrorCode>(~0)};
+  EXPECT_FALSE(u.IsSupportedErrorCode()) << v;
+
+  EXPECT_TRUE(VerifyRandomCalls<Http2RstStreamFields>());
+}
+
+TEST(Http2SettingFieldsTest, Misc) {
+  Http2Random random;
+  Http2SettingsParameter parameter =
+      static_cast<Http2SettingsParameter>(random.Rand16());
+  uint32_t value = random.Rand32();
+
+  Http2SettingFields v(parameter, value);
+
+  EXPECT_EQ(v, v);
+  EXPECT_EQ(parameter, v.parameter);
+  EXPECT_EQ(value, v.value);
+
+  if (static_cast<uint16_t>(parameter) < 7) {
+    EXPECT_TRUE(v.IsSupportedParameter()) << v;
+  } else {
+    EXPECT_FALSE(v.IsSupportedParameter()) << v;
+  }
+
+  Http2SettingFields u(parameter, ~value);
+  EXPECT_NE(v, u);
+  EXPECT_EQ(v.parameter, u.parameter);
+  EXPECT_NE(v.value, u.value);
+
+  Http2SettingFields w(IncrementEnum(parameter), value);
+  EXPECT_NE(v, w);
+  EXPECT_NE(v.parameter, w.parameter);
+  EXPECT_EQ(v.value, w.value);
+
+  Http2SettingFields x(Http2SettingsParameter::MAX_FRAME_SIZE, 123);
+  std::stringstream s;
+  s << x;
+  EXPECT_EQ("parameter=MAX_FRAME_SIZE, value=123", s.str());
+
+  EXPECT_TRUE(VerifyRandomCalls<Http2SettingFields>());
+}
+
+TEST(Http2PushPromiseTest, Misc) {
+  Http2Random random;
+  uint32_t promised_stream_id = random.Rand32() & StreamIdMask();
+
+  Http2PushPromiseFields v{promised_stream_id};
+  EXPECT_EQ(promised_stream_id, v.promised_stream_id);
+  EXPECT_EQ(v, v);
+
+  std::stringstream s;
+  s << v;
+  EXPECT_EQ(Http2StrCat("promised_stream_id=", promised_stream_id), s.str());
+
+  // High-bit is reserved, but not used, so we can set it.
+  promised_stream_id |= 0x80000000;
+  Http2PushPromiseFields w{promised_stream_id};
+  EXPECT_EQ(w, w);
+  EXPECT_NE(v, w);
+
+  v.promised_stream_id = promised_stream_id;
+  EXPECT_EQ(v, w);
+
+  EXPECT_TRUE(VerifyRandomCalls<Http2PushPromiseFields>());
+}
+
+TEST(Http2PingFieldsTest, Misc) {
+  Http2PingFields v{{'8', ' ', 'b', 'y', 't', 'e', 's', '\0'}};
+  std::stringstream s;
+  s << v;
+  EXPECT_EQ("opaque_bytes=0x3820627974657300", s.str());
+
+  EXPECT_TRUE(VerifyRandomCalls<Http2PingFields>());
+}
+
+TEST(Http2GoAwayFieldsTest, Misc) {
+  Http2Random random;
+  uint32_t last_stream_id = random.Rand32() & StreamIdMask();
+  Http2ErrorCode error_code = static_cast<Http2ErrorCode>(random.Rand32());
+
+  Http2GoAwayFields v(last_stream_id, error_code);
+  EXPECT_EQ(v, v);
+  EXPECT_EQ(last_stream_id, v.last_stream_id);
+  EXPECT_EQ(error_code, v.error_code);
+
+  if (static_cast<uint32_t>(error_code) < 14) {
+    EXPECT_TRUE(v.IsSupportedErrorCode()) << v;
+  } else {
+    EXPECT_FALSE(v.IsSupportedErrorCode()) << v;
+  }
+
+  Http2GoAwayFields u(~last_stream_id, error_code);
+  EXPECT_NE(v, u);
+  EXPECT_NE(v.last_stream_id, u.last_stream_id);
+  EXPECT_EQ(v.error_code, u.error_code);
+
+  EXPECT_TRUE(VerifyRandomCalls<Http2GoAwayFields>());
+}
+
+TEST(Http2WindowUpdateTest, Misc) {
+  Http2Random random;
+  uint32_t window_size_increment = random.Rand32() & UInt31Mask();
+
+  Http2WindowUpdateFields v{window_size_increment};
+  EXPECT_EQ(window_size_increment, v.window_size_increment);
+  EXPECT_EQ(v, v);
+
+  std::stringstream s;
+  s << v;
+  EXPECT_EQ(Http2StrCat("window_size_increment=", window_size_increment),
+            s.str());
+
+  // High-bit is reserved, but not used, so we can set it.
+  window_size_increment |= 0x80000000;
+  Http2WindowUpdateFields w{window_size_increment};
+  EXPECT_EQ(w, w);
+  EXPECT_NE(v, w);
+
+  v.window_size_increment = window_size_increment;
+  EXPECT_EQ(v, w);
+
+  EXPECT_TRUE(VerifyRandomCalls<Http2WindowUpdateFields>());
+}
+
+TEST(Http2AltSvcTest, Misc) {
+  Http2Random random;
+  uint16_t origin_length = random.Rand16();
+
+  Http2AltSvcFields v{origin_length};
+  EXPECT_EQ(origin_length, v.origin_length);
+  EXPECT_EQ(v, v);
+
+  std::stringstream s;
+  s << v;
+  EXPECT_EQ(Http2StrCat("origin_length=", origin_length), s.str());
+
+  Http2AltSvcFields w{++origin_length};
+  EXPECT_EQ(w, w);
+  EXPECT_NE(v, w);
+
+  v.origin_length = w.origin_length;
+  EXPECT_EQ(v, w);
+
+  EXPECT_TRUE(VerifyRandomCalls<Http2AltSvcFields>());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/http2_structures_test_util.cc b/http2/http2_structures_test_util.cc
new file mode 100644
index 0000000..4a1a74d
--- /dev/null
+++ b/http2/http2_structures_test_util.cc
@@ -0,0 +1,109 @@
+// 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 "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+void Randomize(Http2FrameHeader* p, Http2Random* rng) {
+  p->payload_length = rng->Rand32() & 0xffffff;
+  p->type = static_cast<Http2FrameType>(rng->Rand8());
+  p->flags = static_cast<Http2FrameFlag>(rng->Rand8());
+  p->stream_id = rng->Rand32() & StreamIdMask();
+}
+void Randomize(Http2PriorityFields* p, Http2Random* rng) {
+  p->stream_dependency = rng->Rand32() & StreamIdMask();
+  p->weight = rng->Rand8() + 1;
+  p->is_exclusive = rng->OneIn(2);
+}
+void Randomize(Http2RstStreamFields* p, Http2Random* rng) {
+  p->error_code = static_cast<Http2ErrorCode>(rng->Rand32());
+}
+void Randomize(Http2SettingFields* p, Http2Random* rng) {
+  p->parameter = static_cast<Http2SettingsParameter>(rng->Rand16());
+  p->value = rng->Rand32();
+}
+void Randomize(Http2PushPromiseFields* p, Http2Random* rng) {
+  p->promised_stream_id = rng->Rand32() & StreamIdMask();
+}
+void Randomize(Http2PingFields* p, Http2Random* rng) {
+  for (int ndx = 0; ndx < 8; ++ndx) {
+    p->opaque_bytes[ndx] = rng->Rand8();
+  }
+}
+void Randomize(Http2GoAwayFields* p, Http2Random* rng) {
+  p->last_stream_id = rng->Rand32() & StreamIdMask();
+  p->error_code = static_cast<Http2ErrorCode>(rng->Rand32());
+}
+void Randomize(Http2WindowUpdateFields* p, Http2Random* rng) {
+  p->window_size_increment = rng->Rand32() & 0x7fffffff;
+}
+void Randomize(Http2AltSvcFields* p, Http2Random* rng) {
+  p->origin_length = rng->Rand16();
+}
+
+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/http2/http2_structures_test_util.h b/http2/http2_structures_test_util.h
new file mode 100644
index 0000000..86fbf3f
--- /dev/null
+++ b/http2/http2_structures_test_util.h
@@ -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.
+
+#ifndef QUICHE_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_
+#define QUICHE_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+
+namespace http2 {
+namespace test {
+
+template <class S>
+Http2String 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);
+
+// 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_HTTP2_STRUCTURES_TEST_UTIL_H_
diff --git a/http2/platform/api/http2_arraysize.h b/http2/platform/api/http2_arraysize.h
new file mode 100644
index 0000000..417e53b
--- /dev/null
+++ b/http2/platform/api/http2_arraysize.h
@@ -0,0 +1,12 @@
+// Copyright 2018 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_PLATFORM_API_HTTP2_ARRAYSIZE_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_ARRAYSIZE_H_
+
+#include "net/http2/platform/impl/http2_arraysize_impl.h"
+
+#define HTTP2_ARRAYSIZE(x) HTTP2_ARRAYSIZE_IMPL(x)
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_ARRAYSIZE_H_
diff --git a/http2/platform/api/http2_bug_tracker.h b/http2/platform/api/http2_bug_tracker.h
new file mode 100644
index 0000000..93ad677
--- /dev/null
+++ b/http2/platform/api/http2_bug_tracker.h
@@ -0,0 +1,15 @@
+// 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_PLATFORM_API_HTTP2_BUG_TRACKER_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_BUG_TRACKER_H_
+
+#include "net/http2/platform/impl/http2_bug_tracker_impl.h"
+
+#define HTTP2_BUG HTTP2_BUG_IMPL
+#define HTTP2_BUG_IF HTTP2_BUG_IF_IMPL
+#define FLAGS_http2_always_log_bugs_for_tests \
+  FLAGS_http2_always_log_bugs_for_tests_IMPL
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_BUG_TRACKER_H_
diff --git a/http2/platform/api/http2_containers.h b/http2/platform/api/http2_containers.h
new file mode 100644
index 0000000..e748e7b
--- /dev/null
+++ b/http2/platform/api/http2_containers.h
@@ -0,0 +1,17 @@
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_
+
+#include "net/http2/platform/impl/http2_containers_impl.h"
+
+namespace http2 {
+
+// Represents a double-ended queue which may be backed by a list or a flat
+// circular buffer.
+//
+// DOES NOT GUARANTEE POINTER OR ITERATOR STABILITY!
+template <typename T>
+using Http2Deque = Http2DequeImpl<T>;
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_
diff --git a/http2/platform/api/http2_estimate_memory_usage.h b/http2/platform/api/http2_estimate_memory_usage.h
new file mode 100644
index 0000000..fd405fa
--- /dev/null
+++ b/http2/platform/api/http2_estimate_memory_usage.h
@@ -0,0 +1,21 @@
+// 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.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_
+
+#include <cstddef>
+
+#include "net/http2/platform/impl/http2_estimate_memory_usage_impl.h"
+
+namespace http2 {
+
+template <class T>
+size_t Http2EstimateMemoryUsage(const T& object) {
+  return Http2EstimateMemoryUsageImpl(object);
+}
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_
diff --git a/http2/platform/api/http2_export.h b/http2/platform/api/http2_export.h
new file mode 100644
index 0000000..e262f74
--- /dev/null
+++ b/http2/platform/api/http2_export.h
@@ -0,0 +1,10 @@
+// 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.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_EXPORT_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_EXPORT_H_
+
+#include "net/http2/platform/impl/http2_export_impl.h"
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_EXPORT_H_
diff --git a/http2/platform/api/http2_flag_utils.h b/http2/platform/api/http2_flag_utils.h
new file mode 100644
index 0000000..4303c43
--- /dev/null
+++ b/http2/platform/api/http2_flag_utils.h
@@ -0,0 +1,12 @@
+// Copyright 2018 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_PLATFORM_API_HTTP2_FLAG_UTILS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAG_UTILS_H_
+
+#include "net/http2/platform/impl/http2_flag_utils_impl.h"
+
+#define HTTP2_RELOADABLE_FLAG_COUNT HTTP2_RELOADABLE_FLAG_COUNT_IMPL
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAG_UTILS_H_
diff --git a/http2/platform/api/http2_flags.h b/http2/platform/api/http2_flags.h
new file mode 100644
index 0000000..08f95da
--- /dev/null
+++ b/http2/platform/api/http2_flags.h
@@ -0,0 +1,14 @@
+// Copyright 2018 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_PLATFORM_API_HTTP2_FLAGS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAGS_H_
+
+#include "net/http2/platform/impl/http2_flags_impl.h"
+
+#define GetHttp2ReloadableFlag(flag) GetHttp2ReloadableFlagImpl(flag)
+#define SetHttp2ReloadableFlag(flag, value) \
+  SetHttp2ReloadableFlagImpl(flag, value)
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAGS_H_
diff --git a/http2/platform/api/http2_macros.h b/http2/platform/api/http2_macros.h
new file mode 100644
index 0000000..0be5e89
--- /dev/null
+++ b/http2/platform/api/http2_macros.h
@@ -0,0 +1,10 @@
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_
+
+#include "net/http2/platform/impl/http2_macros_impl.h"
+
+#define HTTP2_FALLTHROUGH HTTP2_FALLTHROUGH_IMPL
+#define HTTP2_UNREACHABLE() HTTP2_UNREACHABLE_IMPL()
+#define HTTP2_DIE_IF_NULL(ptr) HTTP2_DIE_IF_NULL_IMPL(ptr)
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_
diff --git a/http2/platform/api/http2_mock_log.h b/http2/platform/api/http2_mock_log.h
new file mode 100644
index 0000000..3d16837
--- /dev/null
+++ b/http2/platform/api/http2_mock_log.h
@@ -0,0 +1,18 @@
+// Copyright 2018 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_PLATFORM_API_HTTP2_MOCK_LOG_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_MOCK_LOG_H_
+
+#include "net/http2/platform/impl/http2_mock_log_impl.h"
+
+using Http2MockLog = Http2MockLogImpl;
+#define CREATE_HTTP2_MOCK_LOG(log) CREATE_HTTP2_MOCK_LOG_IMPL(log)
+
+#define EXPECT_HTTP2_LOG_CALL(log) EXPECT_HTTP2_LOG_CALL_IMPL(log)
+
+#define EXPECT_HTTP2_LOG_CALL_CONTAINS(log, level, content) \
+  EXPECT_HTTP2_LOG_CALL_CONTAINS_IMPL(log, level, content)
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_MOCK_LOG_H_
diff --git a/http2/platform/api/http2_optional.h b/http2/platform/api/http2_optional.h
new file mode 100644
index 0000000..64ce5f5
--- /dev/null
+++ b/http2/platform/api/http2_optional.h
@@ -0,0 +1,19 @@
+// Copyright 2018 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_PLATFORM_API_HTTP2_OPTIONAL_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_OPTIONAL_H_
+
+#include <utility>
+
+#include "net/http2/platform/impl/http2_optional_impl.h"
+
+namespace http2 {
+
+template <typename T>
+using Http2Optional = Http2OptionalImpl<T>;
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_OPTIONAL_H_
diff --git a/http2/platform/api/http2_ptr_util.h b/http2/platform/api/http2_ptr_util.h
new file mode 100644
index 0000000..2530e7c
--- /dev/null
+++ b/http2/platform/api/http2_ptr_util.h
@@ -0,0 +1,22 @@
+// Copyright 2018 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_PLATFORM_API_HTTP2_PTR_UTIL_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_PTR_UTIL_H_
+
+#include <memory>
+#include <utility>
+
+#include "net/http2/platform/impl/http2_ptr_util_impl.h"
+
+namespace http2 {
+
+template <typename T, typename... Args>
+std::unique_ptr<T> Http2MakeUnique(Args&&... args) {
+  return Http2MakeUniqueImpl<T>(std::forward<Args>(args)...);
+}
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_PTR_UTIL_H_
diff --git a/http2/platform/api/http2_reconstruct_object.h b/http2/platform/api/http2_reconstruct_object.h
new file mode 100644
index 0000000..ddcb312
--- /dev/null
+++ b/http2/platform/api/http2_reconstruct_object.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_
+
+#include <utility>
+
+#include "net/http2/platform/impl/http2_reconstruct_object_impl.h"
+
+namespace http2 {
+namespace test {
+
+class Http2Random;
+
+// Reconstruct an object so that it is initialized as when it was first
+// constructed. Runs the destructor to handle objects that might own resources,
+// and runs the constructor with the provided arguments, if any.
+template <class T, class... Args>
+void Http2ReconstructObject(T* ptr, Http2Random* rng, Args&&... args) {
+  Http2ReconstructObjectImpl(ptr, rng, std::forward<Args>(args)...);
+}
+
+// This version applies default-initialization to the object.
+template <class T>
+void Http2DefaultReconstructObject(T* ptr, Http2Random* rng) {
+  Http2DefaultReconstructObjectImpl(ptr, rng);
+}
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_
diff --git a/http2/platform/api/http2_string.h b/http2/platform/api/http2_string.h
new file mode 100644
index 0000000..25f9e59
--- /dev/null
+++ b/http2/platform/api/http2_string.h
@@ -0,0 +1,16 @@
+// Copyright (c) 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.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_H_
+
+#include "net/http2/platform/impl/http2_string_impl.h"
+
+namespace http2 {
+
+using Http2String = Http2StringImpl;
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_H_
diff --git a/http2/platform/api/http2_string_piece.h b/http2/platform/api/http2_string_piece.h
new file mode 100644
index 0000000..92fb3ef
--- /dev/null
+++ b/http2/platform/api/http2_string_piece.h
@@ -0,0 +1,16 @@
+// Copyright (c) 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.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_PIECE_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_PIECE_H_
+
+#include "net/http2/platform/impl/http2_string_piece_impl.h"
+
+namespace http2 {
+
+using Http2StringPiece = Http2StringPieceImpl;
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_PIECE_H_
diff --git a/http2/platform/api/http2_string_utils.h b/http2/platform/api/http2_string_utils.h
new file mode 100644
index 0000000..ba40560
--- /dev/null
+++ b/http2/platform/api/http2_string_utils.h
@@ -0,0 +1,56 @@
+// 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.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_
+
+#include <type_traits>
+#include <utility>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/http2/platform/impl/http2_string_utils_impl.h"
+
+namespace http2 {
+
+template <typename... Args>
+inline Http2String Http2StrCat(const Args&... args) {
+  return Http2StrCatImpl(std::forward<const Args&>(args)...);
+}
+
+template <typename... Args>
+inline void Http2StrAppend(Http2String* output, const Args&... args) {
+  Http2StrAppendImpl(output, std::forward<const Args&>(args)...);
+}
+
+template <typename... Args>
+inline Http2String Http2StringPrintf(const Args&... args) {
+  return Http2StringPrintfImpl(std::forward<const Args&>(args)...);
+}
+
+inline Http2String Http2HexEncode(const void* bytes, size_t size) {
+  return Http2HexEncodeImpl(bytes, size);
+}
+
+inline Http2String Http2HexDecode(Http2StringPiece data) {
+  return Http2HexDecodeImpl(data);
+}
+
+inline Http2String Http2HexDump(Http2StringPiece data) {
+  return Http2HexDumpImpl(data);
+}
+
+inline Http2String Http2HexEscape(Http2StringPiece data) {
+  return Http2HexEscapeImpl(data);
+}
+
+template <typename Number>
+inline Http2String Http2Hex(Number number) {
+  static_assert(std::is_integral<Number>::value, "Number has to be an int");
+  return Http2HexImpl(number);
+}
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_
diff --git a/http2/platform/api/http2_string_utils_test.cc b/http2/platform/api/http2_string_utils_test.cc
new file mode 100644
index 0000000..254f9d7
--- /dev/null
+++ b/http2/platform/api/http2_string_utils_test.cc
@@ -0,0 +1,178 @@
+// 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 "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+TEST(Http2StringUtilsTest, Http2StrCat) {
+  // No arguments.
+  EXPECT_EQ("", Http2StrCat());
+
+  // Single string-like argument.
+  const char kFoo[] = "foo";
+  const Http2String string_foo(kFoo);
+  const Http2StringPiece stringpiece_foo(string_foo);
+  EXPECT_EQ("foo", Http2StrCat(kFoo));
+  EXPECT_EQ("foo", Http2StrCat(string_foo));
+  EXPECT_EQ("foo", Http2StrCat(stringpiece_foo));
+
+  // Two string-like arguments.
+  const char kBar[] = "bar";
+  const Http2StringPiece stringpiece_bar(kBar);
+  const Http2String string_bar(kBar);
+  EXPECT_EQ("foobar", Http2StrCat(kFoo, kBar));
+  EXPECT_EQ("foobar", Http2StrCat(kFoo, string_bar));
+  EXPECT_EQ("foobar", Http2StrCat(kFoo, stringpiece_bar));
+  EXPECT_EQ("foobar", Http2StrCat(string_foo, kBar));
+  EXPECT_EQ("foobar", Http2StrCat(string_foo, string_bar));
+  EXPECT_EQ("foobar", Http2StrCat(string_foo, stringpiece_bar));
+  EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, kBar));
+  EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, string_bar));
+  EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, stringpiece_bar));
+
+  // Many-many arguments.
+  EXPECT_EQ(
+      "foobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud",
+      Http2StrCat("foo", "bar", "baz", "qux", "quux", "quuz", "corge", "grault",
+                  "garply", "waldo", "fred", "plugh", "xyzzy", "thud"));
+
+  // Numerical arguments.
+  const int16_t i = 1;
+  const uint64_t u = 8;
+  const double d = 3.1415;
+
+  EXPECT_EQ("1 8", Http2StrCat(i, " ", u));
+  EXPECT_EQ("3.14151181", Http2StrCat(d, i, i, u, i));
+  EXPECT_EQ("i: 1, u: 8, d: 3.1415",
+            Http2StrCat("i: ", i, ", u: ", u, ", d: ", d));
+
+  // Boolean arguments.
+  const bool t = true;
+  const bool f = false;
+
+  EXPECT_EQ("1", Http2StrCat(t));
+  EXPECT_EQ("0", Http2StrCat(f));
+  EXPECT_EQ("0110", Http2StrCat(f, t, t, f));
+
+  // Mixed string-like, numerical, and Boolean arguments.
+  EXPECT_EQ("foo1foo081bar3.14151",
+            Http2StrCat(kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t));
+  EXPECT_EQ("3.141511bar18bar13.14150",
+            Http2StrCat(d, t, t, string_bar, i, u, kBar, t, d, f));
+}
+
+TEST(Http2StringUtilsTest, Http2StrAppend) {
+  // No arguments on empty string.
+  Http2String output;
+  Http2StrAppend(&output);
+  EXPECT_TRUE(output.empty());
+
+  // Single string-like argument.
+  const char kFoo[] = "foo";
+  const Http2String string_foo(kFoo);
+  const Http2StringPiece stringpiece_foo(string_foo);
+  Http2StrAppend(&output, kFoo);
+  EXPECT_EQ("foo", output);
+  Http2StrAppend(&output, string_foo);
+  EXPECT_EQ("foofoo", output);
+  Http2StrAppend(&output, stringpiece_foo);
+  EXPECT_EQ("foofoofoo", output);
+
+  // No arguments on non-empty string.
+  Http2StrAppend(&output);
+  EXPECT_EQ("foofoofoo", output);
+
+  output.clear();
+
+  // Two string-like arguments.
+  const char kBar[] = "bar";
+  const Http2StringPiece stringpiece_bar(kBar);
+  const Http2String string_bar(kBar);
+  Http2StrAppend(&output, kFoo, kBar);
+  EXPECT_EQ("foobar", output);
+  Http2StrAppend(&output, kFoo, string_bar);
+  EXPECT_EQ("foobarfoobar", output);
+  Http2StrAppend(&output, kFoo, stringpiece_bar);
+  EXPECT_EQ("foobarfoobarfoobar", output);
+  Http2StrAppend(&output, string_foo, kBar);
+  EXPECT_EQ("foobarfoobarfoobarfoobar", output);
+
+  output.clear();
+
+  Http2StrAppend(&output, string_foo, string_bar);
+  EXPECT_EQ("foobar", output);
+  Http2StrAppend(&output, string_foo, stringpiece_bar);
+  EXPECT_EQ("foobarfoobar", output);
+  Http2StrAppend(&output, stringpiece_foo, kBar);
+  EXPECT_EQ("foobarfoobarfoobar", output);
+  Http2StrAppend(&output, stringpiece_foo, string_bar);
+  EXPECT_EQ("foobarfoobarfoobarfoobar", output);
+
+  output.clear();
+
+  Http2StrAppend(&output, stringpiece_foo, stringpiece_bar);
+  EXPECT_EQ("foobar", output);
+
+  // Many-many arguments.
+  Http2StrAppend(&output, "foo", "bar", "baz", "qux", "quux", "quuz", "corge",
+                 "grault", "garply", "waldo", "fred", "plugh", "xyzzy", "thud");
+  EXPECT_EQ(
+      "foobarfoobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud",
+      output);
+
+  output.clear();
+
+  // Numerical arguments.
+  const int16_t i = 1;
+  const uint64_t u = 8;
+  const double d = 3.1415;
+
+  Http2StrAppend(&output, i, " ", u);
+  EXPECT_EQ("1 8", output);
+  Http2StrAppend(&output, d, i, i, u, i);
+  EXPECT_EQ("1 83.14151181", output);
+  Http2StrAppend(&output, "i: ", i, ", u: ", u, ", d: ", d);
+  EXPECT_EQ("1 83.14151181i: 1, u: 8, d: 3.1415", output);
+
+  output.clear();
+
+  // Boolean arguments.
+  const bool t = true;
+  const bool f = false;
+
+  Http2StrAppend(&output, t);
+  EXPECT_EQ("1", output);
+  Http2StrAppend(&output, f);
+  EXPECT_EQ("10", output);
+  Http2StrAppend(&output, f, t, t, f);
+  EXPECT_EQ("100110", output);
+
+  output.clear();
+
+  // Mixed string-like, numerical, and Boolean arguments.
+  Http2StrAppend(&output, kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t);
+  EXPECT_EQ("foo1foo081bar3.14151", output);
+  Http2StrAppend(&output, d, t, t, string_bar, i, u, kBar, t, d, f);
+  EXPECT_EQ("foo1foo081bar3.141513.141511bar18bar13.14150", output);
+}
+
+TEST(Http2StringUtilsTest, Http2StringPrintf) {
+  EXPECT_EQ("", Http2StringPrintf("%s", ""));
+  EXPECT_EQ("foobar", Http2StringPrintf("%sbar", "foo"));
+  EXPECT_EQ("foobar", Http2StringPrintf("%s%s", "foo", "bar"));
+  EXPECT_EQ("foo: 1, bar: 2.0",
+            Http2StringPrintf("foo: %d, bar: %.1f", 1, 2.0));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/platform/api/http2_test_helpers.h b/http2/platform/api/http2_test_helpers.h
new file mode 100644
index 0000000..a54f4ab
--- /dev/null
+++ b/http2/platform/api/http2_test_helpers.h
@@ -0,0 +1,16 @@
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_
+
+// Provides VERIFY_* macros, similar to EXPECT_* and ASSERT_*, but they return
+// an AssertionResult if the condition is not satisfied.
+#include "net/http2/platform/impl/http2_test_helpers_impl.h"
+
+#include "testing/gtest/include/gtest/gtest.h"  // For AssertionSuccess
+
+#define VERIFY_AND_RETURN_SUCCESS(expression) \
+  {                                           \
+    VERIFY_SUCCESS(expression);               \
+    return ::testing::AssertionSuccess();     \
+  }
+
+#endif  // QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_
diff --git a/http2/test_tools/frame_parts.cc b/http2/test_tools/frame_parts.cc
new file mode 100644
index 0000000..3d0453e
--- /dev/null
+++ b/http2/test_tools/frame_parts.cc
@@ -0,0 +1,526 @@
+// 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 "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+
+#include <type_traits>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::ContainerEq;
+
+namespace http2 {
+namespace test {
+namespace {
+
+static_assert(std::is_base_of<Http2FrameDecoderListener, FrameParts>::value &&
+                  !std::is_abstract<FrameParts>::value,
+              "FrameParts needs to implement all of the methods of "
+              "Http2FrameDecoderListener");
+
+// Compare two optional variables of the same type.
+// TODO(jamessynge): Maybe create a ::testing::Matcher for this.
+template <class T>
+AssertionResult VerifyOptionalEq(const T& opt_a, const T& opt_b) {
+  if (opt_a) {
+    if (opt_b) {
+      VERIFY_EQ(opt_a.value(), opt_b.value());
+    } else {
+      return AssertionFailure()
+             << "opt_b is not set; opt_a.value()=" << opt_a.value();
+    }
+  } else if (opt_b) {
+    return AssertionFailure()
+           << "opt_a is not set; opt_b.value()=" << opt_b.value();
+  }
+  return AssertionSuccess();
+}
+
+}  // namespace
+
+FrameParts::FrameParts(const Http2FrameHeader& header) : frame_header_(header) {
+  VLOG(1) << "FrameParts, header: " << frame_header_;
+}
+
+FrameParts::FrameParts(const Http2FrameHeader& header, Http2StringPiece payload)
+    : FrameParts(header) {
+  VLOG(1) << "FrameParts with payload.size() = " << payload.size();
+  this->payload_.append(payload.data(), payload.size());
+  opt_payload_length_ = payload.size();
+}
+FrameParts::FrameParts(const Http2FrameHeader& header,
+                       Http2StringPiece payload,
+                       size_t total_pad_length)
+    : FrameParts(header, payload) {
+  VLOG(1) << "FrameParts with total_pad_length=" << total_pad_length;
+  SetTotalPadLength(total_pad_length);
+}
+
+FrameParts::FrameParts(const FrameParts& header) = default;
+
+FrameParts::~FrameParts() = default;
+
+AssertionResult FrameParts::VerifyEquals(const FrameParts& that) const {
+#define COMMON_MESSAGE "\n  this: " << *this << "\n  that: " << that
+
+  VERIFY_EQ(frame_header_, that.frame_header_) << COMMON_MESSAGE;
+  VERIFY_EQ(payload_, that.payload_) << COMMON_MESSAGE;
+  VERIFY_EQ(padding_, that.padding_) << COMMON_MESSAGE;
+  VERIFY_EQ(altsvc_origin_, that.altsvc_origin_) << COMMON_MESSAGE;
+  VERIFY_EQ(altsvc_value_, that.altsvc_value_) << COMMON_MESSAGE;
+  VERIFY_THAT(settings_, ContainerEq(that.settings_)) << COMMON_MESSAGE;
+
+#define VERIFY_OPTIONAL_FIELD(field_name) \
+  VERIFY_SUCCESS(VerifyOptionalEq(field_name, that.field_name))
+
+  VERIFY_OPTIONAL_FIELD(opt_altsvc_origin_length_) << COMMON_MESSAGE;
+  VERIFY_OPTIONAL_FIELD(opt_altsvc_value_length_) << COMMON_MESSAGE;
+  VERIFY_OPTIONAL_FIELD(opt_goaway_) << COMMON_MESSAGE;
+  VERIFY_OPTIONAL_FIELD(opt_missing_length_) << COMMON_MESSAGE;
+  VERIFY_OPTIONAL_FIELD(opt_pad_length_) << COMMON_MESSAGE;
+  VERIFY_OPTIONAL_FIELD(opt_ping_) << COMMON_MESSAGE;
+  VERIFY_OPTIONAL_FIELD(opt_priority_) << COMMON_MESSAGE;
+  VERIFY_OPTIONAL_FIELD(opt_push_promise_) << COMMON_MESSAGE;
+  VERIFY_OPTIONAL_FIELD(opt_rst_stream_error_code_) << COMMON_MESSAGE;
+  VERIFY_OPTIONAL_FIELD(opt_window_update_increment_) << COMMON_MESSAGE;
+
+#undef VERIFY_OPTIONAL_FIELD
+
+  return AssertionSuccess();
+}
+
+void FrameParts::SetTotalPadLength(size_t total_pad_length) {
+  opt_pad_length_.reset();
+  padding_.clear();
+  if (total_pad_length > 0) {
+    ASSERT_LE(total_pad_length, 256u);
+    ASSERT_TRUE(frame_header_.IsPadded());
+    opt_pad_length_ = total_pad_length - 1;
+    char zero = 0;
+    padding_.append(opt_pad_length_.value(), zero);
+  }
+
+  if (opt_pad_length_) {
+    VLOG(1) << "SetTotalPadLength: pad_length=" << opt_pad_length_.value();
+  } else {
+    VLOG(1) << "SetTotalPadLength: has no pad length";
+  }
+}
+
+void FrameParts::SetAltSvcExpected(Http2StringPiece origin,
+                                   Http2StringPiece value) {
+  altsvc_origin_.append(origin.data(), origin.size());
+  altsvc_value_.append(value.data(), value.size());
+  opt_altsvc_origin_length_ = origin.size();
+  opt_altsvc_value_length_ = value.size();
+}
+
+bool FrameParts::OnFrameHeader(const Http2FrameHeader& header) {
+  ADD_FAILURE() << "OnFrameHeader: " << *this;
+  return true;
+}
+
+void FrameParts::OnDataStart(const Http2FrameHeader& header) {
+  VLOG(1) << "OnDataStart: " << header;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::DATA)) << *this;
+  opt_payload_length_ = header.payload_length;
+}
+
+void FrameParts::OnDataPayload(const char* data, size_t len) {
+  VLOG(1) << "OnDataPayload: len=" << len
+          << "; frame_header_: " << frame_header_;
+  ASSERT_TRUE(InFrameOfType(Http2FrameType::DATA)) << *this;
+  ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_,
+                           &opt_payload_length_));
+}
+
+void FrameParts::OnDataEnd() {
+  VLOG(1) << "OnDataEnd; frame_header_: " << frame_header_;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::DATA)) << *this;
+}
+
+void FrameParts::OnHeadersStart(const Http2FrameHeader& header) {
+  VLOG(1) << "OnHeadersStart: " << header;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::HEADERS)) << *this;
+  opt_payload_length_ = header.payload_length;
+}
+
+void FrameParts::OnHeadersPriority(const Http2PriorityFields& priority) {
+  VLOG(1) << "OnHeadersPriority: priority: " << priority
+          << "; frame_header_: " << frame_header_;
+  ASSERT_TRUE(InFrameOfType(Http2FrameType::HEADERS)) << *this;
+  ASSERT_FALSE(opt_priority_);
+  opt_priority_ = priority;
+  ASSERT_TRUE(opt_payload_length_);
+  opt_payload_length_ =
+      opt_payload_length_.value() - Http2PriorityFields::EncodedSize();
+}
+
+void FrameParts::OnHpackFragment(const char* data, size_t len) {
+  VLOG(1) << "OnHpackFragment: len=" << len
+          << "; frame_header_: " << frame_header_;
+  ASSERT_TRUE(got_start_callback_);
+  ASSERT_FALSE(got_end_callback_);
+  ASSERT_TRUE(FrameCanHaveHpackPayload(frame_header_)) << *this;
+  ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_,
+                           &opt_payload_length_));
+}
+
+void FrameParts::OnHeadersEnd() {
+  VLOG(1) << "OnHeadersEnd; frame_header_: " << frame_header_;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::HEADERS)) << *this;
+}
+
+void FrameParts::OnPriorityFrame(const Http2FrameHeader& header,
+                                 const Http2PriorityFields& priority) {
+  VLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PRIORITY)) << *this;
+  ASSERT_FALSE(opt_priority_);
+  opt_priority_ = priority;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::PRIORITY)) << *this;
+}
+
+void FrameParts::OnContinuationStart(const Http2FrameHeader& header) {
+  VLOG(1) << "OnContinuationStart: " << header;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::CONTINUATION)) << *this;
+  opt_payload_length_ = header.payload_length;
+}
+
+void FrameParts::OnContinuationEnd() {
+  VLOG(1) << "OnContinuationEnd; frame_header_: " << frame_header_;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::CONTINUATION)) << *this;
+}
+
+void FrameParts::OnPadLength(size_t trailing_length) {
+  VLOG(1) << "OnPadLength: trailing_length=" << trailing_length;
+  ASSERT_TRUE(InPaddedFrame()) << *this;
+  ASSERT_FALSE(opt_pad_length_);
+  ASSERT_TRUE(opt_payload_length_);
+  size_t total_padding_length = trailing_length + 1;
+  ASSERT_GE(opt_payload_length_.value(), total_padding_length);
+  opt_payload_length_ = opt_payload_length_.value() - total_padding_length;
+  opt_pad_length_ = trailing_length;
+}
+
+void FrameParts::OnPadding(const char* pad, size_t skipped_length) {
+  VLOG(1) << "OnPadding: skipped_length=" << skipped_length;
+  ASSERT_TRUE(InPaddedFrame()) << *this;
+  ASSERT_TRUE(opt_pad_length_);
+  ASSERT_TRUE(AppendString(Http2StringPiece(pad, skipped_length), &padding_,
+                           &opt_pad_length_));
+}
+
+void FrameParts::OnRstStream(const Http2FrameHeader& header,
+                             Http2ErrorCode error_code) {
+  VLOG(1) << "OnRstStream: " << header << "; code=" << error_code;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::RST_STREAM)) << *this;
+  ASSERT_FALSE(opt_rst_stream_error_code_);
+  opt_rst_stream_error_code_ = error_code;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::RST_STREAM)) << *this;
+}
+
+void FrameParts::OnSettingsStart(const Http2FrameHeader& header) {
+  VLOG(1) << "OnSettingsStart: " << header;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::SETTINGS)) << *this;
+  ASSERT_EQ(0u, settings_.size());
+  ASSERT_FALSE(header.IsAck()) << header;
+}
+
+void FrameParts::OnSetting(const Http2SettingFields& setting_fields) {
+  VLOG(1) << "OnSetting: " << setting_fields;
+  ASSERT_TRUE(InFrameOfType(Http2FrameType::SETTINGS)) << *this;
+  settings_.push_back(setting_fields);
+}
+
+void FrameParts::OnSettingsEnd() {
+  VLOG(1) << "OnSettingsEnd; frame_header_: " << frame_header_;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::SETTINGS)) << *this;
+}
+
+void FrameParts::OnSettingsAck(const Http2FrameHeader& header) {
+  VLOG(1) << "OnSettingsAck: " << header;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::SETTINGS)) << *this;
+  ASSERT_EQ(0u, settings_.size());
+  ASSERT_TRUE(header.IsAck());
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::SETTINGS)) << *this;
+}
+
+void FrameParts::OnPushPromiseStart(const Http2FrameHeader& header,
+                                    const Http2PushPromiseFields& promise,
+                                    size_t total_padding_length) {
+  VLOG(1) << "OnPushPromiseStart header: " << header << "; promise: " << promise
+          << "; total_padding_length: " << total_padding_length;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PUSH_PROMISE)) << *this;
+  ASSERT_GE(header.payload_length, Http2PushPromiseFields::EncodedSize());
+  opt_payload_length_ =
+      header.payload_length - Http2PushPromiseFields::EncodedSize();
+  ASSERT_FALSE(opt_push_promise_);
+  opt_push_promise_ = promise;
+  if (total_padding_length > 0) {
+    ASSERT_GE(opt_payload_length_.value(), total_padding_length);
+    OnPadLength(total_padding_length - 1);
+  } else {
+    ASSERT_FALSE(header.IsPadded());
+  }
+}
+
+void FrameParts::OnPushPromiseEnd() {
+  VLOG(1) << "OnPushPromiseEnd; frame_header_: " << frame_header_;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::PUSH_PROMISE)) << *this;
+}
+
+void FrameParts::OnPing(const Http2FrameHeader& header,
+                        const Http2PingFields& ping) {
+  VLOG(1) << "OnPing header: " << header << "   ping: " << ping;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PING)) << *this;
+  ASSERT_FALSE(header.IsAck());
+  ASSERT_FALSE(opt_ping_);
+  opt_ping_ = ping;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::PING)) << *this;
+}
+
+void FrameParts::OnPingAck(const Http2FrameHeader& header,
+                           const Http2PingFields& ping) {
+  VLOG(1) << "OnPingAck header: " << header << "   ping: " << ping;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PING)) << *this;
+  ASSERT_TRUE(header.IsAck());
+  ASSERT_FALSE(opt_ping_);
+  opt_ping_ = ping;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::PING)) << *this;
+}
+
+void FrameParts::OnGoAwayStart(const Http2FrameHeader& header,
+                               const Http2GoAwayFields& goaway) {
+  VLOG(1) << "OnGoAwayStart: " << goaway;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::GOAWAY)) << *this;
+  ASSERT_FALSE(opt_goaway_);
+  opt_goaway_ = goaway;
+  opt_payload_length_ =
+      header.payload_length - Http2GoAwayFields::EncodedSize();
+}
+
+void FrameParts::OnGoAwayOpaqueData(const char* data, size_t len) {
+  VLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+  ASSERT_TRUE(InFrameOfType(Http2FrameType::GOAWAY)) << *this;
+  ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_,
+                           &opt_payload_length_));
+}
+
+void FrameParts::OnGoAwayEnd() {
+  VLOG(1) << "OnGoAwayEnd; frame_header_: " << frame_header_;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::GOAWAY)) << *this;
+}
+
+void FrameParts::OnWindowUpdate(const Http2FrameHeader& header,
+                                uint32_t increment) {
+  VLOG(1) << "OnWindowUpdate header: " << header
+          << "     increment=" << increment;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::WINDOW_UPDATE)) << *this;
+  ASSERT_FALSE(opt_window_update_increment_);
+  opt_window_update_increment_ = increment;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::WINDOW_UPDATE)) << *this;
+}
+
+void FrameParts::OnAltSvcStart(const Http2FrameHeader& header,
+                               size_t origin_length,
+                               size_t value_length) {
+  VLOG(1) << "OnAltSvcStart: " << header
+          << "    origin_length: " << origin_length
+          << "    value_length: " << value_length;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::ALTSVC)) << *this;
+  ASSERT_FALSE(opt_altsvc_origin_length_);
+  opt_altsvc_origin_length_ = origin_length;
+  ASSERT_FALSE(opt_altsvc_value_length_);
+  opt_altsvc_value_length_ = value_length;
+}
+
+void FrameParts::OnAltSvcOriginData(const char* data, size_t len) {
+  VLOG(1) << "OnAltSvcOriginData: len=" << len;
+  ASSERT_TRUE(InFrameOfType(Http2FrameType::ALTSVC)) << *this;
+  ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &altsvc_origin_,
+                           &opt_altsvc_origin_length_));
+}
+
+void FrameParts::OnAltSvcValueData(const char* data, size_t len) {
+  VLOG(1) << "OnAltSvcValueData: len=" << len;
+  ASSERT_TRUE(InFrameOfType(Http2FrameType::ALTSVC)) << *this;
+  ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &altsvc_value_,
+                           &opt_altsvc_value_length_));
+}
+
+void FrameParts::OnAltSvcEnd() {
+  VLOG(1) << "OnAltSvcEnd; frame_header_: " << frame_header_;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::ALTSVC)) << *this;
+}
+
+void FrameParts::OnUnknownStart(const Http2FrameHeader& header) {
+  VLOG(1) << "OnUnknownStart: " << header;
+  ASSERT_FALSE(IsSupportedHttp2FrameType(header.type)) << header;
+  ASSERT_FALSE(got_start_callback_);
+  ASSERT_EQ(frame_header_, header);
+  got_start_callback_ = true;
+  opt_payload_length_ = header.payload_length;
+}
+
+void FrameParts::OnUnknownPayload(const char* data, size_t len) {
+  VLOG(1) << "OnUnknownPayload: len=" << len;
+  ASSERT_FALSE(IsSupportedHttp2FrameType(frame_header_.type)) << *this;
+  ASSERT_TRUE(got_start_callback_);
+  ASSERT_FALSE(got_end_callback_);
+  ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_,
+                           &opt_payload_length_));
+}
+
+void FrameParts::OnUnknownEnd() {
+  VLOG(1) << "OnUnknownEnd; frame_header_: " << frame_header_;
+  ASSERT_FALSE(IsSupportedHttp2FrameType(frame_header_.type)) << *this;
+  ASSERT_TRUE(got_start_callback_);
+  ASSERT_FALSE(got_end_callback_);
+  got_end_callback_ = true;
+}
+
+void FrameParts::OnPaddingTooLong(const Http2FrameHeader& header,
+                                  size_t missing_length) {
+  VLOG(1) << "OnPaddingTooLong: " << header
+          << "; missing_length: " << missing_length;
+  ASSERT_EQ(frame_header_, header);
+  ASSERT_FALSE(got_end_callback_);
+  ASSERT_TRUE(FrameIsPadded(header));
+  ASSERT_FALSE(opt_pad_length_);
+  ASSERT_FALSE(opt_missing_length_);
+  opt_missing_length_ = missing_length;
+  got_start_callback_ = true;
+  got_end_callback_ = true;
+}
+
+void FrameParts::OnFrameSizeError(const Http2FrameHeader& header) {
+  VLOG(1) << "OnFrameSizeError: " << header;
+  ASSERT_EQ(frame_header_, header);
+  ASSERT_FALSE(got_end_callback_);
+  ASSERT_FALSE(has_frame_size_error_);
+  has_frame_size_error_ = true;
+  got_end_callback_ = true;
+}
+
+void FrameParts::OutputTo(std::ostream& out) const {
+  out << "FrameParts{\n  frame_header_: " << frame_header_ << "\n";
+  if (!payload_.empty()) {
+    out << "  payload_=\"" << Http2HexEscape(payload_) << "\"\n";
+  }
+  if (!padding_.empty()) {
+    out << "  padding_=\"" << Http2HexEscape(padding_) << "\"\n";
+  }
+  if (!altsvc_origin_.empty()) {
+    out << "  altsvc_origin_=\"" << Http2HexEscape(altsvc_origin_) << "\"\n";
+  }
+  if (!altsvc_value_.empty()) {
+    out << "  altsvc_value_=\"" << Http2HexEscape(altsvc_value_) << "\"\n";
+  }
+  if (opt_priority_) {
+    out << "  priority=" << opt_priority_.value() << "\n";
+  }
+  if (opt_rst_stream_error_code_) {
+    out << "  rst_stream=" << opt_rst_stream_error_code_.value() << "\n";
+  }
+  if (opt_push_promise_) {
+    out << "  push_promise=" << opt_push_promise_.value() << "\n";
+  }
+  if (opt_ping_) {
+    out << "  ping=" << opt_ping_.value() << "\n";
+  }
+  if (opt_goaway_) {
+    out << "  goaway=" << opt_goaway_.value() << "\n";
+  }
+  if (opt_window_update_increment_) {
+    out << "  window_update=" << opt_window_update_increment_.value() << "\n";
+  }
+  if (opt_payload_length_) {
+    out << "  payload_length=" << opt_payload_length_.value() << "\n";
+  }
+  if (opt_pad_length_) {
+    out << "  pad_length=" << opt_pad_length_.value() << "\n";
+  }
+  if (opt_missing_length_) {
+    out << "  missing_length=" << opt_missing_length_.value() << "\n";
+  }
+  if (opt_altsvc_origin_length_) {
+    out << "  origin_length=" << opt_altsvc_origin_length_.value() << "\n";
+  }
+  if (opt_altsvc_value_length_) {
+    out << "  value_length=" << opt_altsvc_value_length_.value() << "\n";
+  }
+  if (has_frame_size_error_) {
+    out << "  has_frame_size_error\n";
+  }
+  if (got_start_callback_) {
+    out << "  got_start_callback\n";
+  }
+  if (got_end_callback_) {
+    out << "  got_end_callback\n";
+  }
+  for (size_t ndx = 0; ndx < settings_.size(); ++ndx) {
+    out << "  setting[" << ndx << "]=" << settings_[ndx];
+  }
+  out << "}";
+}
+
+AssertionResult FrameParts::StartFrameOfType(
+    const Http2FrameHeader& header,
+    Http2FrameType expected_frame_type) {
+  VERIFY_EQ(header.type, expected_frame_type);
+  VERIFY_FALSE(got_start_callback_);
+  VERIFY_FALSE(got_end_callback_);
+  VERIFY_EQ(frame_header_, header);
+  got_start_callback_ = true;
+  return AssertionSuccess();
+}
+
+AssertionResult FrameParts::InFrameOfType(Http2FrameType expected_frame_type) {
+  VERIFY_TRUE(got_start_callback_);
+  VERIFY_FALSE(got_end_callback_);
+  VERIFY_EQ(frame_header_.type, expected_frame_type);
+  return AssertionSuccess();
+}
+
+AssertionResult FrameParts::EndFrameOfType(Http2FrameType expected_frame_type) {
+  VERIFY_SUCCESS(InFrameOfType(expected_frame_type));
+  got_end_callback_ = true;
+  return AssertionSuccess();
+}
+
+AssertionResult FrameParts::InPaddedFrame() {
+  VERIFY_TRUE(got_start_callback_);
+  VERIFY_FALSE(got_end_callback_);
+  VERIFY_TRUE(FrameIsPadded(frame_header_));
+  return AssertionSuccess();
+}
+
+AssertionResult FrameParts::AppendString(Http2StringPiece source,
+                                         Http2String* target,
+                                         Http2Optional<size_t>* opt_length) {
+  target->append(source.data(), source.size());
+  if (opt_length != nullptr) {
+    VERIFY_TRUE(*opt_length) << "Length is not set yet\n" << *this;
+    VERIFY_LE(target->size(), opt_length->value())
+        << "String too large; source.size() = " << source.size() << "\n"
+        << *this;
+  }
+  return ::testing::AssertionSuccess();
+}
+
+std::ostream& operator<<(std::ostream& out, const FrameParts& v) {
+  v.OutputTo(out);
+  return out;
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/test_tools/frame_parts.h b/http2/test_tools/frame_parts.h
new file mode 100644
index 0000000..b531174
--- /dev/null
+++ b/http2/test_tools/frame_parts.h
@@ -0,0 +1,249 @@
+// 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_PARTS_H_
+#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_H_
+
+// FrameParts implements Http2FrameDecoderListener, recording the callbacks
+// during the decoding of a single frame. It is also used for comparing the
+// info that a test expects to be recorded during the decoding of a frame
+// with the actual recorded value (i.e. by providing a comparator).
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_optional.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+class FrameParts : public Http2FrameDecoderListener {
+ public:
+  // The first callback for every type of frame includes the frame header; this
+  // is the only constructor used during decoding of a frame.
+  explicit FrameParts(const Http2FrameHeader& header);
+
+  // For use in tests where the expected frame has a variable size payload.
+  FrameParts(const Http2FrameHeader& header, Http2StringPiece payload);
+
+  // For use in tests where the expected frame has a variable size payload
+  // and may be padded.
+  FrameParts(const Http2FrameHeader& header,
+             Http2StringPiece payload,
+             size_t total_pad_length);
+
+  // Copy constructor.
+  FrameParts(const FrameParts& header);
+
+  ~FrameParts() override;
+
+  // Returns AssertionSuccess() if they're equal, else AssertionFailure()
+  // with info about the difference.
+  ::testing::AssertionResult VerifyEquals(const FrameParts& other) const;
+
+  // Format this FrameParts object.
+  void OutputTo(std::ostream& out) const;
+
+  // Set the total padding length (0 to 256).
+  void SetTotalPadLength(size_t total_pad_length);
+
+  // Set the origin and value expected in an ALTSVC frame.
+  void SetAltSvcExpected(Http2StringPiece origin, Http2StringPiece value);
+
+  // Http2FrameDecoderListener methods:
+  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* pad, 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 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;
+
+  void AppendSetting(const Http2SettingFields& setting_fields) {
+    settings_.push_back(setting_fields);
+  }
+
+  const Http2FrameHeader& GetFrameHeader() const { return frame_header_; }
+
+  Http2Optional<Http2PriorityFields> GetOptPriority() const {
+    return opt_priority_;
+  }
+  Http2Optional<Http2ErrorCode> GetOptRstStreamErrorCode() const {
+    return opt_rst_stream_error_code_;
+  }
+  Http2Optional<Http2PushPromiseFields> GetOptPushPromise() const {
+    return opt_push_promise_;
+  }
+  Http2Optional<Http2PingFields> GetOptPing() const { return opt_ping_; }
+  Http2Optional<Http2GoAwayFields> GetOptGoaway() const { return opt_goaway_; }
+  Http2Optional<size_t> GetOptPadLength() const { return opt_pad_length_; }
+  Http2Optional<size_t> GetOptPayloadLength() const {
+    return opt_payload_length_;
+  }
+  Http2Optional<size_t> GetOptMissingLength() const {
+    return opt_missing_length_;
+  }
+  Http2Optional<size_t> GetOptAltsvcOriginLength() const {
+    return opt_altsvc_origin_length_;
+  }
+  Http2Optional<size_t> GetOptAltsvcValueLength() const {
+    return opt_altsvc_value_length_;
+  }
+  Http2Optional<size_t> GetOptWindowUpdateIncrement() const {
+    return opt_window_update_increment_;
+  }
+  bool GetHasFrameSizeError() const { return has_frame_size_error_; }
+
+  void SetOptPriority(Http2Optional<Http2PriorityFields> opt_priority) {
+    opt_priority_ = opt_priority;
+  }
+  void SetOptRstStreamErrorCode(
+      Http2Optional<Http2ErrorCode> opt_rst_stream_error_code) {
+    opt_rst_stream_error_code_ = opt_rst_stream_error_code;
+  }
+  void SetOptPushPromise(
+      Http2Optional<Http2PushPromiseFields> opt_push_promise) {
+    opt_push_promise_ = opt_push_promise;
+  }
+  void SetOptPing(Http2Optional<Http2PingFields> opt_ping) {
+    opt_ping_ = opt_ping;
+  }
+  void SetOptGoaway(Http2Optional<Http2GoAwayFields> opt_goaway) {
+    opt_goaway_ = opt_goaway;
+  }
+  void SetOptPadLength(Http2Optional<size_t> opt_pad_length) {
+    opt_pad_length_ = opt_pad_length;
+  }
+  void SetOptPayloadLength(Http2Optional<size_t> opt_payload_length) {
+    opt_payload_length_ = opt_payload_length;
+  }
+  void SetOptMissingLength(Http2Optional<size_t> opt_missing_length) {
+    opt_missing_length_ = opt_missing_length;
+  }
+  void SetOptAltsvcOriginLength(
+      Http2Optional<size_t> opt_altsvc_origin_length) {
+    opt_altsvc_origin_length_ = opt_altsvc_origin_length;
+  }
+  void SetOptAltsvcValueLength(Http2Optional<size_t> opt_altsvc_value_length) {
+    opt_altsvc_value_length_ = opt_altsvc_value_length;
+  }
+  void SetOptWindowUpdateIncrement(
+      Http2Optional<size_t> opt_window_update_increment) {
+    opt_window_update_increment_ = opt_window_update_increment;
+  }
+
+  void SetHasFrameSizeError(bool has_frame_size_error) {
+    has_frame_size_error_ = has_frame_size_error;
+  }
+
+ private:
+  // ASSERT during an On* method that we're handling a frame of type
+  // expected_frame_type, and have not already received other On* methods
+  // (i.e. got_start_callback is false).
+  ::testing::AssertionResult StartFrameOfType(
+      const Http2FrameHeader& header,
+      Http2FrameType expected_frame_type);
+
+  // ASSERT that StartFrameOfType has already been called with
+  // expected_frame_type (i.e. got_start_callback has been called), and that
+  // EndFrameOfType has not yet been called (i.e. got_end_callback is false).
+  ::testing::AssertionResult InFrameOfType(Http2FrameType expected_frame_type);
+
+  // ASSERT that we're InFrameOfType, and then sets got_end_callback=true.
+  ::testing::AssertionResult EndFrameOfType(Http2FrameType expected_frame_type);
+
+  // ASSERT that we're in the middle of processing a frame that is padded.
+  ::testing::AssertionResult InPaddedFrame();
+
+  // Append source to target. If opt_length is not nullptr, then verifies that
+  // the optional has a value (i.e. that the necessary On*Start method has been
+  // called), and that target is not longer than opt_length->value().
+  ::testing::AssertionResult AppendString(Http2StringPiece source,
+                                          Http2String* target,
+                                          Http2Optional<size_t>* opt_length);
+
+  const Http2FrameHeader frame_header_;
+
+  Http2String payload_;
+  Http2String padding_;
+  Http2String altsvc_origin_;
+  Http2String altsvc_value_;
+
+  Http2Optional<Http2PriorityFields> opt_priority_;
+  Http2Optional<Http2ErrorCode> opt_rst_stream_error_code_;
+  Http2Optional<Http2PushPromiseFields> opt_push_promise_;
+  Http2Optional<Http2PingFields> opt_ping_;
+  Http2Optional<Http2GoAwayFields> opt_goaway_;
+
+  Http2Optional<size_t> opt_pad_length_;
+  Http2Optional<size_t> opt_payload_length_;
+  Http2Optional<size_t> opt_missing_length_;
+  Http2Optional<size_t> opt_altsvc_origin_length_;
+  Http2Optional<size_t> opt_altsvc_value_length_;
+
+  Http2Optional<size_t> opt_window_update_increment_;
+
+  bool has_frame_size_error_ = false;
+
+  std::vector<Http2SettingFields> settings_;
+
+  // These booleans are not checked by CompareCollectedFrames.
+  bool got_start_callback_ = false;
+  bool got_end_callback_ = false;
+};
+
+std::ostream& operator<<(std::ostream& out, const FrameParts& v);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_H_
diff --git a/http2/test_tools/frame_parts_collector.cc b/http2/test_tools/frame_parts_collector.cc
new file mode 100644
index 0000000..be4d986
--- /dev/null
+++ b/http2/test_tools/frame_parts_collector.cc
@@ -0,0 +1,113 @@
+// 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 "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_ptr_util.h"
+
+namespace http2 {
+namespace test {
+
+FramePartsCollector::FramePartsCollector() = default;
+FramePartsCollector::~FramePartsCollector() = default;
+
+void FramePartsCollector::Reset() {
+  current_frame_.reset();
+  collected_frames_.clear();
+  expected_header_set_ = false;
+}
+
+const FrameParts* FramePartsCollector::frame(size_t n) const {
+  if (n < size()) {
+    return collected_frames_.at(n).get();
+  }
+  CHECK(n == size());
+  return current_frame();
+}
+
+void FramePartsCollector::ExpectFrameHeader(const Http2FrameHeader& header) {
+  EXPECT_FALSE(IsInProgress());
+  EXPECT_FALSE(expected_header_set_)
+      << "expected_header_: " << expected_header_;
+  expected_header_ = header;
+  expected_header_set_ = true;
+  // OnFrameHeader is called before the flags are scrubbed, but the other
+  // methods are called after, so scrub the invalid flags from expected_header_.
+  ScrubFlagsOfHeader(&expected_header_);
+}
+
+void FramePartsCollector::TestExpectedHeader(const Http2FrameHeader& header) {
+  if (expected_header_set_) {
+    EXPECT_EQ(header, expected_header_);
+    expected_header_set_ = false;
+  }
+}
+
+Http2FrameDecoderListener* FramePartsCollector::StartFrame(
+    const Http2FrameHeader& header) {
+  TestExpectedHeader(header);
+  EXPECT_FALSE(IsInProgress());
+  if (current_frame_ == nullptr) {
+    current_frame_ = Http2MakeUnique<FrameParts>(header);
+  }
+  return current_frame();
+}
+
+Http2FrameDecoderListener* FramePartsCollector::StartAndEndFrame(
+    const Http2FrameHeader& header) {
+  TestExpectedHeader(header);
+  EXPECT_FALSE(IsInProgress());
+  if (current_frame_ == nullptr) {
+    current_frame_ = Http2MakeUnique<FrameParts>(header);
+  }
+  Http2FrameDecoderListener* result = current_frame();
+  collected_frames_.push_back(std::move(current_frame_));
+  return result;
+}
+
+Http2FrameDecoderListener* FramePartsCollector::CurrentFrame() {
+  EXPECT_TRUE(IsInProgress());
+  if (current_frame_ == nullptr) {
+    return &failing_listener_;
+  }
+  return current_frame();
+}
+
+Http2FrameDecoderListener* FramePartsCollector::EndFrame() {
+  EXPECT_TRUE(IsInProgress());
+  if (current_frame_ == nullptr) {
+    return &failing_listener_;
+  }
+  Http2FrameDecoderListener* result = current_frame();
+  collected_frames_.push_back(std::move(current_frame_));
+  return result;
+}
+
+Http2FrameDecoderListener* FramePartsCollector::FrameError(
+    const Http2FrameHeader& header) {
+  TestExpectedHeader(header);
+  if (current_frame_ == nullptr) {
+    // The decoder may detect an error before making any calls to the listener
+    // regarding the frame, in which case current_frame_==nullptr and we need
+    // to create a FrameParts instance.
+    current_frame_ = Http2MakeUnique<FrameParts>(header);
+  } else {
+    // Similarly, the decoder may have made calls to the listener regarding the
+    // frame before detecting the error; for example, the DATA payload decoder
+    // calls OnDataStart before it can detect padding errors, hence before it
+    // can call OnPaddingTooLong.
+    EXPECT_EQ(header, current_frame_->GetFrameHeader());
+  }
+  Http2FrameDecoderListener* result = current_frame();
+  collected_frames_.push_back(std::move(current_frame_));
+  return result;
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/test_tools/frame_parts_collector.h b/http2/test_tools/frame_parts_collector.h
new file mode 100644
index 0000000..a35740a
--- /dev/null
+++ b/http2/test_tools/frame_parts_collector.h
@@ -0,0 +1,111 @@
+// 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_PARTS_COLLECTOR_H_
+#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_H_
+
+// FramePartsCollector is a base class for Http2FrameDecoderListener
+// implementations that create one FrameParts instance for each decoded frame.
+
+#include <stddef.h>
+
+#include <memory>
+#include <vector>
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+
+namespace http2 {
+namespace test {
+
+class FramePartsCollector : public FailingHttp2FrameDecoderListener {
+ public:
+  FramePartsCollector();
+  ~FramePartsCollector() override;
+
+  // Toss out the collected data.
+  void Reset();
+
+  // Returns true if has started recording the info for a frame and has not yet
+  // finished doing so.
+  bool IsInProgress() const { return current_frame_ != nullptr; }
+
+  // Returns the FrameParts instance into which we're currently recording
+  // callback info if IsInProgress, else nullptr.
+  const FrameParts* current_frame() const { return current_frame_.get(); }
+
+  // Returns the number of completely collected FrameParts instances.
+  size_t size() const { return collected_frames_.size(); }
+
+  // Returns the n'th frame, where 0 is the oldest of the collected frames,
+  // and n==size() is the frame currently being collected, if there is one.
+  // Returns nullptr if the requested index is not valid.
+  const FrameParts* frame(size_t n) const;
+
+ protected:
+  // In support of OnFrameHeader, set the header that we expect to be used in
+  // the next call.
+  // TODO(jamessynge): Remove ExpectFrameHeader et al. once done with supporting
+  // SpdyFramer's exact states.
+  void ExpectFrameHeader(const Http2FrameHeader& header);
+
+  // For use in implementing On*Start methods of Http2FrameDecoderListener,
+  // returns a FrameParts instance, which will be newly created if
+  // IsInProgress==false (which the caller should ensure), else will be the
+  // current_frame(); never returns nullptr.
+  // If called when IsInProgress==true, a test failure will be recorded.
+  Http2FrameDecoderListener* StartFrame(const Http2FrameHeader& header);
+
+  // For use in implementing On* callbacks, such as OnPingAck, that are the only
+  // call expected for the frame being decoded; not for On*Start methods.
+  // Returns a FrameParts instance, which will be newly created if
+  // IsInProgress==false (which the caller should ensure), else will be the
+  // current_frame(); never returns nullptr.
+  // If called when IsInProgress==true, a test failure will be recorded.
+  Http2FrameDecoderListener* StartAndEndFrame(const Http2FrameHeader& header);
+
+  // If IsInProgress==true, returns the FrameParts into which the current
+  // frame is being recorded; else records a test failure and returns
+  // failing_listener_, which will record a test failure when any of its
+  // On* methods is called.
+  Http2FrameDecoderListener* CurrentFrame();
+
+  // For use in implementing On*End methods, pushes the current frame onto
+  // the vector of completed frames, and returns a pointer to it for recording
+  // the info in the final call. If IsInProgress==false, records a test failure
+  // and returns failing_listener_, which will record a test failure when any
+  // of its On* methods is called.
+  Http2FrameDecoderListener* EndFrame();
+
+  // For use in implementing OnPaddingTooLong and OnFrameSizeError, is
+  // equivalent to EndFrame() if IsInProgress==true, else equivalent to
+  // StartAndEndFrame().
+  Http2FrameDecoderListener* FrameError(const Http2FrameHeader& header);
+
+ private:
+  // Returns the mutable FrameParts instance into which we're currently
+  // recording callback info if IsInProgress, else nullptr.
+  FrameParts* current_frame() { return current_frame_.get(); }
+
+  // If expected header is set, verify that it matches the header param.
+  // TODO(jamessynge): Remove TestExpectedHeader et al. once done
+  // with supporting SpdyFramer's exact states.
+  void TestExpectedHeader(const Http2FrameHeader& header);
+
+  std::unique_ptr<FrameParts> current_frame_;
+  std::vector<std::unique_ptr<FrameParts>> collected_frames_;
+  FailingHttp2FrameDecoderListener failing_listener_;
+
+  // TODO(jamessynge): Remove expected_header_ et al. once done with supporting
+  // SpdyFramer's exact states.
+  Http2FrameHeader expected_header_;
+  bool expected_header_set_ = false;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_H_
diff --git a/http2/test_tools/frame_parts_collector_listener.cc b/http2/test_tools/frame_parts_collector_listener.cc
new file mode 100644
index 0000000..8d9da78
--- /dev/null
+++ b/http2/test_tools/frame_parts_collector_listener.cc
@@ -0,0 +1,230 @@
+// 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 "net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace http2 {
+namespace test {
+
+bool FramePartsCollectorListener::OnFrameHeader(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnFrameHeader: " << header;
+  ExpectFrameHeader(header);
+  return true;
+}
+
+void FramePartsCollectorListener::OnDataStart(const Http2FrameHeader& header) {
+  VLOG(1) << "OnDataStart: " << header;
+  StartFrame(header)->OnDataStart(header);
+}
+
+void FramePartsCollectorListener::OnDataPayload(const char* data, size_t len) {
+  VLOG(1) << "OnDataPayload: len=" << len;
+  CurrentFrame()->OnDataPayload(data, len);
+}
+
+void FramePartsCollectorListener::OnDataEnd() {
+  VLOG(1) << "OnDataEnd";
+  EndFrame()->OnDataEnd();
+}
+
+void FramePartsCollectorListener::OnHeadersStart(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnHeadersStart: " << header;
+  StartFrame(header)->OnHeadersStart(header);
+}
+
+void FramePartsCollectorListener::OnHeadersPriority(
+    const Http2PriorityFields& priority) {
+  VLOG(1) << "OnHeadersPriority: " << priority;
+  CurrentFrame()->OnHeadersPriority(priority);
+}
+
+void FramePartsCollectorListener::OnHpackFragment(const char* data,
+                                                  size_t len) {
+  VLOG(1) << "OnHpackFragment: len=" << len;
+  CurrentFrame()->OnHpackFragment(data, len);
+}
+
+void FramePartsCollectorListener::OnHeadersEnd() {
+  VLOG(1) << "OnHeadersEnd";
+  EndFrame()->OnHeadersEnd();
+}
+
+void FramePartsCollectorListener::OnPriorityFrame(
+    const Http2FrameHeader& header,
+    const Http2PriorityFields& priority_fields) {
+  VLOG(1) << "OnPriority: " << header << "; " << priority_fields;
+  StartAndEndFrame(header)->OnPriorityFrame(header, priority_fields);
+}
+
+void FramePartsCollectorListener::OnContinuationStart(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnContinuationStart: " << header;
+  StartFrame(header)->OnContinuationStart(header);
+}
+
+void FramePartsCollectorListener::OnContinuationEnd() {
+  VLOG(1) << "OnContinuationEnd";
+  EndFrame()->OnContinuationEnd();
+}
+
+void FramePartsCollectorListener::OnPadLength(size_t pad_length) {
+  VLOG(1) << "OnPadLength: " << pad_length;
+  CurrentFrame()->OnPadLength(pad_length);
+}
+
+void FramePartsCollectorListener::OnPadding(const char* padding,
+                                            size_t skipped_length) {
+  VLOG(1) << "OnPadding: " << skipped_length;
+  CurrentFrame()->OnPadding(padding, skipped_length);
+}
+
+void FramePartsCollectorListener::OnRstStream(const Http2FrameHeader& header,
+                                              Http2ErrorCode error_code) {
+  VLOG(1) << "OnRstStream: " << header << "; error_code=" << error_code;
+  StartAndEndFrame(header)->OnRstStream(header, error_code);
+}
+
+void FramePartsCollectorListener::OnSettingsStart(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnSettingsStart: " << header;
+  EXPECT_EQ(Http2FrameType::SETTINGS, header.type) << header;
+  EXPECT_EQ(Http2FrameFlag(), header.flags) << header;
+  StartFrame(header)->OnSettingsStart(header);
+}
+
+void FramePartsCollectorListener::OnSetting(
+    const Http2SettingFields& setting_fields) {
+  VLOG(1) << "Http2SettingFields: setting_fields=" << setting_fields;
+  CurrentFrame()->OnSetting(setting_fields);
+}
+
+void FramePartsCollectorListener::OnSettingsEnd() {
+  VLOG(1) << "OnSettingsEnd";
+  EndFrame()->OnSettingsEnd();
+}
+
+void FramePartsCollectorListener::OnSettingsAck(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnSettingsAck: " << header;
+  StartAndEndFrame(header)->OnSettingsAck(header);
+}
+
+void FramePartsCollectorListener::OnPushPromiseStart(
+    const Http2FrameHeader& header,
+    const Http2PushPromiseFields& promise,
+    size_t total_padding_length) {
+  VLOG(1) << "OnPushPromiseStart header: " << header << "  promise: " << promise
+          << "  total_padding_length: " << total_padding_length;
+  EXPECT_EQ(Http2FrameType::PUSH_PROMISE, header.type);
+  StartFrame(header)->OnPushPromiseStart(header, promise, total_padding_length);
+}
+
+void FramePartsCollectorListener::OnPushPromiseEnd() {
+  VLOG(1) << "OnPushPromiseEnd";
+  EndFrame()->OnPushPromiseEnd();
+}
+
+void FramePartsCollectorListener::OnPing(const Http2FrameHeader& header,
+                                         const Http2PingFields& ping) {
+  VLOG(1) << "OnPing: " << header << "; " << ping;
+  StartAndEndFrame(header)->OnPing(header, ping);
+}
+
+void FramePartsCollectorListener::OnPingAck(const Http2FrameHeader& header,
+                                            const Http2PingFields& ping) {
+  VLOG(1) << "OnPingAck: " << header << "; " << ping;
+  StartAndEndFrame(header)->OnPingAck(header, ping);
+}
+
+void FramePartsCollectorListener::OnGoAwayStart(
+    const Http2FrameHeader& header,
+    const Http2GoAwayFields& goaway) {
+  VLOG(1) << "OnGoAwayStart header: " << header << "; goaway: " << goaway;
+  StartFrame(header)->OnGoAwayStart(header, goaway);
+}
+
+void FramePartsCollectorListener::OnGoAwayOpaqueData(const char* data,
+                                                     size_t len) {
+  VLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+  CurrentFrame()->OnGoAwayOpaqueData(data, len);
+}
+
+void FramePartsCollectorListener::OnGoAwayEnd() {
+  VLOG(1) << "OnGoAwayEnd";
+  EndFrame()->OnGoAwayEnd();
+}
+
+void FramePartsCollectorListener::OnWindowUpdate(
+    const Http2FrameHeader& header,
+    uint32_t window_size_increment) {
+  VLOG(1) << "OnWindowUpdate: " << header
+          << "; window_size_increment=" << window_size_increment;
+  EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, header.type);
+  StartAndEndFrame(header)->OnWindowUpdate(header, window_size_increment);
+}
+
+void FramePartsCollectorListener::OnAltSvcStart(const Http2FrameHeader& header,
+                                                size_t origin_length,
+                                                size_t value_length) {
+  VLOG(1) << "OnAltSvcStart header: " << header
+          << "; origin_length=" << origin_length
+          << "; value_length=" << value_length;
+  StartFrame(header)->OnAltSvcStart(header, origin_length, value_length);
+}
+
+void FramePartsCollectorListener::OnAltSvcOriginData(const char* data,
+                                                     size_t len) {
+  VLOG(1) << "OnAltSvcOriginData: len=" << len;
+  CurrentFrame()->OnAltSvcOriginData(data, len);
+}
+
+void FramePartsCollectorListener::OnAltSvcValueData(const char* data,
+                                                    size_t len) {
+  VLOG(1) << "OnAltSvcValueData: len=" << len;
+  CurrentFrame()->OnAltSvcValueData(data, len);
+}
+
+void FramePartsCollectorListener::OnAltSvcEnd() {
+  VLOG(1) << "OnAltSvcEnd";
+  EndFrame()->OnAltSvcEnd();
+}
+
+void FramePartsCollectorListener::OnUnknownStart(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnUnknownStart: " << header;
+  StartFrame(header)->OnUnknownStart(header);
+}
+
+void FramePartsCollectorListener::OnUnknownPayload(const char* data,
+                                                   size_t len) {
+  VLOG(1) << "OnUnknownPayload: len=" << len;
+  CurrentFrame()->OnUnknownPayload(data, len);
+}
+
+void FramePartsCollectorListener::OnUnknownEnd() {
+  VLOG(1) << "OnUnknownEnd";
+  EndFrame()->OnUnknownEnd();
+}
+
+void FramePartsCollectorListener::OnPaddingTooLong(
+    const Http2FrameHeader& header,
+    size_t missing_length) {
+  VLOG(1) << "OnPaddingTooLong: " << header
+          << "    missing_length: " << missing_length;
+  EndFrame()->OnPaddingTooLong(header, missing_length);
+}
+
+void FramePartsCollectorListener::OnFrameSizeError(
+    const Http2FrameHeader& header) {
+  VLOG(1) << "OnFrameSizeError: " << header;
+  FrameError(header)->OnFrameSizeError(header);
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/test_tools/frame_parts_collector_listener.h b/http2/test_tools/frame_parts_collector_listener.h
new file mode 100644
index 0000000..07a82aa
--- /dev/null
+++ b/http2/test_tools/frame_parts_collector_listener.h
@@ -0,0 +1,85 @@
+// 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_PARTS_COLLECTOR_LISTENER_H_
+#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_LISTENER_H_
+
+// FramePartsCollectorListener extends FramePartsCollector with an
+// implementation of every method of Http2FrameDecoderListener; it is
+// essentially the union of all the Listener classes in the tests of the
+// payload decoders (i.e. in ./payload_decoders/*_test.cc files), with the
+// addition of the OnFrameHeader method.
+// FramePartsCollectorListener supports tests of Http2FrameDecoder.
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+
+namespace http2 {
+namespace test {
+
+class FramePartsCollectorListener : public FramePartsCollector {
+ public:
+  FramePartsCollectorListener() {}
+  ~FramePartsCollectorListener() 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_fields) override;
+  void OnContinuationStart(const Http2FrameHeader& header) override;
+  void OnContinuationEnd() override;
+  void OnPadLength(size_t pad_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 window_size_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 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;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_LISTENER_H_
diff --git a/http2/test_tools/http2_random.cc b/http2/test_tools/http2_random.cc
new file mode 100644
index 0000000..fc577f4
--- /dev/null
+++ b/http2/test_tools/http2_random.cc
@@ -0,0 +1,72 @@
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "third_party/boringssl/src/include/openssl/chacha.h"
+#include "third_party/boringssl/src/include/openssl/rand.h"
+
+static const uint8_t kZeroNonce[12] = {0};
+
+namespace http2 {
+namespace test {
+
+Http2Random::Http2Random() {
+  RAND_bytes(key_, sizeof(key_));
+
+  LOG(INFO) << "Initialized test RNG with the following key: " << Key();
+}
+
+Http2Random::Http2Random(Http2StringPiece key) {
+  Http2String decoded_key = Http2HexDecode(key);
+  CHECK_EQ(sizeof(key_), decoded_key.size());
+  memcpy(key_, decoded_key.data(), sizeof(key_));
+}
+
+Http2String Http2Random::Key() const {
+  return Http2HexEncode(key_, sizeof(key_));
+}
+
+void Http2Random::FillRandom(void* buffer, size_t buffer_size) {
+  memset(buffer, 0, buffer_size);
+  uint8_t* buffer_u8 = reinterpret_cast<uint8_t*>(buffer);
+  CRYPTO_chacha_20(buffer_u8, buffer_u8, buffer_size, key_, kZeroNonce,
+                   counter_++);
+}
+
+Http2String Http2Random::RandString(int length) {
+  Http2String result;
+  result.resize(length);
+  FillRandom(&result[0], length);
+  return result;
+}
+
+uint64_t Http2Random::Rand64() {
+  union {
+    uint64_t number;
+    uint8_t bytes[sizeof(uint64_t)];
+  } result;
+  FillRandom(result.bytes, sizeof(result.bytes));
+  return result.number;
+}
+
+double Http2Random::RandDouble() {
+  union {
+    double f;
+    uint64_t i;
+  } value;
+  value.i = (1023ull << 52ull) | (Rand64() & 0xfffffffffffffu);
+  return value.f - 1.0;
+}
+
+Http2String Http2Random::RandStringWithAlphabet(int length,
+                                                Http2StringPiece alphabet) {
+  Http2String result;
+  result.resize(length);
+  for (int i = 0; i < length; i++) {
+    result[i] = alphabet[Uniform(alphabet.size())];
+  }
+  return result;
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/test_tools/http2_random.h b/http2/test_tools/http2_random.h
new file mode 100644
index 0000000..def60f8
--- /dev/null
+++ b/http2/test_tools/http2_random.h
@@ -0,0 +1,88 @@
+// Copyright 2018 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_RANDOM_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_RANDOM_H_
+
+#include <cmath>
+#include <cstdint>
+#include <limits>
+#include <random>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+// The random number generator used for unit tests.  Since the algorithm is
+// deterministic and fixed, this can be used to reproduce flakes in the unit
+// tests caused by specific random values.
+class Http2Random {
+ public:
+  Http2Random();
+
+  Http2Random(const Http2Random&) = delete;
+  Http2Random& operator=(const Http2Random&) = delete;
+
+  // Reproducible random number generation: by using the same key, the same
+  // sequence of results is obtained.
+  explicit Http2Random(Http2StringPiece key);
+  Http2String Key() const;
+
+  void FillRandom(void* buffer, size_t buffer_size);
+  Http2String RandString(int length);
+
+  // Returns a random 64-bit value.
+  uint64_t Rand64();
+
+  // Return a uniformly distrubted random number in [0, n).
+  uint32_t Uniform(uint32_t n) { return Rand64() % n; }
+  // Return a uniformly distrubted random number in [lo, hi).
+  uint64_t UniformInRange(uint64_t lo, uint64_t hi) {
+    return lo + Rand64() % (hi - lo);
+  }
+  // Return an integer of logarithmically random scale.
+  uint32_t Skewed(uint32_t max_log) {
+    const uint32_t base = Rand32() % (max_log + 1);
+    const uint32_t mask = ((base < 32) ? (1u << base) : 0u) - 1u;
+    return Rand32() & mask;
+  }
+  // Return a random number in [0, max] range that skews low.
+  uint64_t RandomSizeSkewedLow(uint64_t max) {
+    return std::round(max * std::pow(RandDouble(), 2));
+  }
+
+  // Returns a random double between 0 and 1.
+  double RandDouble();
+  float RandFloat() { return RandDouble(); }
+
+  // Has 1/n chance of returning true.
+  bool OneIn(int n) { return Uniform(n) == 0; }
+
+  uint8_t Rand8() { return Rand64(); }
+  uint16_t Rand16() { return Rand64(); }
+  uint32_t Rand32() { return Rand64(); }
+
+  // Return a random string consisting of the characters from the specified
+  // alphabet.
+  Http2String RandStringWithAlphabet(int length, Http2StringPiece alphabet);
+
+  // STL UniformRandomNumberGenerator implementation.
+  using result_type = uint64_t;
+  static constexpr result_type min() { return 0; }
+  static constexpr result_type max() {
+    return std::numeric_limits<result_type>::max();
+  }
+  result_type operator()() { return Rand64(); }
+
+ private:
+  uint8_t key_[32];
+  uint32_t counter_ = 0;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_HTTP2_RANDOM_H_
diff --git a/http2/test_tools/http2_random_test.cc b/http2/test_tools/http2_random_test.cc
new file mode 100644
index 0000000..a1b74f6
--- /dev/null
+++ b/http2/test_tools/http2_random_test.cc
@@ -0,0 +1,94 @@
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+#include <set>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+TEST(Http2RandomTest, ProducesDifferentNumbers) {
+  Http2Random random;
+  uint64_t value1 = random.Rand64();
+  uint64_t value2 = random.Rand64();
+  uint64_t value3 = random.Rand64();
+
+  EXPECT_NE(value1, value2);
+  EXPECT_NE(value2, value3);
+  EXPECT_NE(value3, value1);
+}
+
+TEST(Http2RandomTest, StartsWithDifferentKeys) {
+  Http2Random random1;
+  Http2Random random2;
+
+  EXPECT_NE(random1.Key(), random2.Key());
+  EXPECT_NE(random1.Rand64(), random2.Rand64());
+  EXPECT_NE(random1.Rand64(), random2.Rand64());
+  EXPECT_NE(random1.Rand64(), random2.Rand64());
+}
+
+TEST(Http2RandomTest, ReproducibleRandom) {
+  Http2Random random;
+  uint64_t value1 = random.Rand64();
+  uint64_t value2 = random.Rand64();
+
+  Http2Random clone_random(random.Key());
+  EXPECT_EQ(clone_random.Key(), random.Key());
+  EXPECT_EQ(value1, clone_random.Rand64());
+  EXPECT_EQ(value2, clone_random.Rand64());
+}
+
+TEST(Http2RandomTest, STLShuffle) {
+  Http2Random random;
+  const Http2String original = "abcdefghijklmonpqrsuvwxyz";
+
+  Http2String shuffled = original;
+  std::shuffle(shuffled.begin(), shuffled.end(), random);
+  EXPECT_NE(original, shuffled);
+}
+
+TEST(Http2RandomTest, RandFloat) {
+  Http2Random random;
+  for (int i = 0; i < 10000; i++) {
+    float value = random.RandFloat();
+    ASSERT_GE(value, 0.f);
+    ASSERT_LE(value, 1.f);
+  }
+}
+
+TEST(Http2RandomTest, RandStringWithAlphabet) {
+  Http2Random random;
+  Http2String str = random.RandStringWithAlphabet(1000, "xyz");
+  EXPECT_EQ(1000u, str.size());
+
+  std::set<char> characters(str.begin(), str.end());
+  EXPECT_THAT(characters, testing::ElementsAre('x', 'y', 'z'));
+}
+
+TEST(Http2RandomTest, SkewedLow) {
+  Http2Random random;
+  constexpr size_t kMax = 1234;
+  for (int i = 0; i < 10000; i++) {
+    size_t value = random.RandomSizeSkewedLow(kMax);
+    ASSERT_GE(value, 0u);
+    ASSERT_LE(value, kMax);
+  }
+}
+
+// Checks that SkewedLow() generates full range.  This is required, since in
+// some unit tests would infinitely loop.
+TEST(Http2RandomTest, SkewedLowFullRange) {
+  Http2Random random;
+  std::set<size_t> values;
+  for (int i = 0; i < 1000; i++) {
+    values.insert(random.RandomSizeSkewedLow(3));
+  }
+  EXPECT_THAT(values, testing::ElementsAre(0, 1, 2, 3));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/tools/http2_frame_builder.cc b/http2/tools/http2_frame_builder.cc
new file mode 100644
index 0000000..1dfcdeb
--- /dev/null
+++ b/http2/tools/http2_frame_builder.cc
@@ -0,0 +1,181 @@
+// 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 "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+
+#ifdef WIN32
+#include <winsock2.h>  // for htonl() functions
+#else
+#include <arpa/inet.h>
+#include <netinet/in.h>  // for htonl, htons
+#endif
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+namespace test {
+
+Http2FrameBuilder::Http2FrameBuilder(Http2FrameType type,
+                                     uint8_t flags,
+                                     uint32_t stream_id) {
+  AppendUInt24(0);  // Frame payload length, unknown so far.
+  Append(type);
+  AppendUInt8(flags);
+  AppendUInt31(stream_id);
+}
+
+Http2FrameBuilder::Http2FrameBuilder(const Http2FrameHeader& v) {
+  Append(v);
+}
+
+void Http2FrameBuilder::Append(Http2StringPiece s) {
+  Http2StrAppend(&buffer_, s);
+}
+
+void Http2FrameBuilder::AppendBytes(const void* data, uint32_t num_bytes) {
+  Append(Http2StringPiece(static_cast<const char*>(data), num_bytes));
+}
+
+void Http2FrameBuilder::AppendZeroes(size_t num_zero_bytes) {
+  char zero = 0;
+  buffer_.append(num_zero_bytes, zero);
+}
+
+void Http2FrameBuilder::AppendUInt8(uint8_t value) {
+  AppendBytes(&value, 1);
+}
+
+void Http2FrameBuilder::AppendUInt16(uint16_t value) {
+  value = htons(value);
+  AppendBytes(&value, 2);
+}
+
+void Http2FrameBuilder::AppendUInt24(uint32_t value) {
+  // Doesn't make sense to try to append a larger value, as that doesn't
+  // simulate something an encoder could do (i.e. the other 8 bits simply aren't
+  // there to be occupied).
+  EXPECT_EQ(value, value & 0xffffff);
+  value = htonl(value);
+  AppendBytes(reinterpret_cast<char*>(&value) + 1, 3);
+}
+
+void Http2FrameBuilder::AppendUInt31(uint32_t value) {
+  // If you want to test the high-bit being set, call AppendUInt32 instead.
+  uint32_t tmp = value & StreamIdMask();
+  EXPECT_EQ(value, value & StreamIdMask())
+      << "High-bit of uint32_t should be clear.";
+  value = htonl(tmp);
+  AppendBytes(&value, 4);
+}
+
+void Http2FrameBuilder::AppendUInt32(uint32_t value) {
+  value = htonl(value);
+  AppendBytes(&value, sizeof(value));
+}
+
+void Http2FrameBuilder::Append(Http2ErrorCode error_code) {
+  AppendUInt32(static_cast<uint32_t>(error_code));
+}
+
+void Http2FrameBuilder::Append(Http2FrameType type) {
+  AppendUInt8(static_cast<uint8_t>(type));
+}
+
+void Http2FrameBuilder::Append(Http2SettingsParameter parameter) {
+  AppendUInt16(static_cast<uint16_t>(parameter));
+}
+
+void Http2FrameBuilder::Append(const Http2FrameHeader& v) {
+  AppendUInt24(v.payload_length);
+  Append(v.type);
+  AppendUInt8(v.flags);
+  AppendUInt31(v.stream_id);
+}
+
+void Http2FrameBuilder::Append(const Http2PriorityFields& v) {
+  // The EXCLUSIVE flag is the high-bit of the 32-bit stream dependency field.
+  uint32_t tmp = v.stream_dependency & StreamIdMask();
+  EXPECT_EQ(tmp, v.stream_dependency);
+  if (v.is_exclusive) {
+    tmp |= 0x80000000;
+  }
+  AppendUInt32(tmp);
+
+  // The PRIORITY frame's weight field is logically in the range [1, 256],
+  // but is encoded as a byte in the range [0, 255].
+  ASSERT_LE(1u, v.weight);
+  ASSERT_LE(v.weight, 256u);
+  AppendUInt8(v.weight - 1);
+}
+
+void Http2FrameBuilder::Append(const Http2RstStreamFields& v) {
+  Append(v.error_code);
+}
+
+void Http2FrameBuilder::Append(const Http2SettingFields& v) {
+  Append(v.parameter);
+  AppendUInt32(v.value);
+}
+
+void Http2FrameBuilder::Append(const Http2PushPromiseFields& v) {
+  AppendUInt31(v.promised_stream_id);
+}
+
+void Http2FrameBuilder::Append(const Http2PingFields& v) {
+  AppendBytes(v.opaque_bytes, sizeof Http2PingFields::opaque_bytes);
+}
+
+void Http2FrameBuilder::Append(const Http2GoAwayFields& v) {
+  AppendUInt31(v.last_stream_id);
+  Append(v.error_code);
+}
+
+void Http2FrameBuilder::Append(const Http2WindowUpdateFields& v) {
+  EXPECT_NE(0u, v.window_size_increment) << "Increment must be non-zero.";
+  AppendUInt31(v.window_size_increment);
+}
+
+void Http2FrameBuilder::Append(const Http2AltSvcFields& v) {
+  AppendUInt16(v.origin_length);
+}
+
+// Methods for changing existing buffer contents.
+
+void Http2FrameBuilder::WriteAt(Http2StringPiece s, size_t offset) {
+  ASSERT_LE(offset, buffer_.size());
+  size_t len = offset + s.size();
+  if (len > buffer_.size()) {
+    buffer_.resize(len);
+  }
+  for (size_t ndx = 0; ndx < s.size(); ++ndx) {
+    buffer_[offset + ndx] = s[ndx];
+  }
+}
+
+void Http2FrameBuilder::WriteBytesAt(const void* data,
+                                     uint32_t num_bytes,
+                                     size_t offset) {
+  WriteAt(Http2StringPiece(static_cast<const char*>(data), num_bytes), offset);
+}
+
+void Http2FrameBuilder::WriteUInt24At(uint32_t value, size_t offset) {
+  ASSERT_LT(value, static_cast<uint32_t>(1 << 24));
+  value = htonl(value);
+  WriteBytesAt(reinterpret_cast<char*>(&value) + 1, sizeof(value) - 1, offset);
+}
+
+void Http2FrameBuilder::SetPayloadLength(uint32_t payload_length) {
+  WriteUInt24At(payload_length, 0);
+}
+
+size_t Http2FrameBuilder::SetPayloadLength() {
+  EXPECT_GE(size(), Http2FrameHeader::EncodedSize());
+  uint32_t payload_length = size() - Http2FrameHeader::EncodedSize();
+  SetPayloadLength(payload_length);
+  return payload_length;
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/tools/http2_frame_builder.h b/http2/tools/http2_frame_builder.h
new file mode 100644
index 0000000..c36cb56
--- /dev/null
+++ b/http2/tools/http2_frame_builder.h
@@ -0,0 +1,101 @@
+// 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_TOOLS_HTTP2_FRAME_BUILDER_H_
+#define QUICHE_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_
+
+// Http2FrameBuilder builds wire-format HTTP/2 frames (or fragments thereof)
+// from components.
+//
+// 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 DCHECKs.
+
+#include <stddef.h>  // for size_t
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+class Http2FrameBuilder {
+ public:
+  Http2FrameBuilder(Http2FrameType type, uint8_t flags, uint32_t stream_id);
+  explicit Http2FrameBuilder(const Http2FrameHeader& v);
+  Http2FrameBuilder() {}
+  ~Http2FrameBuilder() {}
+
+  size_t size() const { return buffer_.size(); }
+  const Http2String& buffer() const { return buffer_; }
+
+  //----------------------------------------------------------------------------
+  // Methods for appending to the end of the buffer.
+
+  // Append a sequence of bytes from various sources.
+  void Append(Http2StringPiece s);
+  void AppendBytes(const void* data, uint32_t num_bytes);
+
+  // Append an array of type T[N] to the string. Intended for tests with arrays
+  // initialized from literals, such as:
+  //    const char kData[] = {0x12, 0x23, ...};
+  //    builder.AppendBytes(kData);
+  template <typename T, size_t N>
+  void AppendBytes(T (&buf)[N]) {
+    AppendBytes(buf, N * sizeof(buf[0]));
+  }
+
+  // Support for appending padding. Does not read or write the Pad Length field.
+  void AppendZeroes(size_t num_zero_bytes);
+
+  // Append various sizes of unsigned integers.
+  void AppendUInt8(uint8_t value);
+  void AppendUInt16(uint16_t value);
+  void AppendUInt24(uint32_t value);
+  void AppendUInt31(uint32_t value);
+  void AppendUInt32(uint32_t value);
+
+  // Append various enums.
+  void Append(Http2ErrorCode error_code);
+  void Append(Http2FrameType type);
+  void Append(Http2SettingsParameter parameter);
+
+  // Append various structures.
+  void Append(const Http2FrameHeader& v);
+  void Append(const Http2PriorityFields& v);
+  void Append(const Http2RstStreamFields& v);
+  void Append(const Http2SettingFields& v);
+  void Append(const Http2PushPromiseFields& v);
+  void Append(const Http2PingFields& v);
+  void Append(const Http2GoAwayFields& v);
+  void Append(const Http2WindowUpdateFields& v);
+  void Append(const Http2AltSvcFields& v);
+
+  // Methods for changing existing buffer contents (mostly focused on updating
+  // the payload length).
+
+  void WriteAt(Http2StringPiece s, size_t offset);
+  void WriteBytesAt(const void* data, uint32_t num_bytes, size_t offset);
+  void WriteUInt24At(uint32_t value, size_t offset);
+
+  // Set the payload length to the specified size.
+  void SetPayloadLength(uint32_t payload_length);
+
+  // Sets the payload length to the size of the buffer minus the size of
+  // the frame header.
+  size_t SetPayloadLength();
+
+ private:
+  Http2String buffer_;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_
diff --git a/http2/tools/random_decoder_test.cc b/http2/tools/random_decoder_test.cc
new file mode 100644
index 0000000..623487c
--- /dev/null
+++ b/http2/tools/random_decoder_test.cc
@@ -0,0 +1,167 @@
+// 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 "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <memory>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+
+RandomDecoderTest::RandomDecoderTest() = default;
+
+bool RandomDecoderTest::StopDecodeOnDone() {
+  return stop_decode_on_done_;
+}
+
+DecodeStatus RandomDecoderTest::DecodeSegments(DecodeBuffer* original,
+                                               const SelectSize& select_size) {
+  DecodeStatus status = DecodeStatus::kDecodeInProgress;
+  bool first = true;
+  VLOG(2) << "DecodeSegments: input size=" << original->Remaining();
+  while (first || original->HasData()) {
+    size_t remaining = original->Remaining();
+    size_t size =
+        std::min(remaining, select_size(first, original->Offset(), remaining));
+    DecodeBuffer db(original->cursor(), size);
+    VLOG(2) << "Decoding " << size << " bytes of " << remaining << " remaining";
+    if (first) {
+      first = false;
+      status = StartDecoding(&db);
+    } else {
+      status = ResumeDecoding(&db);
+    }
+    // A decoder MUST consume some input (if any is available), else we could
+    // get stuck in infinite loops.
+    if (db.Offset() == 0 && db.HasData() &&
+        status != DecodeStatus::kDecodeError) {
+      ADD_FAILURE() << "Decoder didn't make any progress; db.FullSize="
+                    << db.FullSize()
+                    << "   original.Offset=" << original->Offset();
+      return DecodeStatus::kDecodeError;
+    }
+    original->AdvanceCursor(db.Offset());
+    switch (status) {
+      case DecodeStatus::kDecodeDone:
+        if (original->Empty() || StopDecodeOnDone()) {
+          return DecodeStatus::kDecodeDone;
+        }
+        continue;
+      case DecodeStatus::kDecodeInProgress:
+        continue;
+      case DecodeStatus::kDecodeError:
+        return DecodeStatus::kDecodeError;
+    }
+  }
+  return status;
+}
+
+// Decode |original| multiple times, with different segmentations, validating
+// after each decode, returning on the first failure.
+AssertionResult RandomDecoderTest::DecodeAndValidateSeveralWays(
+    DecodeBuffer* original,
+    bool return_non_zero_on_first,
+    const Validator& validator) {
+  const uint32_t original_remaining = original->Remaining();
+  VLOG(1) << "DecodeAndValidateSeveralWays - Start, remaining = "
+          << original_remaining;
+  uint32_t first_consumed;
+  {
+    // Fast decode (no stopping unless decoder does so).
+    DecodeBuffer input(original->cursor(), original_remaining);
+    VLOG(2) << "DecodeSegmentsAndValidate with SelectRemaining";
+    VERIFY_SUCCESS(
+        DecodeSegmentsAndValidate(&input, SelectRemaining(), validator))
+        << "\nFailed with SelectRemaining; input.Offset=" << input.Offset()
+        << "; input.Remaining=" << input.Remaining();
+    first_consumed = input.Offset();
+  }
+  if (original_remaining <= 30) {
+    // Decode again, one byte at a time.
+    DecodeBuffer input(original->cursor(), original_remaining);
+    VLOG(2) << "DecodeSegmentsAndValidate with SelectOne";
+    VERIFY_SUCCESS(DecodeSegmentsAndValidate(&input, SelectOne(), validator))
+        << "\nFailed with SelectOne; input.Offset=" << input.Offset()
+        << "; input.Remaining=" << input.Remaining();
+    VERIFY_EQ(first_consumed, input.Offset()) << "\nFailed with SelectOne";
+  }
+  if (original_remaining <= 20) {
+    // Decode again, one or zero bytes at a time.
+    DecodeBuffer input(original->cursor(), original_remaining);
+    VLOG(2) << "DecodeSegmentsAndValidate with SelectZeroAndOne";
+    VERIFY_SUCCESS(DecodeSegmentsAndValidate(
+        &input, SelectZeroAndOne(return_non_zero_on_first), validator))
+        << "\nFailed with SelectZeroAndOne";
+    VERIFY_EQ(first_consumed, input.Offset())
+        << "\nFailed with SelectZeroAndOne; input.Offset=" << input.Offset()
+        << "; input.Remaining=" << input.Remaining();
+  }
+  {
+    // Decode again, with randomly selected segment sizes.
+    DecodeBuffer input(original->cursor(), original_remaining);
+    VLOG(2) << "DecodeSegmentsAndValidate with SelectRandom";
+    VERIFY_SUCCESS(DecodeSegmentsAndValidate(
+        &input, SelectRandom(return_non_zero_on_first), validator))
+        << "\nFailed with SelectRandom; input.Offset=" << input.Offset()
+        << "; input.Remaining=" << input.Remaining();
+    VERIFY_EQ(first_consumed, input.Offset()) << "\nFailed with SelectRandom";
+  }
+  VERIFY_EQ(original_remaining, original->Remaining());
+  original->AdvanceCursor(first_consumed);
+  VLOG(1) << "DecodeAndValidateSeveralWays - SUCCESS";
+  return ::testing::AssertionSuccess();
+}
+
+// static
+RandomDecoderTest::SelectSize RandomDecoderTest::SelectZeroAndOne(
+    bool return_non_zero_on_first) {
+  std::shared_ptr<bool> zero_next(new bool);
+  *zero_next = !return_non_zero_on_first;
+  return [zero_next](bool first, size_t offset, size_t remaining) -> size_t {
+    if (*zero_next) {
+      *zero_next = false;
+      return 0;
+    } else {
+      *zero_next = true;
+      return 1;
+    }
+  };
+}
+
+RandomDecoderTest::SelectSize RandomDecoderTest::SelectRandom(
+    bool return_non_zero_on_first) {
+  return [this, return_non_zero_on_first](bool first, size_t offset,
+                                          size_t remaining) -> size_t {
+    uint32_t r = random_.Rand32();
+    if (first && return_non_zero_on_first) {
+      CHECK_LT(0u, remaining);
+      if (remaining == 1) {
+        return 1;
+      }
+      return 1 + (r % remaining);  // size in range [1, remaining).
+    }
+    return r % (remaining + 1);  // size in range [0, remaining].
+  };
+}
+
+uint32_t RandomDecoderTest::RandStreamId() {
+  return random_.Rand32() & StreamIdMask();
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/tools/random_decoder_test.h b/http2/tools/random_decoder_test.h
new file mode 100644
index 0000000..36f3196
--- /dev/null
+++ b/http2/tools/random_decoder_test.h
@@ -0,0 +1,258 @@
+// 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_TOOLS_RANDOM_DECODER_TEST_H_
+#define QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_
+
+// RandomDecoderTest is a base class for tests of decoding various kinds
+// of HTTP/2 and HPACK encodings.
+
+// TODO(jamessynge): Move more methods into .cc file.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <type_traits>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+// Some helpers.
+
+template <typename T, size_t N>
+Http2StringPiece ToStringPiece(T (&data)[N]) {
+  return Http2StringPiece(reinterpret_cast<const char*>(data), N * sizeof(T));
+}
+
+// Overwrite the enum with some random value, probably not a valid value for
+// the enum type, but which fits into its storage.
+template <typename T,
+          typename E = typename std::enable_if<std::is_enum<T>::value>::type>
+void CorruptEnum(T* out, Http2Random* rng) {
+  // Per cppreference.com, if the destination type of a static_cast is
+  // smaller than the source type (i.e. type of r and uint32 below), the
+  // resulting value is the smallest unsigned value equal to the source value
+  // modulo 2^n, where n is the number of bits used to represent the
+  // destination type unsigned U.
+  using underlying_type_T = typename std::underlying_type<T>::type;
+  using unsigned_underlying_type_T =
+      typename std::make_unsigned<underlying_type_T>::type;
+  auto r = static_cast<unsigned_underlying_type_T>(rng->Rand32());
+  *out = static_cast<T>(r);
+}
+
+// Base class for tests of the ability to decode a sequence of bytes with
+// various boundaries between the DecodeBuffers provided to the decoder.
+class RandomDecoderTest : public ::testing::Test {
+ public:
+  // SelectSize returns the size of the next DecodeBuffer to be passed to the
+  // decoder. Note that RandomDecoderTest allows that size to be zero, though
+  // some decoders can't deal with that on the first byte, hence the |first|
+  // parameter.
+  typedef std::function<size_t(bool first, size_t offset, size_t remaining)>
+      SelectSize;
+
+  // Validator returns an AssertionResult so test can do:
+  // EXPECT_THAT(DecodeAndValidate(..., validator));
+  typedef ::testing::AssertionResult AssertionResult;
+  typedef std::function<AssertionResult(const DecodeBuffer& input,
+                                        DecodeStatus status)>
+      Validator;
+  typedef std::function<AssertionResult()> NoArgValidator;
+
+  RandomDecoderTest();
+
+ protected:
+  // TODO(jamessynge): Modify StartDecoding, etc. to (somehow) return
+  // AssertionResult so that the VERIFY_* methods exported from
+  // gunit_helpers.h can be widely used.
+
+  // Start decoding; call allows sub-class to Reset the decoder, or deal with
+  // the first byte if that is done in a unique fashion.  Might be called with
+  // a zero byte buffer.
+  virtual DecodeStatus StartDecoding(DecodeBuffer* db) = 0;
+
+  // Resume decoding of the input after a prior call to StartDecoding, and
+  // possibly many calls to ResumeDecoding.
+  virtual DecodeStatus ResumeDecoding(DecodeBuffer* db) = 0;
+
+  // Return true if a decode status of kDecodeDone indicates that
+  // decoding should stop.
+  virtual bool StopDecodeOnDone();
+
+  // Decode buffer |original| until we run out of input, or kDecodeDone is
+  // returned by the decoder AND StopDecodeOnDone() returns true. Segments
+  // (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers
+  // by calling |select_size| to decide how large each buffer should be.
+  // We do this to test the ability to deal with arbitrary boundaries, as might
+  // happen in transport.
+  // Returns the final DecodeStatus.
+  DecodeStatus DecodeSegments(DecodeBuffer* original,
+                              const SelectSize& select_size);
+
+  // Decode buffer |original| until we run out of input, or kDecodeDone is
+  // returned by the decoder AND StopDecodeOnDone() returns true. Segments
+  // (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers
+  // by calling |select_size| to decide how large each buffer should be.
+  // We do this to test the ability to deal with arbitrary boundaries, as might
+  // happen in transport.
+  // Invokes |validator| with the final decode status and the original decode
+  // buffer, with the cursor advanced as far as has been consumed by the decoder
+  // and returns validator's result.
+  ::testing::AssertionResult DecodeSegmentsAndValidate(
+      DecodeBuffer* original,
+      const SelectSize& select_size,
+      const Validator& validator) {
+    DecodeStatus status = DecodeSegments(original, select_size);
+    VERIFY_AND_RETURN_SUCCESS(validator(*original, status));
+  }
+
+  // Returns a SelectSize function for fast decoding, i.e. passing all that
+  // is available to the decoder.
+  static SelectSize SelectRemaining() {
+    return [](bool first, size_t offset, size_t remaining) -> size_t {
+      return remaining;
+    };
+  }
+
+  // Returns a SelectSize function for decoding a single byte at a time.
+  static SelectSize SelectOne() {
+    return
+        [](bool first, size_t offset, size_t remaining) -> size_t { return 1; };
+  }
+
+  // Returns a SelectSize function for decoding a single byte at a time, where
+  // zero byte buffers are also allowed. Alternates between zero and one.
+  static SelectSize SelectZeroAndOne(bool return_non_zero_on_first);
+
+  // Returns a SelectSize function for decoding random sized segments.
+  SelectSize SelectRandom(bool return_non_zero_on_first);
+
+  // Decode |original| multiple times, with different segmentations of the
+  // decode buffer, validating after each decode, and confirming that they
+  // each decode the same amount. Returns on the first failure, else returns
+  // success.
+  AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* original,
+                                               bool return_non_zero_on_first,
+                                               const Validator& validator);
+
+  static Validator ToValidator(std::nullptr_t) {
+    return [](const DecodeBuffer& input, DecodeStatus status) {
+      return ::testing::AssertionSuccess();
+    };
+  }
+
+  static Validator ToValidator(const Validator& validator) {
+    if (validator == nullptr) {
+      return ToValidator(nullptr);
+    }
+    return validator;
+  }
+
+  static Validator ToValidator(const NoArgValidator& validator) {
+    if (validator == nullptr) {
+      return ToValidator(nullptr);
+    }
+    return [validator](const DecodeBuffer& input, DecodeStatus status) {
+      return validator();
+    };
+  }
+
+  // Wraps a validator with another validator
+  // that first checks that the DecodeStatus is kDecodeDone and
+  // that the DecodeBuffer is empty.
+  // TODO(jamessynge): Replace this overload with the next, as using this method
+  // usually means that the wrapped function doesn't need to be passed the
+  // DecodeBuffer nor the DecodeStatus.
+  static Validator ValidateDoneAndEmpty(const Validator& wrapped) {
+    return [wrapped](const DecodeBuffer& input,
+                     DecodeStatus status) -> AssertionResult {
+      VERIFY_EQ(status, DecodeStatus::kDecodeDone);
+      VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset();
+      if (wrapped) {
+        return wrapped(input, status);
+      }
+      return ::testing::AssertionSuccess();
+    };
+  }
+  static Validator ValidateDoneAndEmpty(NoArgValidator wrapped) {
+    return [wrapped](const DecodeBuffer& input,
+                     DecodeStatus status) -> AssertionResult {
+      VERIFY_EQ(status, DecodeStatus::kDecodeDone);
+      VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset();
+      if (wrapped) {
+        return wrapped();
+      }
+      return ::testing::AssertionSuccess();
+    };
+  }
+  static Validator ValidateDoneAndEmpty() {
+    NoArgValidator validator;
+    return ValidateDoneAndEmpty(validator);
+  }
+
+  // Wraps a validator with another validator
+  // that first checks that the DecodeStatus is kDecodeDone and
+  // that the DecodeBuffer has the expected offset.
+  // TODO(jamessynge): Replace this overload with the next, as using this method
+  // usually means that the wrapped function doesn't need to be passed the
+  // DecodeBuffer nor the DecodeStatus.
+  static Validator ValidateDoneAndOffset(uint32_t offset,
+                                         const Validator& wrapped) {
+    return [wrapped, offset](const DecodeBuffer& input,
+                             DecodeStatus status) -> AssertionResult {
+      VERIFY_EQ(status, DecodeStatus::kDecodeDone);
+      VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining();
+      if (wrapped) {
+        return wrapped(input, status);
+      }
+      return ::testing::AssertionSuccess();
+    };
+  }
+  static Validator ValidateDoneAndOffset(uint32_t offset,
+                                         NoArgValidator wrapped) {
+    return [wrapped, offset](const DecodeBuffer& input,
+                             DecodeStatus status) -> AssertionResult {
+      VERIFY_EQ(status, DecodeStatus::kDecodeDone);
+      VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining();
+      if (wrapped) {
+        return wrapped();
+      }
+      return ::testing::AssertionSuccess();
+    };
+  }
+  static Validator ValidateDoneAndOffset(uint32_t offset) {
+    NoArgValidator validator;
+    return ValidateDoneAndOffset(offset, validator);
+  }
+
+  // Expose |random_| as Http2Random so callers don't have to care about which
+  // sub-class of Http2Random is used, nor can they rely on the specific
+  // sub-class that RandomDecoderTest uses.
+  Http2Random& Random() { return random_; }
+  Http2Random* RandomPtr() { return &random_; }
+
+  uint32_t RandStreamId();
+
+  bool stop_decode_on_done_ = true;
+
+ private:
+  Http2Random random_;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_
diff --git a/http2/tools/random_util.cc b/http2/tools/random_util.cc
new file mode 100644
index 0000000..82c3edd
--- /dev/null
+++ b/http2/tools/random_util.cc
@@ -0,0 +1,39 @@
+// 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 "net/third_party/quiche/src/http2/tools/random_util.h"
+
+#include <cmath>
+
+namespace http2 {
+namespace test {
+
+// Here "word" means something that starts with a lower-case letter, and has
+// zero or more additional characters that are numbers or lower-case letters.
+Http2String GenerateHttp2HeaderName(size_t len, Http2Random* rng) {
+  Http2StringPiece alpha_lc = "abcdefghijklmnopqrstuvwxyz";
+  // If the name is short, just make it one word.
+  if (len < 8) {
+    return rng->RandStringWithAlphabet(len, alpha_lc);
+  }
+  // If the name is longer, ensure it starts with a word, and after that may
+  // have any character in alphanumdash_lc. 4 is arbitrary, could be as low
+  // as 1.
+  Http2StringPiece alphanumdash_lc = "abcdefghijklmnopqrstuvwxyz0123456789-";
+  return rng->RandStringWithAlphabet(4, alpha_lc) +
+         rng->RandStringWithAlphabet(len - 4, alphanumdash_lc);
+}
+
+Http2String GenerateWebSafeString(size_t len, Http2Random* rng) {
+  static const char* kWebsafe64 =
+      "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
+  return rng->RandStringWithAlphabet(len, kWebsafe64);
+}
+
+Http2String GenerateWebSafeString(size_t lo, size_t hi, Http2Random* rng) {
+  return GenerateWebSafeString(rng->UniformInRange(lo, hi), rng);
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/tools/random_util.h b/http2/tools/random_util.h
new file mode 100644
index 0000000..a2107b7
--- /dev/null
+++ b/http2/tools/random_util.h
@@ -0,0 +1,29 @@
+// 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_TOOLS_RANDOM_UTIL_H_
+#define QUICHE_HTTP2_TOOLS_RANDOM_UTIL_H_
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+// Generate a string with the allowed character set for HTTP/2 / HPACK header
+// names.
+Http2String GenerateHttp2HeaderName(size_t len, Http2Random* rng);
+
+// Generate a string with the web-safe string character set of specified len.
+Http2String GenerateWebSafeString(size_t len, Http2Random* rng);
+
+// Generate a string with the web-safe string character set of length [lo, hi).
+Http2String GenerateWebSafeString(size_t lo, size_t hi, Http2Random* rng);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TOOLS_RANDOM_UTIL_H_