Project import generated by Copybara.

PiperOrigin-RevId: 224614037
Change-Id: I14e53449d4aeccb328f86828c76b5f09dea0d4b8
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