Project import generated by Copybara.

PiperOrigin-RevId: 229942388
Change-Id: Ib5a23c152c95ed4294cece9f902227c21ce531ef
diff --git a/spdy/core/array_output_buffer.cc b/spdy/core/array_output_buffer.cc
new file mode 100644
index 0000000..ea82eb0
--- /dev/null
+++ b/spdy/core/array_output_buffer.cc
@@ -0,0 +1,23 @@
+// 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/spdy/core/array_output_buffer.h"
+
+namespace spdy {
+
+void ArrayOutputBuffer::Next(char** data, int* size) {
+  *data = current_;
+  *size = capacity_ > 0 ? capacity_ : 0;
+}
+
+void ArrayOutputBuffer::AdvanceWritePtr(int64_t count) {
+  current_ += count;
+  capacity_ -= count;
+}
+
+uint64_t ArrayOutputBuffer::BytesFree() const {
+  return capacity_;
+}
+
+}  // namespace spdy
diff --git a/spdy/core/array_output_buffer.h b/spdy/core/array_output_buffer.h
new file mode 100644
index 0000000..72303f1
--- /dev/null
+++ b/spdy/core/array_output_buffer.h
@@ -0,0 +1,45 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_ARRAY_OUTPUT_BUFFER_H_
+#define QUICHE_SPDY_CORE_ARRAY_OUTPUT_BUFFER_H_
+
+#include <cstddef>
+#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h"
+
+namespace spdy {
+
+class ArrayOutputBuffer : public ZeroCopyOutputBuffer {
+ public:
+  // |buffer| is pointed to the output to write to, and |size| is the capacity
+  // of the output.
+  ArrayOutputBuffer(char* buffer, int64_t size)
+      : current_(buffer), begin_(buffer), capacity_(size) {}
+  ~ArrayOutputBuffer() override {}
+
+  ArrayOutputBuffer(const ArrayOutputBuffer&) = delete;
+  ArrayOutputBuffer& operator=(const ArrayOutputBuffer&) = delete;
+
+  void Next(char** data, int* size) override;
+  void AdvanceWritePtr(int64_t count) override;
+  uint64_t BytesFree() const override;
+
+  size_t Size() const { return current_ - begin_; }
+  char* Begin() const { return begin_; }
+
+  // Resets the buffer to its original state.
+  void Reset() {
+    capacity_ += Size();
+    current_ = begin_;
+  }
+
+ private:
+  char* current_ = nullptr;
+  char* begin_ = nullptr;
+  uint64_t capacity_ = 0;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_ARRAY_OUTPUT_BUFFER_H_
diff --git a/spdy/core/array_output_buffer_test.cc b/spdy/core/array_output_buffer_test.cc
new file mode 100644
index 0000000..369778d
--- /dev/null
+++ b/spdy/core/array_output_buffer_test.cc
@@ -0,0 +1,50 @@
+// 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/spdy/core/array_output_buffer.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace spdy {
+namespace test {
+
+// This test verifies that ArrayOutputBuffer is initialized properly.
+TEST(ArrayOutputBufferTest, InitializedFromArray) {
+  char array[100];
+  ArrayOutputBuffer buffer(array, sizeof(array));
+  EXPECT_EQ(sizeof(array), buffer.BytesFree());
+  EXPECT_EQ(0u, buffer.Size());
+  EXPECT_EQ(array, buffer.Begin());
+}
+
+// This test verifies that Reset() causes an ArrayOutputBuffer's capacity and
+// size to be reset to the initial state.
+TEST(ArrayOutputBufferTest, WriteAndReset) {
+  char array[100];
+  ArrayOutputBuffer buffer(array, sizeof(array));
+
+  // Let's write some bytes.
+  char* dst;
+  int size;
+  buffer.Next(&dst, &size);
+  ASSERT_GT(size, 1);
+  ASSERT_NE(nullptr, dst);
+  const int64_t written = size / 2;
+  memset(dst, 'x', written);
+  buffer.AdvanceWritePtr(written);
+
+  // The buffer should be partially used.
+  EXPECT_EQ(static_cast<uint64_t>(size) - written, buffer.BytesFree());
+  EXPECT_EQ(static_cast<uint64_t>(written), buffer.Size());
+
+  buffer.Reset();
+
+  // After a reset, the buffer should regain its full capacity.
+  EXPECT_EQ(sizeof(array), buffer.BytesFree());
+  EXPECT_EQ(0u, buffer.Size());
+}
+
+}  // namespace test
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_constants.cc b/spdy/core/hpack/hpack_constants.cc
new file mode 100644
index 0000000..9c1498a
--- /dev/null
+++ b/spdy/core/hpack/hpack_constants.cc
@@ -0,0 +1,386 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h"
+
+namespace spdy {
+
+// Produced by applying the python program [1] with tables provided by [2]
+// (inserted into the source of the python program) and copy-paste them into
+// this file.
+//
+// [1] net/tools/build_hpack_constants.py in Chromium
+// [2] http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08
+
+// HpackHuffmanSymbol entries are initialized as {code, length, id}.
+// Codes are specified in the |length| most-significant bits of |code|.
+const std::vector<HpackHuffmanSymbol>& HpackHuffmanCodeVector() {
+  static const auto* kHpackHuffmanCode = new std::vector<HpackHuffmanSymbol>{
+      {0xffc00000ul, 13, 0},    //     11111111|11000
+      {0xffffb000ul, 23, 1},    //     11111111|11111111|1011000
+      {0xfffffe20ul, 28, 2},    //     11111111|11111111|11111110|0010
+      {0xfffffe30ul, 28, 3},    //     11111111|11111111|11111110|0011
+      {0xfffffe40ul, 28, 4},    //     11111111|11111111|11111110|0100
+      {0xfffffe50ul, 28, 5},    //     11111111|11111111|11111110|0101
+      {0xfffffe60ul, 28, 6},    //     11111111|11111111|11111110|0110
+      {0xfffffe70ul, 28, 7},    //     11111111|11111111|11111110|0111
+      {0xfffffe80ul, 28, 8},    //     11111111|11111111|11111110|1000
+      {0xffffea00ul, 24, 9},    //     11111111|11111111|11101010
+      {0xfffffff0ul, 30, 10},   //     11111111|11111111|11111111|111100
+      {0xfffffe90ul, 28, 11},   //     11111111|11111111|11111110|1001
+      {0xfffffea0ul, 28, 12},   //     11111111|11111111|11111110|1010
+      {0xfffffff4ul, 30, 13},   //     11111111|11111111|11111111|111101
+      {0xfffffeb0ul, 28, 14},   //     11111111|11111111|11111110|1011
+      {0xfffffec0ul, 28, 15},   //     11111111|11111111|11111110|1100
+      {0xfffffed0ul, 28, 16},   //     11111111|11111111|11111110|1101
+      {0xfffffee0ul, 28, 17},   //     11111111|11111111|11111110|1110
+      {0xfffffef0ul, 28, 18},   //     11111111|11111111|11111110|1111
+      {0xffffff00ul, 28, 19},   //     11111111|11111111|11111111|0000
+      {0xffffff10ul, 28, 20},   //     11111111|11111111|11111111|0001
+      {0xffffff20ul, 28, 21},   //     11111111|11111111|11111111|0010
+      {0xfffffff8ul, 30, 22},   //     11111111|11111111|11111111|111110
+      {0xffffff30ul, 28, 23},   //     11111111|11111111|11111111|0011
+      {0xffffff40ul, 28, 24},   //     11111111|11111111|11111111|0100
+      {0xffffff50ul, 28, 25},   //     11111111|11111111|11111111|0101
+      {0xffffff60ul, 28, 26},   //     11111111|11111111|11111111|0110
+      {0xffffff70ul, 28, 27},   //     11111111|11111111|11111111|0111
+      {0xffffff80ul, 28, 28},   //     11111111|11111111|11111111|1000
+      {0xffffff90ul, 28, 29},   //     11111111|11111111|11111111|1001
+      {0xffffffa0ul, 28, 30},   //     11111111|11111111|11111111|1010
+      {0xffffffb0ul, 28, 31},   //     11111111|11111111|11111111|1011
+      {0x50000000ul, 6, 32},    // ' ' 010100
+      {0xfe000000ul, 10, 33},   // '!' 11111110|00
+      {0xfe400000ul, 10, 34},   // '"' 11111110|01
+      {0xffa00000ul, 12, 35},   // '#' 11111111|1010
+      {0xffc80000ul, 13, 36},   // '$' 11111111|11001
+      {0x54000000ul, 6, 37},    // '%' 010101
+      {0xf8000000ul, 8, 38},    // '&' 11111000
+      {0xff400000ul, 11, 39},   // ''' 11111111|010
+      {0xfe800000ul, 10, 40},   // '(' 11111110|10
+      {0xfec00000ul, 10, 41},   // ')' 11111110|11
+      {0xf9000000ul, 8, 42},    // '*' 11111001
+      {0xff600000ul, 11, 43},   // '+' 11111111|011
+      {0xfa000000ul, 8, 44},    // ',' 11111010
+      {0x58000000ul, 6, 45},    // '-' 010110
+      {0x5c000000ul, 6, 46},    // '.' 010111
+      {0x60000000ul, 6, 47},    // '/' 011000
+      {0x00000000ul, 5, 48},    // '0' 00000
+      {0x08000000ul, 5, 49},    // '1' 00001
+      {0x10000000ul, 5, 50},    // '2' 00010
+      {0x64000000ul, 6, 51},    // '3' 011001
+      {0x68000000ul, 6, 52},    // '4' 011010
+      {0x6c000000ul, 6, 53},    // '5' 011011
+      {0x70000000ul, 6, 54},    // '6' 011100
+      {0x74000000ul, 6, 55},    // '7' 011101
+      {0x78000000ul, 6, 56},    // '8' 011110
+      {0x7c000000ul, 6, 57},    // '9' 011111
+      {0xb8000000ul, 7, 58},    // ':' 1011100
+      {0xfb000000ul, 8, 59},    // ';' 11111011
+      {0xfff80000ul, 15, 60},   // '<' 11111111|1111100
+      {0x80000000ul, 6, 61},    // '=' 100000
+      {0xffb00000ul, 12, 62},   // '>' 11111111|1011
+      {0xff000000ul, 10, 63},   // '?' 11111111|00
+      {0xffd00000ul, 13, 64},   // '@' 11111111|11010
+      {0x84000000ul, 6, 65},    // 'A' 100001
+      {0xba000000ul, 7, 66},    // 'B' 1011101
+      {0xbc000000ul, 7, 67},    // 'C' 1011110
+      {0xbe000000ul, 7, 68},    // 'D' 1011111
+      {0xc0000000ul, 7, 69},    // 'E' 1100000
+      {0xc2000000ul, 7, 70},    // 'F' 1100001
+      {0xc4000000ul, 7, 71},    // 'G' 1100010
+      {0xc6000000ul, 7, 72},    // 'H' 1100011
+      {0xc8000000ul, 7, 73},    // 'I' 1100100
+      {0xca000000ul, 7, 74},    // 'J' 1100101
+      {0xcc000000ul, 7, 75},    // 'K' 1100110
+      {0xce000000ul, 7, 76},    // 'L' 1100111
+      {0xd0000000ul, 7, 77},    // 'M' 1101000
+      {0xd2000000ul, 7, 78},    // 'N' 1101001
+      {0xd4000000ul, 7, 79},    // 'O' 1101010
+      {0xd6000000ul, 7, 80},    // 'P' 1101011
+      {0xd8000000ul, 7, 81},    // 'Q' 1101100
+      {0xda000000ul, 7, 82},    // 'R' 1101101
+      {0xdc000000ul, 7, 83},    // 'S' 1101110
+      {0xde000000ul, 7, 84},    // 'T' 1101111
+      {0xe0000000ul, 7, 85},    // 'U' 1110000
+      {0xe2000000ul, 7, 86},    // 'V' 1110001
+      {0xe4000000ul, 7, 87},    // 'W' 1110010
+      {0xfc000000ul, 8, 88},    // 'X' 11111100
+      {0xe6000000ul, 7, 89},    // 'Y' 1110011
+      {0xfd000000ul, 8, 90},    // 'Z' 11111101
+      {0xffd80000ul, 13, 91},   // '[' 11111111|11011
+      {0xfffe0000ul, 19, 92},   // '\' 11111111|11111110|000
+      {0xffe00000ul, 13, 93},   // ']' 11111111|11100
+      {0xfff00000ul, 14, 94},   // '^' 11111111|111100
+      {0x88000000ul, 6, 95},    // '_' 100010
+      {0xfffa0000ul, 15, 96},   // '`' 11111111|1111101
+      {0x18000000ul, 5, 97},    // 'a' 00011
+      {0x8c000000ul, 6, 98},    // 'b' 100011
+      {0x20000000ul, 5, 99},    // 'c' 00100
+      {0x90000000ul, 6, 100},   // 'd' 100100
+      {0x28000000ul, 5, 101},   // 'e' 00101
+      {0x94000000ul, 6, 102},   // 'f' 100101
+      {0x98000000ul, 6, 103},   // 'g' 100110
+      {0x9c000000ul, 6, 104},   // 'h' 100111
+      {0x30000000ul, 5, 105},   // 'i' 00110
+      {0xe8000000ul, 7, 106},   // 'j' 1110100
+      {0xea000000ul, 7, 107},   // 'k' 1110101
+      {0xa0000000ul, 6, 108},   // 'l' 101000
+      {0xa4000000ul, 6, 109},   // 'm' 101001
+      {0xa8000000ul, 6, 110},   // 'n' 101010
+      {0x38000000ul, 5, 111},   // 'o' 00111
+      {0xac000000ul, 6, 112},   // 'p' 101011
+      {0xec000000ul, 7, 113},   // 'q' 1110110
+      {0xb0000000ul, 6, 114},   // 'r' 101100
+      {0x40000000ul, 5, 115},   // 's' 01000
+      {0x48000000ul, 5, 116},   // 't' 01001
+      {0xb4000000ul, 6, 117},   // 'u' 101101
+      {0xee000000ul, 7, 118},   // 'v' 1110111
+      {0xf0000000ul, 7, 119},   // 'w' 1111000
+      {0xf2000000ul, 7, 120},   // 'x' 1111001
+      {0xf4000000ul, 7, 121},   // 'y' 1111010
+      {0xf6000000ul, 7, 122},   // 'z' 1111011
+      {0xfffc0000ul, 15, 123},  // '{' 11111111|1111110
+      {0xff800000ul, 11, 124},  // '|' 11111111|100
+      {0xfff40000ul, 14, 125},  // '}' 11111111|111101
+      {0xffe80000ul, 13, 126},  // '~' 11111111|11101
+      {0xffffffc0ul, 28, 127},  //     11111111|11111111|11111111|1100
+      {0xfffe6000ul, 20, 128},  //     11111111|11111110|0110
+      {0xffff4800ul, 22, 129},  //     11111111|11111111|010010
+      {0xfffe7000ul, 20, 130},  //     11111111|11111110|0111
+      {0xfffe8000ul, 20, 131},  //     11111111|11111110|1000
+      {0xffff4c00ul, 22, 132},  //     11111111|11111111|010011
+      {0xffff5000ul, 22, 133},  //     11111111|11111111|010100
+      {0xffff5400ul, 22, 134},  //     11111111|11111111|010101
+      {0xffffb200ul, 23, 135},  //     11111111|11111111|1011001
+      {0xffff5800ul, 22, 136},  //     11111111|11111111|010110
+      {0xffffb400ul, 23, 137},  //     11111111|11111111|1011010
+      {0xffffb600ul, 23, 138},  //     11111111|11111111|1011011
+      {0xffffb800ul, 23, 139},  //     11111111|11111111|1011100
+      {0xffffba00ul, 23, 140},  //     11111111|11111111|1011101
+      {0xffffbc00ul, 23, 141},  //     11111111|11111111|1011110
+      {0xffffeb00ul, 24, 142},  //     11111111|11111111|11101011
+      {0xffffbe00ul, 23, 143},  //     11111111|11111111|1011111
+      {0xffffec00ul, 24, 144},  //     11111111|11111111|11101100
+      {0xffffed00ul, 24, 145},  //     11111111|11111111|11101101
+      {0xffff5c00ul, 22, 146},  //     11111111|11111111|010111
+      {0xffffc000ul, 23, 147},  //     11111111|11111111|1100000
+      {0xffffee00ul, 24, 148},  //     11111111|11111111|11101110
+      {0xffffc200ul, 23, 149},  //     11111111|11111111|1100001
+      {0xffffc400ul, 23, 150},  //     11111111|11111111|1100010
+      {0xffffc600ul, 23, 151},  //     11111111|11111111|1100011
+      {0xffffc800ul, 23, 152},  //     11111111|11111111|1100100
+      {0xfffee000ul, 21, 153},  //     11111111|11111110|11100
+      {0xffff6000ul, 22, 154},  //     11111111|11111111|011000
+      {0xffffca00ul, 23, 155},  //     11111111|11111111|1100101
+      {0xffff6400ul, 22, 156},  //     11111111|11111111|011001
+      {0xffffcc00ul, 23, 157},  //     11111111|11111111|1100110
+      {0xffffce00ul, 23, 158},  //     11111111|11111111|1100111
+      {0xffffef00ul, 24, 159},  //     11111111|11111111|11101111
+      {0xffff6800ul, 22, 160},  //     11111111|11111111|011010
+      {0xfffee800ul, 21, 161},  //     11111111|11111110|11101
+      {0xfffe9000ul, 20, 162},  //     11111111|11111110|1001
+      {0xffff6c00ul, 22, 163},  //     11111111|11111111|011011
+      {0xffff7000ul, 22, 164},  //     11111111|11111111|011100
+      {0xffffd000ul, 23, 165},  //     11111111|11111111|1101000
+      {0xffffd200ul, 23, 166},  //     11111111|11111111|1101001
+      {0xfffef000ul, 21, 167},  //     11111111|11111110|11110
+      {0xffffd400ul, 23, 168},  //     11111111|11111111|1101010
+      {0xffff7400ul, 22, 169},  //     11111111|11111111|011101
+      {0xffff7800ul, 22, 170},  //     11111111|11111111|011110
+      {0xfffff000ul, 24, 171},  //     11111111|11111111|11110000
+      {0xfffef800ul, 21, 172},  //     11111111|11111110|11111
+      {0xffff7c00ul, 22, 173},  //     11111111|11111111|011111
+      {0xffffd600ul, 23, 174},  //     11111111|11111111|1101011
+      {0xffffd800ul, 23, 175},  //     11111111|11111111|1101100
+      {0xffff0000ul, 21, 176},  //     11111111|11111111|00000
+      {0xffff0800ul, 21, 177},  //     11111111|11111111|00001
+      {0xffff8000ul, 22, 178},  //     11111111|11111111|100000
+      {0xffff1000ul, 21, 179},  //     11111111|11111111|00010
+      {0xffffda00ul, 23, 180},  //     11111111|11111111|1101101
+      {0xffff8400ul, 22, 181},  //     11111111|11111111|100001
+      {0xffffdc00ul, 23, 182},  //     11111111|11111111|1101110
+      {0xffffde00ul, 23, 183},  //     11111111|11111111|1101111
+      {0xfffea000ul, 20, 184},  //     11111111|11111110|1010
+      {0xffff8800ul, 22, 185},  //     11111111|11111111|100010
+      {0xffff8c00ul, 22, 186},  //     11111111|11111111|100011
+      {0xffff9000ul, 22, 187},  //     11111111|11111111|100100
+      {0xffffe000ul, 23, 188},  //     11111111|11111111|1110000
+      {0xffff9400ul, 22, 189},  //     11111111|11111111|100101
+      {0xffff9800ul, 22, 190},  //     11111111|11111111|100110
+      {0xffffe200ul, 23, 191},  //     11111111|11111111|1110001
+      {0xfffff800ul, 26, 192},  //     11111111|11111111|11111000|00
+      {0xfffff840ul, 26, 193},  //     11111111|11111111|11111000|01
+      {0xfffeb000ul, 20, 194},  //     11111111|11111110|1011
+      {0xfffe2000ul, 19, 195},  //     11111111|11111110|001
+      {0xffff9c00ul, 22, 196},  //     11111111|11111111|100111
+      {0xffffe400ul, 23, 197},  //     11111111|11111111|1110010
+      {0xffffa000ul, 22, 198},  //     11111111|11111111|101000
+      {0xfffff600ul, 25, 199},  //     11111111|11111111|11110110|0
+      {0xfffff880ul, 26, 200},  //     11111111|11111111|11111000|10
+      {0xfffff8c0ul, 26, 201},  //     11111111|11111111|11111000|11
+      {0xfffff900ul, 26, 202},  //     11111111|11111111|11111001|00
+      {0xfffffbc0ul, 27, 203},  //     11111111|11111111|11111011|110
+      {0xfffffbe0ul, 27, 204},  //     11111111|11111111|11111011|111
+      {0xfffff940ul, 26, 205},  //     11111111|11111111|11111001|01
+      {0xfffff100ul, 24, 206},  //     11111111|11111111|11110001
+      {0xfffff680ul, 25, 207},  //     11111111|11111111|11110110|1
+      {0xfffe4000ul, 19, 208},  //     11111111|11111110|010
+      {0xffff1800ul, 21, 209},  //     11111111|11111111|00011
+      {0xfffff980ul, 26, 210},  //     11111111|11111111|11111001|10
+      {0xfffffc00ul, 27, 211},  //     11111111|11111111|11111100|000
+      {0xfffffc20ul, 27, 212},  //     11111111|11111111|11111100|001
+      {0xfffff9c0ul, 26, 213},  //     11111111|11111111|11111001|11
+      {0xfffffc40ul, 27, 214},  //     11111111|11111111|11111100|010
+      {0xfffff200ul, 24, 215},  //     11111111|11111111|11110010
+      {0xffff2000ul, 21, 216},  //     11111111|11111111|00100
+      {0xffff2800ul, 21, 217},  //     11111111|11111111|00101
+      {0xfffffa00ul, 26, 218},  //     11111111|11111111|11111010|00
+      {0xfffffa40ul, 26, 219},  //     11111111|11111111|11111010|01
+      {0xffffffd0ul, 28, 220},  //     11111111|11111111|11111111|1101
+      {0xfffffc60ul, 27, 221},  //     11111111|11111111|11111100|011
+      {0xfffffc80ul, 27, 222},  //     11111111|11111111|11111100|100
+      {0xfffffca0ul, 27, 223},  //     11111111|11111111|11111100|101
+      {0xfffec000ul, 20, 224},  //     11111111|11111110|1100
+      {0xfffff300ul, 24, 225},  //     11111111|11111111|11110011
+      {0xfffed000ul, 20, 226},  //     11111111|11111110|1101
+      {0xffff3000ul, 21, 227},  //     11111111|11111111|00110
+      {0xffffa400ul, 22, 228},  //     11111111|11111111|101001
+      {0xffff3800ul, 21, 229},  //     11111111|11111111|00111
+      {0xffff4000ul, 21, 230},  //     11111111|11111111|01000
+      {0xffffe600ul, 23, 231},  //     11111111|11111111|1110011
+      {0xffffa800ul, 22, 232},  //     11111111|11111111|101010
+      {0xffffac00ul, 22, 233},  //     11111111|11111111|101011
+      {0xfffff700ul, 25, 234},  //     11111111|11111111|11110111|0
+      {0xfffff780ul, 25, 235},  //     11111111|11111111|11110111|1
+      {0xfffff400ul, 24, 236},  //     11111111|11111111|11110100
+      {0xfffff500ul, 24, 237},  //     11111111|11111111|11110101
+      {0xfffffa80ul, 26, 238},  //     11111111|11111111|11111010|10
+      {0xffffe800ul, 23, 239},  //     11111111|11111111|1110100
+      {0xfffffac0ul, 26, 240},  //     11111111|11111111|11111010|11
+      {0xfffffcc0ul, 27, 241},  //     11111111|11111111|11111100|110
+      {0xfffffb00ul, 26, 242},  //     11111111|11111111|11111011|00
+      {0xfffffb40ul, 26, 243},  //     11111111|11111111|11111011|01
+      {0xfffffce0ul, 27, 244},  //     11111111|11111111|11111100|111
+      {0xfffffd00ul, 27, 245},  //     11111111|11111111|11111101|000
+      {0xfffffd20ul, 27, 246},  //     11111111|11111111|11111101|001
+      {0xfffffd40ul, 27, 247},  //     11111111|11111111|11111101|010
+      {0xfffffd60ul, 27, 248},  //     11111111|11111111|11111101|011
+      {0xffffffe0ul, 28, 249},  //     11111111|11111111|11111111|1110
+      {0xfffffd80ul, 27, 250},  //     11111111|11111111|11111101|100
+      {0xfffffda0ul, 27, 251},  //     11111111|11111111|11111101|101
+      {0xfffffdc0ul, 27, 252},  //     11111111|11111111|11111101|110
+      {0xfffffde0ul, 27, 253},  //     11111111|11111111|11111101|111
+      {0xfffffe00ul, 27, 254},  //     11111111|11111111|11111110|000
+      {0xfffffb80ul, 26, 255},  //     11111111|11111111|11111011|10
+      {0xfffffffcul, 30, 256},  // EOS 11111111|11111111|11111111|111111
+  };
+  return *kHpackHuffmanCode;
+}
+
+// The "constructor" for a HpackStaticEntry that computes the lengths at
+// compile time.
+#define STATIC_ENTRY(name, value) \
+  { name, SPDY_ARRAYSIZE(name) - 1, value, SPDY_ARRAYSIZE(value) - 1 }
+
+const std::vector<HpackStaticEntry>& HpackStaticTableVector() {
+  static const auto* kHpackStaticTable = new std::vector<HpackStaticEntry>{
+      STATIC_ENTRY(":authority", ""),                    // 1
+      STATIC_ENTRY(":method", "GET"),                    // 2
+      STATIC_ENTRY(":method", "POST"),                   // 3
+      STATIC_ENTRY(":path", "/"),                        // 4
+      STATIC_ENTRY(":path", "/index.html"),              // 5
+      STATIC_ENTRY(":scheme", "http"),                   // 6
+      STATIC_ENTRY(":scheme", "https"),                  // 7
+      STATIC_ENTRY(":status", "200"),                    // 8
+      STATIC_ENTRY(":status", "204"),                    // 9
+      STATIC_ENTRY(":status", "206"),                    // 10
+      STATIC_ENTRY(":status", "304"),                    // 11
+      STATIC_ENTRY(":status", "400"),                    // 12
+      STATIC_ENTRY(":status", "404"),                    // 13
+      STATIC_ENTRY(":status", "500"),                    // 14
+      STATIC_ENTRY("accept-charset", ""),                // 15
+      STATIC_ENTRY("accept-encoding", "gzip, deflate"),  // 16
+      STATIC_ENTRY("accept-language", ""),               // 17
+      STATIC_ENTRY("accept-ranges", ""),                 // 18
+      STATIC_ENTRY("accept", ""),                        // 19
+      STATIC_ENTRY("access-control-allow-origin", ""),   // 20
+      STATIC_ENTRY("age", ""),                           // 21
+      STATIC_ENTRY("allow", ""),                         // 22
+      STATIC_ENTRY("authorization", ""),                 // 23
+      STATIC_ENTRY("cache-control", ""),                 // 24
+      STATIC_ENTRY("content-disposition", ""),           // 25
+      STATIC_ENTRY("content-encoding", ""),              // 26
+      STATIC_ENTRY("content-language", ""),              // 27
+      STATIC_ENTRY("content-length", ""),                // 28
+      STATIC_ENTRY("content-location", ""),              // 29
+      STATIC_ENTRY("content-range", ""),                 // 30
+      STATIC_ENTRY("content-type", ""),                  // 31
+      STATIC_ENTRY("cookie", ""),                        // 32
+      STATIC_ENTRY("date", ""),                          // 33
+      STATIC_ENTRY("etag", ""),                          // 34
+      STATIC_ENTRY("expect", ""),                        // 35
+      STATIC_ENTRY("expires", ""),                       // 36
+      STATIC_ENTRY("from", ""),                          // 37
+      STATIC_ENTRY("host", ""),                          // 38
+      STATIC_ENTRY("if-match", ""),                      // 39
+      STATIC_ENTRY("if-modified-since", ""),             // 40
+      STATIC_ENTRY("if-none-match", ""),                 // 41
+      STATIC_ENTRY("if-range", ""),                      // 42
+      STATIC_ENTRY("if-unmodified-since", ""),           // 43
+      STATIC_ENTRY("last-modified", ""),                 // 44
+      STATIC_ENTRY("link", ""),                          // 45
+      STATIC_ENTRY("location", ""),                      // 46
+      STATIC_ENTRY("max-forwards", ""),                  // 47
+      STATIC_ENTRY("proxy-authenticate", ""),            // 48
+      STATIC_ENTRY("proxy-authorization", ""),           // 49
+      STATIC_ENTRY("range", ""),                         // 50
+      STATIC_ENTRY("referer", ""),                       // 51
+      STATIC_ENTRY("refresh", ""),                       // 52
+      STATIC_ENTRY("retry-after", ""),                   // 53
+      STATIC_ENTRY("server", ""),                        // 54
+      STATIC_ENTRY("set-cookie", ""),                    // 55
+      STATIC_ENTRY("strict-transport-security", ""),     // 56
+      STATIC_ENTRY("transfer-encoding", ""),             // 57
+      STATIC_ENTRY("user-agent", ""),                    // 58
+      STATIC_ENTRY("vary", ""),                          // 59
+      STATIC_ENTRY("via", ""),                           // 60
+      STATIC_ENTRY("www-authenticate", ""),              // 61
+  };
+  return *kHpackStaticTable;
+}
+
+#undef STATIC_ENTRY
+
+const HpackHuffmanTable& ObtainHpackHuffmanTable() {
+  static const HpackHuffmanTable* const shared_huffman_table = []() {
+    auto* table = new HpackHuffmanTable();
+    CHECK(table->Initialize(HpackHuffmanCodeVector().data(),
+                            HpackHuffmanCodeVector().size()));
+    CHECK(table->IsInitialized());
+    return table;
+  }();
+  return *shared_huffman_table;
+}
+
+const HpackStaticTable& ObtainHpackStaticTable() {
+  static const HpackStaticTable* const shared_static_table = []() {
+    auto* table = new HpackStaticTable();
+    table->Initialize(HpackStaticTableVector().data(),
+                      HpackStaticTableVector().size());
+    CHECK(table->IsInitialized());
+    return table;
+  }();
+  return *shared_static_table;
+}
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_constants.h b/spdy/core/hpack/hpack_constants.h
new file mode 100644
index 0000000..3f4026b
--- /dev/null
+++ b/spdy/core/hpack/hpack_constants.h
@@ -0,0 +1,95 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_CONSTANTS_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_CONSTANTS_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+
+// All section references below are to
+// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08
+
+namespace spdy {
+
+// An HpackPrefix signifies |bits| stored in the top |bit_size| bits
+// of an octet.
+struct HpackPrefix {
+  uint8_t bits;
+  size_t bit_size;
+};
+
+// Represents a symbol and its Huffman code (stored in most-significant bits).
+struct HpackHuffmanSymbol {
+  uint32_t code;
+  uint8_t length;
+  uint16_t id;
+};
+
+// An entry in the static table. Must be a POD in order to avoid static
+// initializers, i.e. no user-defined constructors or destructors.
+struct HpackStaticEntry {
+  const char* const name;
+  const size_t name_len;
+  const char* const value;
+  const size_t value_len;
+};
+
+class HpackHuffmanTable;
+class HpackStaticTable;
+
+// Defined in RFC 7540, 6.5.2.
+const uint32_t kDefaultHeaderTableSizeSetting = 4096;
+
+// RFC 7541, 5.2: Flag for a string literal that is stored unmodified (i.e.,
+// without Huffman encoding).
+const HpackPrefix kStringLiteralIdentityEncoded = {0x0, 1};
+
+// RFC 7541, 5.2: Flag for a Huffman-coded string literal.
+const HpackPrefix kStringLiteralHuffmanEncoded = {0x1, 1};
+
+// RFC 7541, 6.1: Opcode for an indexed header field.
+const HpackPrefix kIndexedOpcode = {0b1, 1};
+
+// RFC 7541, 6.2.1: Opcode for a literal header field with incremental indexing.
+const HpackPrefix kLiteralIncrementalIndexOpcode = {0b01, 2};
+
+// RFC 7541, 6.2.2: Opcode for a literal header field without indexing.
+const HpackPrefix kLiteralNoIndexOpcode = {0b0000, 4};
+
+// RFC 7541, 6.2.3: Opcode for a literal header field which is never indexed.
+// Currently unused.
+// const HpackPrefix kLiteralNeverIndexOpcode = {0b0001, 4};
+
+// RFC 7541, 6.3: Opcode for maximum header table size update. Begins a
+// varint-encoded table size with a 5-bit prefix.
+const HpackPrefix kHeaderTableSizeUpdateOpcode = {0b001, 3};
+
+// Symbol code table from RFC 7541, "Appendix C. Huffman Code".
+SPDY_EXPORT_PRIVATE const std::vector<HpackHuffmanSymbol>&
+HpackHuffmanCodeVector();
+
+// Static table from RFC 7541, "Appendix B. Static Table Definition".
+SPDY_EXPORT_PRIVATE const std::vector<HpackStaticEntry>&
+HpackStaticTableVector();
+
+// Returns a HpackHuffmanTable instance initialized with |kHpackHuffmanCode|.
+// The instance is read-only, has static lifetime, and is safe to share amoung
+// threads. This function is thread-safe.
+SPDY_EXPORT_PRIVATE const HpackHuffmanTable& ObtainHpackHuffmanTable();
+
+// Returns a HpackStaticTable instance initialized with |kHpackStaticTable|.
+// The instance is read-only, has static lifetime, and is safe to share amoung
+// threads. This function is thread-safe.
+SPDY_EXPORT_PRIVATE const HpackStaticTable& ObtainHpackStaticTable();
+
+// Pseudo-headers start with a colon.  (HTTP2 8.1.2.1., HPACK 3.1.)
+const char kPseudoHeaderPrefix = ':';
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_HPACK_HPACK_CONSTANTS_H_
diff --git a/spdy/core/hpack/hpack_decoder_adapter.cc b/spdy/core/hpack/hpack_decoder_adapter.cc
new file mode 100644
index 0000000..309af39
--- /dev/null
+++ b/spdy/core/hpack/hpack_decoder_adapter.cc
@@ -0,0 +1,201 @@
+// 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/spdy/core/hpack/hpack_decoder_adapter.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_status.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+
+using ::http2::DecodeBuffer;
+using ::http2::HpackEntryType;
+using ::http2::HpackString;
+
+namespace spdy {
+namespace {
+const size_t kMaxDecodeBufferSizeBytes = 32 * 1024;  // 32 KB
+}  // namespace
+
+HpackDecoderAdapter::HpackDecoderAdapter()
+    : hpack_decoder_(&listener_adapter_, kMaxDecodeBufferSizeBytes),
+      max_decode_buffer_size_bytes_(kMaxDecodeBufferSizeBytes),
+      header_block_started_(false) {}
+
+HpackDecoderAdapter::~HpackDecoderAdapter() = default;
+
+void HpackDecoderAdapter::ApplyHeaderTableSizeSetting(size_t size_setting) {
+  DVLOG(2) << "HpackDecoderAdapter::ApplyHeaderTableSizeSetting";
+  hpack_decoder_.ApplyHeaderTableSizeSetting(size_setting);
+}
+
+void HpackDecoderAdapter::HandleControlFrameHeadersStart(
+    SpdyHeadersHandlerInterface* handler) {
+  DVLOG(2) << "HpackDecoderAdapter::HandleControlFrameHeadersStart";
+  DCHECK(!header_block_started_);
+  listener_adapter_.set_handler(handler);
+}
+
+bool HpackDecoderAdapter::HandleControlFrameHeadersData(
+    const char* headers_data,
+    size_t headers_data_length) {
+  DVLOG(2) << "HpackDecoderAdapter::HandleControlFrameHeadersData: len="
+           << headers_data_length;
+  if (!header_block_started_) {
+    // Initialize the decoding process here rather than in
+    // HandleControlFrameHeadersStart because that method is not always called.
+    header_block_started_ = true;
+    if (!hpack_decoder_.StartDecodingBlock()) {
+      header_block_started_ = false;
+      return false;
+    }
+  }
+
+  // Sometimes we get a call with headers_data==nullptr and
+  // headers_data_length==0, in which case we need to avoid creating
+  // a DecodeBuffer, which would otherwise complain.
+  if (headers_data_length > 0) {
+    DCHECK_NE(headers_data, nullptr);
+    if (headers_data_length > max_decode_buffer_size_bytes_) {
+      DVLOG(1) << "max_decode_buffer_size_bytes_ < headers_data_length: "
+               << max_decode_buffer_size_bytes_ << " < " << headers_data_length;
+      return false;
+    }
+    listener_adapter_.AddToTotalHpackBytes(headers_data_length);
+    http2::DecodeBuffer db(headers_data, headers_data_length);
+    bool ok = hpack_decoder_.DecodeFragment(&db);
+    DCHECK(!ok || db.Empty()) << "Remaining=" << db.Remaining();
+    return ok;
+  }
+  return true;
+}
+
+bool HpackDecoderAdapter::HandleControlFrameHeadersComplete(
+    size_t* compressed_len) {
+  DVLOG(2) << "HpackDecoderAdapter::HandleControlFrameHeadersComplete";
+  if (compressed_len != nullptr) {
+    *compressed_len = listener_adapter_.total_hpack_bytes();
+  }
+  if (!hpack_decoder_.EndDecodingBlock()) {
+    DVLOG(3) << "EndDecodingBlock returned false";
+    return false;
+  }
+  header_block_started_ = false;
+  return true;
+}
+
+const SpdyHeaderBlock& HpackDecoderAdapter::decoded_block() const {
+  return listener_adapter_.decoded_block();
+}
+
+void HpackDecoderAdapter::SetHeaderTableDebugVisitor(
+    std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) {
+  DVLOG(2) << "HpackDecoderAdapter::SetHeaderTableDebugVisitor";
+  if (visitor != nullptr) {
+    listener_adapter_.SetHeaderTableDebugVisitor(std::move(visitor));
+    hpack_decoder_.set_tables_debug_listener(&listener_adapter_);
+  } else {
+    hpack_decoder_.set_tables_debug_listener(nullptr);
+    listener_adapter_.SetHeaderTableDebugVisitor(nullptr);
+  }
+}
+
+void HpackDecoderAdapter::set_max_decode_buffer_size_bytes(
+    size_t max_decode_buffer_size_bytes) {
+  DVLOG(2) << "HpackDecoderAdapter::set_max_decode_buffer_size_bytes";
+  max_decode_buffer_size_bytes_ = max_decode_buffer_size_bytes;
+  hpack_decoder_.set_max_string_size_bytes(max_decode_buffer_size_bytes);
+}
+
+size_t HpackDecoderAdapter::EstimateMemoryUsage() const {
+  return SpdyEstimateMemoryUsage(hpack_decoder_);
+}
+
+HpackDecoderAdapter::ListenerAdapter::ListenerAdapter() : handler_(nullptr) {}
+HpackDecoderAdapter::ListenerAdapter::~ListenerAdapter() = default;
+
+void HpackDecoderAdapter::ListenerAdapter::set_handler(
+    SpdyHeadersHandlerInterface* handler) {
+  handler_ = handler;
+}
+
+void HpackDecoderAdapter::ListenerAdapter::SetHeaderTableDebugVisitor(
+    std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) {
+  visitor_ = std::move(visitor);
+}
+
+void HpackDecoderAdapter::ListenerAdapter::OnHeaderListStart() {
+  DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnHeaderListStart";
+  total_hpack_bytes_ = 0;
+  total_uncompressed_bytes_ = 0;
+  decoded_block_.clear();
+  if (handler_ != nullptr) {
+    handler_->OnHeaderBlockStart();
+  }
+}
+
+void HpackDecoderAdapter::ListenerAdapter::OnHeader(HpackEntryType entry_type,
+                                                    const HpackString& name,
+                                                    const HpackString& value) {
+  DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnHeader:\n name: " << name
+           << "\n value: " << value;
+  total_uncompressed_bytes_ += name.size() + value.size();
+  if (handler_ == nullptr) {
+    DVLOG(3) << "Adding to decoded_block";
+    decoded_block_.AppendValueOrAddHeader(name.ToStringPiece(),
+                                          value.ToStringPiece());
+  } else {
+    DVLOG(3) << "Passing to handler";
+    handler_->OnHeader(name.ToStringPiece(), value.ToStringPiece());
+  }
+}
+
+void HpackDecoderAdapter::ListenerAdapter::OnHeaderListEnd() {
+  DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnHeaderListEnd";
+  // We don't clear the SpdyHeaderBlock here to allow access to it until the
+  // next HPACK block is decoded.
+  if (handler_ != nullptr) {
+    handler_->OnHeaderBlockEnd(total_uncompressed_bytes_, total_hpack_bytes_);
+    handler_ = nullptr;
+  }
+}
+
+void HpackDecoderAdapter::ListenerAdapter::OnHeaderErrorDetected(
+    SpdyStringPiece error_message) {
+  VLOG(1) << error_message;
+}
+
+int64_t HpackDecoderAdapter::ListenerAdapter::OnEntryInserted(
+    const http2::HpackStringPair& sp,
+    size_t insert_count) {
+  DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnEntryInserted: " << sp
+           << ",  insert_count=" << insert_count;
+  if (visitor_ == nullptr) {
+    return 0;
+  }
+  HpackEntry entry(sp.name.ToStringPiece(), sp.value.ToStringPiece(),
+                   /*is_static*/ false, insert_count);
+  int64_t time_added = visitor_->OnNewEntry(entry);
+  DVLOG(2)
+      << "HpackDecoderAdapter::ListenerAdapter::OnEntryInserted: time_added="
+      << time_added;
+  return time_added;
+}
+
+void HpackDecoderAdapter::ListenerAdapter::OnUseEntry(
+    const http2::HpackStringPair& sp,
+    size_t insert_count,
+    int64_t time_added) {
+  DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnUseEntry: " << sp
+           << ",  insert_count=" << insert_count
+           << ",  time_added=" << time_added;
+  if (visitor_ != nullptr) {
+    HpackEntry entry(sp.name.ToStringPiece(), sp.value.ToStringPiece(),
+                     /*is_static*/ false, insert_count);
+    entry.set_time_added(time_added);
+    visitor_->OnUseEntry(entry);
+  }
+}
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_decoder_adapter.h b/spdy/core/hpack/hpack_decoder_adapter.h
new file mode 100644
index 0000000..b497fe7
--- /dev/null
+++ b/spdy/core/hpack/hpack_decoder_adapter.h
@@ -0,0 +1,159 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_DECODER_ADAPTER_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_DECODER_ADAPTER_H_
+
+// HpackDecoderAdapter uses http2::HpackDecoder to decode HPACK blocks into
+// HTTP/2 header lists as outlined in http://tools.ietf.org/html/rfc7541.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+namespace test {
+class HpackDecoderAdapterPeer;
+}  // namespace test
+
+class SPDY_EXPORT_PRIVATE HpackDecoderAdapter {
+ public:
+  friend test::HpackDecoderAdapterPeer;
+  HpackDecoderAdapter();
+  HpackDecoderAdapter(const HpackDecoderAdapter&) = delete;
+  HpackDecoderAdapter& operator=(const HpackDecoderAdapter&) = delete;
+  ~HpackDecoderAdapter();
+
+  // Called upon acknowledgement of SETTINGS_HEADER_TABLE_SIZE.
+  void ApplyHeaderTableSizeSetting(size_t size_setting);
+
+  // If a SpdyHeadersHandlerInterface is provided, the decoder will emit
+  // headers to it rather than accumulating them in a SpdyHeaderBlock.
+  // Does not take ownership of the handler, but does use the pointer until
+  // the current HPACK block is completely decoded.
+  void HandleControlFrameHeadersStart(SpdyHeadersHandlerInterface* handler);
+
+  // Called as HPACK block fragments arrive. Returns false if an error occurred
+  // while decoding the block. Does not take ownership of headers_data.
+  bool HandleControlFrameHeadersData(const char* headers_data,
+                                     size_t headers_data_length);
+
+  // Called after a HPACK block has been completely delivered via
+  // HandleControlFrameHeadersData(). Returns false if an error occurred.
+  // |compressed_len| if non-null will be set to the size of the encoded
+  // buffered block that was accumulated in HandleControlFrameHeadersData(),
+  // to support subsequent calculation of compression percentage.
+  // Discards the handler supplied at the start of decoding the block.
+  // TODO(jamessynge): Determine if compressed_len is needed; it is used to
+  // produce UUMA stat Net.SpdyHpackDecompressionPercentage, but only for
+  // deprecated SPDY3.
+  bool HandleControlFrameHeadersComplete(size_t* compressed_len);
+
+  // Accessor for the most recently decoded headers block. Valid until the next
+  // call to HandleControlFrameHeadersData().
+  // TODO(birenroy): Remove this method when all users of HpackDecoder specify
+  // a SpdyHeadersHandlerInterface.
+  const SpdyHeaderBlock& decoded_block() const;
+
+  void SetHeaderTableDebugVisitor(
+      std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor);
+
+  // Set how much encoded data this decoder is willing to buffer.
+  // TODO(jamessynge): Resolve definition of this value, as it is currently
+  // too tied to a single implementation. We probably want to limit one or more
+  // of these: individual name or value strings, header entries, the entire
+  // header list, or the HPACK block; we probably shouldn't care about the size
+  // of individual transport buffers.
+  void set_max_decode_buffer_size_bytes(size_t max_decode_buffer_size_bytes);
+
+  size_t EstimateMemoryUsage() const;
+
+ private:
+  class SPDY_EXPORT_PRIVATE ListenerAdapter
+      : public http2::HpackDecoderListener,
+        public http2::HpackDecoderTablesDebugListener {
+   public:
+    ListenerAdapter();
+    ~ListenerAdapter() override;
+
+    // If a SpdyHeadersHandlerInterface is provided, the decoder will emit
+    // headers to it rather than accumulating them in a SpdyHeaderBlock.
+    // Does not take ownership of the handler, but does use the pointer until
+    // the current HPACK block is completely decoded.
+    void set_handler(SpdyHeadersHandlerInterface* handler);
+    const SpdyHeaderBlock& decoded_block() const { return decoded_block_; }
+
+    void SetHeaderTableDebugVisitor(
+        std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor);
+
+    // Override the HpackDecoderListener methods:
+    void OnHeaderListStart() override;
+    void OnHeader(http2::HpackEntryType entry_type,
+                  const http2::HpackString& name,
+                  const http2::HpackString& value) override;
+    void OnHeaderListEnd() override;
+    void OnHeaderErrorDetected(SpdyStringPiece error_message) override;
+
+    // Override the HpackDecoderTablesDebugListener methods:
+    int64_t OnEntryInserted(const http2::HpackStringPair& entry,
+                            size_t insert_count) override;
+    void OnUseEntry(const http2::HpackStringPair& entry,
+                    size_t insert_count,
+                    int64_t insert_time) override;
+
+    void AddToTotalHpackBytes(size_t delta) { total_hpack_bytes_ += delta; }
+    size_t total_hpack_bytes() const { return total_hpack_bytes_; }
+
+   private:
+    // If the caller doesn't provide a handler, the header list is stored in
+    // this SpdyHeaderBlock.
+    SpdyHeaderBlock decoded_block_;
+
+    // If non-NULL, handles decoded headers. Not owned.
+    SpdyHeadersHandlerInterface* handler_;
+
+    // Total bytes that have been received as input (i.e. HPACK encoded)
+    // in the current HPACK block.
+    size_t total_hpack_bytes_;
+
+    // Total bytes of the name and value strings in the current HPACK block.
+    size_t total_uncompressed_bytes_;
+
+    // visitor_ is used by a QUIC experiment regarding HPACK; remove
+    // when the experiment is done.
+    std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor_;
+  };
+
+  // Converts calls to HpackDecoderListener into calls to
+  // SpdyHeadersHandlerInterface.
+  ListenerAdapter listener_adapter_;
+
+  // The actual decoder.
+  http2::HpackDecoder hpack_decoder_;
+
+  // How much encoded data this decoder is willing to buffer.
+  size_t max_decode_buffer_size_bytes_;
+
+  // Flag to keep track of having seen the header block start. Needed at the
+  // moment because HandleControlFrameHeadersStart won't be called if a handler
+  // is not being provided by the caller.
+  bool header_block_started_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_HPACK_HPACK_DECODER_ADAPTER_H_
diff --git a/spdy/core/hpack/hpack_decoder_adapter_test.cc b/spdy/core/hpack/hpack_decoder_adapter_test.cc
new file mode 100644
index 0000000..765c2ea
--- /dev/null
+++ b/spdy/core/hpack/hpack_decoder_adapter_test.cc
@@ -0,0 +1,1097 @@
+// 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/spdy/core/hpack/hpack_decoder_adapter.h"
+
+// Tests of HpackDecoderAdapter.
+
+#include <stdint.h>
+
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+using ::http2::HpackEntryType;
+using ::http2::HpackString;
+using ::http2::HpackStringPair;
+using ::http2::test::HpackBlockBuilder;
+using ::http2::test::HpackDecoderPeer;
+using ::testing::ElementsAre;
+using ::testing::Pair;
+
+namespace http2 {
+namespace test {
+
+class HpackDecoderStatePeer {
+ public:
+  static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) {
+    return &state->decoder_tables_;
+  }
+};
+
+class HpackDecoderPeer {
+ public:
+  static HpackDecoderState* GetDecoderState(HpackDecoder* decoder) {
+    return &decoder->decoder_state_;
+  }
+  static HpackDecoderTables* GetDecoderTables(HpackDecoder* decoder) {
+    return HpackDecoderStatePeer::GetDecoderTables(GetDecoderState(decoder));
+  }
+};
+
+}  // namespace test
+}  // namespace http2
+
+namespace spdy {
+namespace test {
+
+class HpackDecoderAdapterPeer {
+ public:
+  explicit HpackDecoderAdapterPeer(HpackDecoderAdapter* decoder)
+      : decoder_(decoder) {}
+
+  void HandleHeaderRepresentation(SpdyStringPiece name, SpdyStringPiece value) {
+    decoder_->listener_adapter_.OnHeader(HpackEntryType::kIndexedLiteralHeader,
+                                         HpackString(name), HpackString(value));
+  }
+
+  http2::HpackDecoderTables* GetDecoderTables() {
+    return HpackDecoderPeer::GetDecoderTables(&decoder_->hpack_decoder_);
+  }
+
+  const HpackStringPair* GetTableEntry(uint32_t index) {
+    return GetDecoderTables()->Lookup(index);
+  }
+
+  size_t current_header_table_size() {
+    return GetDecoderTables()->current_header_table_size();
+  }
+
+  size_t header_table_size_limit() {
+    return GetDecoderTables()->header_table_size_limit();
+  }
+
+  void set_header_table_size_limit(size_t size) {
+    return GetDecoderTables()->DynamicTableSizeUpdate(size);
+  }
+
+ private:
+  HpackDecoderAdapter* decoder_;
+};
+
+class HpackEncoderPeer {
+ public:
+  static void CookieToCrumbs(const HpackEncoder::Representation& cookie,
+                             HpackEncoder::Representations* crumbs_out) {
+    HpackEncoder::CookieToCrumbs(cookie, crumbs_out);
+  }
+};
+
+namespace {
+
+const bool kNoCheckDecodedSize = false;
+const char* kCookieKey = "cookie";
+
+// Is HandleControlFrameHeadersStart to be called, and with what value?
+enum StartChoice { START_WITH_HANDLER, START_WITHOUT_HANDLER, NO_START };
+
+class HpackDecoderAdapterTest
+    : public ::testing::TestWithParam<std::tuple<StartChoice, bool>> {
+ protected:
+  HpackDecoderAdapterTest() : decoder_(), decoder_peer_(&decoder_) {}
+
+  void SetUp() override {
+    std::tie(start_choice_, randomly_split_input_buffer_) = GetParam();
+  }
+
+  void HandleControlFrameHeadersStart() {
+    bytes_passed_in_ = 0;
+    switch (start_choice_) {
+      case START_WITH_HANDLER:
+        decoder_.HandleControlFrameHeadersStart(&handler_);
+        break;
+      case START_WITHOUT_HANDLER:
+        decoder_.HandleControlFrameHeadersStart(nullptr);
+        break;
+      case NO_START:
+        break;
+    }
+  }
+
+  bool HandleControlFrameHeadersData(SpdyStringPiece str) {
+    VLOG(3) << "HandleControlFrameHeadersData:\n" << SpdyHexDump(str);
+    bytes_passed_in_ += str.size();
+    return decoder_.HandleControlFrameHeadersData(str.data(), str.size());
+  }
+
+  bool HandleControlFrameHeadersComplete(size_t* size) {
+    bool rc = decoder_.HandleControlFrameHeadersComplete(size);
+    if (size != nullptr) {
+      EXPECT_EQ(*size, bytes_passed_in_);
+    }
+    return rc;
+  }
+
+  bool DecodeHeaderBlock(SpdyStringPiece str, bool check_decoded_size = true) {
+    // Don't call this again if HandleControlFrameHeadersData failed previously.
+    EXPECT_FALSE(decode_has_failed_);
+    HandleControlFrameHeadersStart();
+    if (randomly_split_input_buffer_) {
+      do {
+        // Decode some fragment of the remaining bytes.
+        size_t bytes = str.size();
+        if (!str.empty()) {
+          bytes = random_.Uniform(str.size()) + 1;
+        }
+        EXPECT_LE(bytes, str.size());
+        if (!HandleControlFrameHeadersData(str.substr(0, bytes))) {
+          decode_has_failed_ = true;
+          return false;
+        }
+        str.remove_prefix(bytes);
+      } while (!str.empty());
+    } else if (!HandleControlFrameHeadersData(str)) {
+      decode_has_failed_ = true;
+      return false;
+    }
+    // Want to get out the number of compressed bytes that were decoded,
+    // so pass in a pointer if no handler.
+    size_t total_hpack_bytes = 0;
+    if (start_choice_ == START_WITH_HANDLER) {
+      if (!HandleControlFrameHeadersComplete(nullptr)) {
+        decode_has_failed_ = true;
+        return false;
+      }
+      total_hpack_bytes = handler_.compressed_header_bytes_parsed();
+    } else {
+      if (!HandleControlFrameHeadersComplete(&total_hpack_bytes)) {
+        decode_has_failed_ = true;
+        return false;
+      }
+    }
+    EXPECT_EQ(total_hpack_bytes, bytes_passed_in_);
+    if (check_decoded_size && start_choice_ == START_WITH_HANDLER) {
+      EXPECT_EQ(handler_.header_bytes_parsed(), SizeOfHeaders(decoded_block()));
+    }
+    return true;
+  }
+
+  bool EncodeAndDecodeDynamicTableSizeUpdates(size_t first, size_t second) {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(first);
+    if (second != first) {
+      hbb.AppendDynamicTableSizeUpdate(second);
+    }
+    return DecodeHeaderBlock(hbb.buffer());
+  }
+
+  const SpdyHeaderBlock& decoded_block() const {
+    if (start_choice_ == START_WITH_HANDLER) {
+      return handler_.decoded_block();
+    } else {
+      return decoder_.decoded_block();
+    }
+  }
+
+  static size_t SizeOfHeaders(const SpdyHeaderBlock& headers) {
+    size_t size = 0;
+    for (const auto& kv : headers) {
+      if (kv.first == kCookieKey) {
+        HpackEncoder::Representations crumbs;
+        HpackEncoderPeer::CookieToCrumbs(kv, &crumbs);
+        for (const auto& crumb : crumbs) {
+          size += crumb.first.size() + crumb.second.size();
+        }
+      } else {
+        size += kv.first.size() + kv.second.size();
+      }
+    }
+    return size;
+  }
+
+  const SpdyHeaderBlock& DecodeBlockExpectingSuccess(SpdyStringPiece str) {
+    EXPECT_TRUE(DecodeHeaderBlock(str));
+    return decoded_block();
+  }
+
+  void expectEntry(size_t index,
+                   size_t size,
+                   const SpdyString& name,
+                   const SpdyString& value) {
+    const HpackStringPair* entry = decoder_peer_.GetTableEntry(index);
+    EXPECT_EQ(name, entry->name) << "index " << index;
+    EXPECT_EQ(value, entry->value);
+    EXPECT_EQ(size, entry->size());
+  }
+
+  SpdyHeaderBlock MakeHeaderBlock(
+      const std::vector<std::pair<SpdyString, SpdyString>>& headers) {
+    SpdyHeaderBlock result;
+    for (const auto& kv : headers) {
+      result.AppendValueOrAddHeader(kv.first, kv.second);
+    }
+    return result;
+  }
+
+  http2::test::Http2Random random_;
+  HpackDecoderAdapter decoder_;
+  test::HpackDecoderAdapterPeer decoder_peer_;
+  TestHeadersHandler handler_;
+  StartChoice start_choice_;
+  bool randomly_split_input_buffer_;
+  bool decode_has_failed_ = false;
+  size_t bytes_passed_in_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+    NoHandler,
+    HpackDecoderAdapterTest,
+    ::testing::Combine(::testing::Values(START_WITHOUT_HANDLER, NO_START),
+                       ::testing::Bool()));
+
+INSTANTIATE_TEST_CASE_P(
+    WithHandler,
+    HpackDecoderAdapterTest,
+    ::testing::Combine(::testing::Values(START_WITH_HANDLER),
+                       ::testing::Bool()));
+
+TEST_P(HpackDecoderAdapterTest,
+       AddHeaderDataWithHandleControlFrameHeadersData) {
+  // The hpack decode buffer size is limited in size. This test verifies that
+  // adding encoded data under that limit is accepted, and data that exceeds the
+  // limit is rejected.
+  HandleControlFrameHeadersStart();
+  const size_t kMaxBufferSizeBytes = 50;
+  const SpdyString a_value = SpdyString(49, 'x');
+  decoder_.set_max_decode_buffer_size_bytes(kMaxBufferSizeBytes);
+  HpackBlockBuilder hbb;
+  hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+                                false, "a", false, a_value);
+  const SpdyString& s = hbb.buffer();
+  EXPECT_GT(s.size(), kMaxBufferSizeBytes);
+
+  // Any one in input buffer must not exceed kMaxBufferSizeBytes.
+  EXPECT_TRUE(HandleControlFrameHeadersData(s.substr(0, s.size() / 2)));
+  EXPECT_TRUE(HandleControlFrameHeadersData(s.substr(s.size() / 2)));
+
+  EXPECT_FALSE(HandleControlFrameHeadersData(s));
+  SpdyHeaderBlock expected_block = MakeHeaderBlock({{"a", a_value}});
+  EXPECT_EQ(expected_block, decoded_block());
+}
+
+TEST_P(HpackDecoderAdapterTest, NameTooLong) {
+  // Verify that a name longer than the allowed size generates an error.
+  const size_t kMaxBufferSizeBytes = 50;
+  const SpdyString name = SpdyString(2 * kMaxBufferSizeBytes, 'x');
+  const SpdyString value = "abc";
+
+  decoder_.set_max_decode_buffer_size_bytes(kMaxBufferSizeBytes);
+
+  HpackBlockBuilder hbb;
+  hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+                                false, name, false, value);
+
+  const size_t fragment_size = (3 * kMaxBufferSizeBytes) / 2;
+  const SpdyString fragment = hbb.buffer().substr(0, fragment_size);
+
+  HandleControlFrameHeadersStart();
+  EXPECT_FALSE(HandleControlFrameHeadersData(fragment));
+}
+
+TEST_P(HpackDecoderAdapterTest, HeaderTooLongToBuffer) {
+  // Verify that a header longer than the allowed size generates an error if
+  // it isn't all in one input buffer.
+  const SpdyString name = "some-key";
+  const SpdyString value = "some-value";
+  const size_t kMaxBufferSizeBytes = name.size() + value.size() - 2;
+  decoder_.set_max_decode_buffer_size_bytes(kMaxBufferSizeBytes);
+
+  HpackBlockBuilder hbb;
+  hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+                                false, name, false, value);
+  const size_t fragment_size = hbb.size() - 1;
+  const SpdyString fragment = hbb.buffer().substr(0, fragment_size);
+
+  HandleControlFrameHeadersStart();
+  EXPECT_FALSE(HandleControlFrameHeadersData(fragment));
+}
+
+// Decode with incomplete data in buffer.
+TEST_P(HpackDecoderAdapterTest, DecodeWithIncompleteData) {
+  HandleControlFrameHeadersStart();
+
+  // No need to wait for more data.
+  EXPECT_TRUE(HandleControlFrameHeadersData("\x82\x85\x82"));
+  std::vector<std::pair<SpdyString, SpdyString>> expected_headers = {
+      {":method", "GET"}, {":path", "/index.html"}, {":method", "GET"}};
+
+  SpdyHeaderBlock expected_block1 = MakeHeaderBlock(expected_headers);
+  EXPECT_EQ(expected_block1, decoded_block());
+
+  // Full and partial headers, won't add partial to the headers.
+  EXPECT_TRUE(
+      HandleControlFrameHeadersData("\x40\x03goo"
+                                    "\x03gar\xbe\x40\x04spam"));
+  expected_headers.push_back({"goo", "gar"});
+  expected_headers.push_back({"goo", "gar"});
+
+  SpdyHeaderBlock expected_block2 = MakeHeaderBlock(expected_headers);
+  EXPECT_EQ(expected_block2, decoded_block());
+
+  // Add the needed data.
+  EXPECT_TRUE(HandleControlFrameHeadersData("\x04gggs"));
+
+  size_t size = 0;
+  EXPECT_TRUE(HandleControlFrameHeadersComplete(&size));
+  EXPECT_EQ(24u, size);
+
+  expected_headers.push_back({"spam", "gggs"});
+
+  SpdyHeaderBlock expected_block3 = MakeHeaderBlock(expected_headers);
+  EXPECT_EQ(expected_block3, decoded_block());
+}
+
+TEST_P(HpackDecoderAdapterTest, HandleHeaderRepresentation) {
+  // Make sure the decoder is properly initialized.
+  HandleControlFrameHeadersStart();
+  HandleControlFrameHeadersData("");
+
+  // All cookie crumbs are joined.
+  decoder_peer_.HandleHeaderRepresentation("cookie", " part 1");
+  decoder_peer_.HandleHeaderRepresentation("cookie", "part 2 ");
+  decoder_peer_.HandleHeaderRepresentation("cookie", "part3");
+
+  // Already-delimited headers are passed through.
+  decoder_peer_.HandleHeaderRepresentation("passed-through",
+                                           SpdyString("foo\0baz", 7));
+
+  // Other headers are joined on \0. Case matters.
+  decoder_peer_.HandleHeaderRepresentation("joined", "not joined");
+  decoder_peer_.HandleHeaderRepresentation("joineD", "value 1");
+  decoder_peer_.HandleHeaderRepresentation("joineD", "value 2");
+
+  // Empty headers remain empty.
+  decoder_peer_.HandleHeaderRepresentation("empty", "");
+
+  // Joined empty headers work as expected.
+  decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
+  decoder_peer_.HandleHeaderRepresentation("empty-joined", "foo");
+  decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
+  decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
+
+  // Non-contiguous cookie crumb.
+  decoder_peer_.HandleHeaderRepresentation("cookie", " fin!");
+
+  // Finish and emit all headers.
+  decoder_.HandleControlFrameHeadersComplete(nullptr);
+
+  // Resulting decoded headers are in the same order as the inputs.
+  EXPECT_THAT(
+      decoded_block(),
+      ElementsAre(Pair("cookie", " part 1; part 2 ; part3;  fin!"),
+                  Pair("passed-through", SpdyStringPiece("foo\0baz", 7)),
+                  Pair("joined", "not joined"),
+                  Pair("joineD", SpdyStringPiece("value 1\0value 2", 15)),
+                  Pair("empty", ""),
+                  Pair("empty-joined", SpdyStringPiece("\0foo\0\0", 6))));
+}
+
+// Decoding indexed static table field should work.
+TEST_P(HpackDecoderAdapterTest, IndexedHeaderStatic) {
+  // Reference static table entries #2 and #5.
+  const SpdyHeaderBlock& header_set1 = DecodeBlockExpectingSuccess("\x82\x85");
+  SpdyHeaderBlock expected_header_set1;
+  expected_header_set1[":method"] = "GET";
+  expected_header_set1[":path"] = "/index.html";
+  EXPECT_EQ(expected_header_set1, header_set1);
+
+  // Reference static table entry #2.
+  const SpdyHeaderBlock& header_set2 = DecodeBlockExpectingSuccess("\x82");
+  SpdyHeaderBlock expected_header_set2;
+  expected_header_set2[":method"] = "GET";
+  EXPECT_EQ(expected_header_set2, header_set2);
+}
+
+TEST_P(HpackDecoderAdapterTest, IndexedHeaderDynamic) {
+  // First header block: add an entry to header table.
+  const SpdyHeaderBlock& header_set1 = DecodeBlockExpectingSuccess(
+      "\x40\x03"
+      "foo"
+      "\x03"
+      "bar");
+  SpdyHeaderBlock expected_header_set1;
+  expected_header_set1["foo"] = "bar";
+  EXPECT_EQ(expected_header_set1, header_set1);
+
+  // Second header block: add another entry to header table.
+  const SpdyHeaderBlock& header_set2 = DecodeBlockExpectingSuccess(
+      "\xbe\x40\x04"
+      "spam"
+      "\x04"
+      "eggs");
+  SpdyHeaderBlock expected_header_set2;
+  expected_header_set2["foo"] = "bar";
+  expected_header_set2["spam"] = "eggs";
+  EXPECT_EQ(expected_header_set2, header_set2);
+
+  // Third header block: refer to most recently added entry.
+  const SpdyHeaderBlock& header_set3 = DecodeBlockExpectingSuccess("\xbe");
+  SpdyHeaderBlock expected_header_set3;
+  expected_header_set3["spam"] = "eggs";
+  EXPECT_EQ(expected_header_set3, header_set3);
+}
+
+// Test a too-large indexed header.
+TEST_P(HpackDecoderAdapterTest, InvalidIndexedHeader) {
+  // High-bit set, and a prefix of one more than the number of static entries.
+  EXPECT_FALSE(DecodeHeaderBlock("\xbe"));
+}
+
+TEST_P(HpackDecoderAdapterTest, ContextUpdateMaximumSize) {
+  EXPECT_EQ(kDefaultHeaderTableSizeSetting,
+            decoder_peer_.header_table_size_limit());
+  SpdyString input;
+  {
+    // Maximum-size update with size 126. Succeeds.
+    HpackOutputStream output_stream;
+    output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream.AppendUint32(126);
+
+    output_stream.TakeString(&input);
+    EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece(input)));
+    EXPECT_EQ(126u, decoder_peer_.header_table_size_limit());
+  }
+  {
+    // Maximum-size update with kDefaultHeaderTableSizeSetting. Succeeds.
+    HpackOutputStream output_stream;
+    output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream.AppendUint32(kDefaultHeaderTableSizeSetting);
+
+    output_stream.TakeString(&input);
+    EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece(input)));
+    EXPECT_EQ(kDefaultHeaderTableSizeSetting,
+              decoder_peer_.header_table_size_limit());
+  }
+  {
+    // Maximum-size update with kDefaultHeaderTableSizeSetting + 1. Fails.
+    HpackOutputStream output_stream;
+    output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream.AppendUint32(kDefaultHeaderTableSizeSetting + 1);
+
+    output_stream.TakeString(&input);
+    EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input)));
+    EXPECT_EQ(kDefaultHeaderTableSizeSetting,
+              decoder_peer_.header_table_size_limit());
+  }
+}
+
+// Two HeaderTableSizeUpdates may appear at the beginning of the block
+TEST_P(HpackDecoderAdapterTest, TwoTableSizeUpdates) {
+  SpdyString input;
+  {
+    // Should accept two table size updates, update to second one
+    HpackOutputStream output_stream;
+    output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream.AppendUint32(0);
+    output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream.AppendUint32(122);
+
+    output_stream.TakeString(&input);
+    EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece(input)));
+    EXPECT_EQ(122u, decoder_peer_.header_table_size_limit());
+  }
+}
+
+// Three HeaderTableSizeUpdates should result in an error
+TEST_P(HpackDecoderAdapterTest, ThreeTableSizeUpdatesError) {
+  SpdyString input;
+  {
+    // Should reject three table size updates, update to second one
+    HpackOutputStream output_stream;
+    output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream.AppendUint32(5);
+    output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream.AppendUint32(10);
+    output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream.AppendUint32(15);
+
+    output_stream.TakeString(&input);
+
+    EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input)));
+    EXPECT_EQ(10u, decoder_peer_.header_table_size_limit());
+  }
+}
+
+// HeaderTableSizeUpdates may only appear at the beginning of the block
+// Any other updates should result in an error
+TEST_P(HpackDecoderAdapterTest, TableSizeUpdateSecondError) {
+  SpdyString input;
+  {
+    // Should reject a table size update appearing after a different entry
+    // The table size should remain as the default
+    HpackOutputStream output_stream;
+    output_stream.AppendBytes("\x82\x85");
+    output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream.AppendUint32(123);
+
+    output_stream.TakeString(&input);
+
+    EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input)));
+    EXPECT_EQ(kDefaultHeaderTableSizeSetting,
+              decoder_peer_.header_table_size_limit());
+  }
+}
+
+// HeaderTableSizeUpdates may only appear at the beginning of the block
+// Any other updates should result in an error
+TEST_P(HpackDecoderAdapterTest, TableSizeUpdateFirstThirdError) {
+  SpdyString input;
+  {
+    // Should reject the second table size update
+    // if a different entry appears after the first update
+    // The table size should update to the first but not the second
+    HpackOutputStream output_stream;
+    output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream.AppendUint32(60);
+    output_stream.AppendBytes("\x82\x85");
+    output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream.AppendUint32(125);
+
+    output_stream.TakeString(&input);
+
+    EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input)));
+    EXPECT_EQ(60u, decoder_peer_.header_table_size_limit());
+  }
+}
+
+// Decoding two valid encoded literal headers with no indexing should
+// work.
+TEST_P(HpackDecoderAdapterTest, LiteralHeaderNoIndexing) {
+  // First header with indexed name, second header with string literal
+  // name.
+  const char input[] = "\x04\x0c/sample/path\x00\x06:path2\x0e/sample/path/2";
+  const SpdyHeaderBlock& header_set = DecodeBlockExpectingSuccess(
+      SpdyStringPiece(input, SPDY_ARRAYSIZE(input) - 1));
+
+  SpdyHeaderBlock expected_header_set;
+  expected_header_set[":path"] = "/sample/path";
+  expected_header_set[":path2"] = "/sample/path/2";
+  EXPECT_EQ(expected_header_set, header_set);
+}
+
+// Decoding two valid encoded literal headers with incremental
+// indexing and string literal names should work.
+TEST_P(HpackDecoderAdapterTest, LiteralHeaderIncrementalIndexing) {
+  const char input[] = "\x44\x0c/sample/path\x40\x06:path2\x0e/sample/path/2";
+  const SpdyHeaderBlock& header_set = DecodeBlockExpectingSuccess(
+      SpdyStringPiece(input, SPDY_ARRAYSIZE(input) - 1));
+
+  SpdyHeaderBlock expected_header_set;
+  expected_header_set[":path"] = "/sample/path";
+  expected_header_set[":path2"] = "/sample/path/2";
+  EXPECT_EQ(expected_header_set, header_set);
+}
+
+TEST_P(HpackDecoderAdapterTest, LiteralHeaderWithIndexingInvalidNameIndex) {
+  decoder_.ApplyHeaderTableSizeSetting(0);
+  EXPECT_TRUE(EncodeAndDecodeDynamicTableSizeUpdates(0, 0));
+
+  // Name is the last static index. Works.
+  EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece("\x7d\x03ooo")));
+  // Name is one beyond the last static index. Fails.
+  EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece("\x7e\x03ooo")));
+}
+
+TEST_P(HpackDecoderAdapterTest, LiteralHeaderNoIndexingInvalidNameIndex) {
+  // Name is the last static index. Works.
+  EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece("\x0f\x2e\x03ooo")));
+  // Name is one beyond the last static index. Fails.
+  EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece("\x0f\x2f\x03ooo")));
+}
+
+TEST_P(HpackDecoderAdapterTest, LiteralHeaderNeverIndexedInvalidNameIndex) {
+  // Name is the last static index. Works.
+  EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece("\x1f\x2e\x03ooo")));
+  // Name is one beyond the last static index. Fails.
+  EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece("\x1f\x2f\x03ooo")));
+}
+
+TEST_P(HpackDecoderAdapterTest, TruncatedIndex) {
+  // Indexed Header, varint for index requires multiple bytes,
+  // but only one provided.
+  EXPECT_FALSE(DecodeHeaderBlock("\xff"));
+}
+
+TEST_P(HpackDecoderAdapterTest, TruncatedHuffmanLiteral) {
+  // Literal value, Huffman encoded, but with the last byte missing (i.e.
+  // drop the final ff shown below).
+  //
+  // 41                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 1)
+  //                                         |     :authority
+  // 8c                                      |   Literal value (len = 12)
+  //                                         |     Huffman encoded:
+  // f1e3 c2e5 f23a 6ba0 ab90 f4ff           | .....:k.....
+  //                                         |     Decoded:
+  //                                         | www.example.com
+  //                                         | -> :authority: www.example.com
+
+  SpdyString first = SpdyHexDecode("418cf1e3c2e5f23a6ba0ab90f4ff");
+  EXPECT_TRUE(DecodeHeaderBlock(first));
+  first.pop_back();
+  EXPECT_FALSE(DecodeHeaderBlock(first));
+}
+
+TEST_P(HpackDecoderAdapterTest, HuffmanEOSError) {
+  // Literal value, Huffman encoded, but with an additional ff byte at the end
+  // of the string, i.e. an EOS that is longer than permitted.
+  //
+  // 41                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 1)
+  //                                         |     :authority
+  // 8d                                      |   Literal value (len = 13)
+  //                                         |     Huffman encoded:
+  // f1e3 c2e5 f23a 6ba0 ab90 f4ff           | .....:k.....
+  //                                         |     Decoded:
+  //                                         | www.example.com
+  //                                         | -> :authority: www.example.com
+
+  SpdyString first = SpdyHexDecode("418cf1e3c2e5f23a6ba0ab90f4ff");
+  EXPECT_TRUE(DecodeHeaderBlock(first));
+  first = SpdyHexDecode("418df1e3c2e5f23a6ba0ab90f4ffff");
+  EXPECT_FALSE(DecodeHeaderBlock(first));
+}
+
+// Round-tripping the header set from RFC 7541 C.3.1 should work.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3.1
+TEST_P(HpackDecoderAdapterTest, BasicC31) {
+  HpackEncoder encoder(ObtainHpackHuffmanTable());
+
+  SpdyHeaderBlock expected_header_set;
+  expected_header_set[":method"] = "GET";
+  expected_header_set[":scheme"] = "http";
+  expected_header_set[":path"] = "/";
+  expected_header_set[":authority"] = "www.example.com";
+
+  SpdyString encoded_header_set;
+  EXPECT_TRUE(
+      encoder.EncodeHeaderSet(expected_header_set, &encoded_header_set));
+
+  EXPECT_TRUE(DecodeHeaderBlock(encoded_header_set));
+  EXPECT_EQ(expected_header_set, decoded_block());
+}
+
+// RFC 7541, Section C.4: Request Examples with Huffman Coding
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.4
+TEST_P(HpackDecoderAdapterTest, SectionC4RequestHuffmanExamples) {
+  // TODO(jamessynge): Use http2/hpack/tools/hpack_example.h to parse the
+  // example directly, instead of having it as a comment.
+  //
+  // 82                                      | == Indexed - Add ==
+  //                                         |   idx = 2
+  //                                         | -> :method: GET
+  // 86                                      | == Indexed - Add ==
+  //                                         |   idx = 6
+  //                                         | -> :scheme: http
+  // 84                                      | == Indexed - Add ==
+  //                                         |   idx = 4
+  //                                         | -> :path: /
+  // 41                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 1)
+  //                                         |     :authority
+  // 8c                                      |   Literal value (len = 12)
+  //                                         |     Huffman encoded:
+  // f1e3 c2e5 f23a 6ba0 ab90 f4ff           | .....:k.....
+  //                                         |     Decoded:
+  //                                         | www.example.com
+  //                                         | -> :authority: www.example.com
+  SpdyString first = SpdyHexDecode("828684418cf1e3c2e5f23a6ba0ab90f4ff");
+  const SpdyHeaderBlock& first_header_set = DecodeBlockExpectingSuccess(first);
+
+  EXPECT_THAT(first_header_set,
+              ElementsAre(
+                  // clang-format off
+      Pair(":method", "GET"),
+      Pair(":scheme", "http"),
+      Pair(":path", "/"),
+      Pair(":authority", "www.example.com")));
+  // clang-format on
+
+  expectEntry(62, 57, ":authority", "www.example.com");
+  EXPECT_EQ(57u, decoder_peer_.current_header_table_size());
+
+  // 82                                      | == Indexed - Add ==
+  //                                         |   idx = 2
+  //                                         | -> :method: GET
+  // 86                                      | == Indexed - Add ==
+  //                                         |   idx = 6
+  //                                         | -> :scheme: http
+  // 84                                      | == Indexed - Add ==
+  //                                         |   idx = 4
+  //                                         | -> :path: /
+  // be                                      | == Indexed - Add ==
+  //                                         |   idx = 62
+  //                                         | -> :authority: www.example.com
+  // 58                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 24)
+  //                                         |     cache-control
+  // 86                                      |   Literal value (len = 8)
+  //                                         |     Huffman encoded:
+  // a8eb 1064 9cbf                          | ...d..
+  //                                         |     Decoded:
+  //                                         | no-cache
+  //                                         | -> cache-control: no-cache
+
+  SpdyString second = SpdyHexDecode("828684be5886a8eb10649cbf");
+  const SpdyHeaderBlock& second_header_set =
+      DecodeBlockExpectingSuccess(second);
+
+  EXPECT_THAT(second_header_set,
+              ElementsAre(
+                  // clang-format off
+      Pair(":method", "GET"),
+      Pair(":scheme", "http"),
+      Pair(":path", "/"),
+      Pair(":authority", "www.example.com"),
+      Pair("cache-control", "no-cache")));
+  // clang-format on
+
+  expectEntry(62, 53, "cache-control", "no-cache");
+  expectEntry(63, 57, ":authority", "www.example.com");
+  EXPECT_EQ(110u, decoder_peer_.current_header_table_size());
+
+  // 82                                      | == Indexed - Add ==
+  //                                         |   idx = 2
+  //                                         | -> :method: GET
+  // 87                                      | == Indexed - Add ==
+  //                                         |   idx = 7
+  //                                         | -> :scheme: https
+  // 85                                      | == Indexed - Add ==
+  //                                         |   idx = 5
+  //                                         | -> :path: /index.html
+  // bf                                      | == Indexed - Add ==
+  //                                         |   idx = 63
+  //                                         | -> :authority: www.example.com
+  // 40                                      | == Literal indexed ==
+  // 88                                      |   Literal name (len = 10)
+  //                                         |     Huffman encoded:
+  // 25a8 49e9 5ba9 7d7f                     | %.I.[.}.
+  //                                         |     Decoded:
+  //                                         | custom-key
+  // 89                                      |   Literal value (len = 12)
+  //                                         |     Huffman encoded:
+  // 25a8 49e9 5bb8 e8b4 bf                  | %.I.[....
+  //                                         |     Decoded:
+  //                                         | custom-value
+  //                                         | -> custom-key: custom-value
+  SpdyString third =
+      SpdyHexDecode("828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf");
+  const SpdyHeaderBlock& third_header_set = DecodeBlockExpectingSuccess(third);
+
+  EXPECT_THAT(
+      third_header_set,
+      ElementsAre(
+          // clang-format off
+      Pair(":method", "GET"),
+      Pair(":scheme", "https"),
+      Pair(":path", "/index.html"),
+      Pair(":authority", "www.example.com"),
+      Pair("custom-key", "custom-value")));
+  // clang-format on
+
+  expectEntry(62, 54, "custom-key", "custom-value");
+  expectEntry(63, 53, "cache-control", "no-cache");
+  expectEntry(64, 57, ":authority", "www.example.com");
+  EXPECT_EQ(164u, decoder_peer_.current_header_table_size());
+}
+
+// RFC 7541, Section C.6: Response Examples with Huffman Coding
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.6
+TEST_P(HpackDecoderAdapterTest, SectionC6ResponseHuffmanExamples) {
+  // The example is based on a maximum dynamic table size of 256,
+  // which allows for testing dynamic table evictions.
+  decoder_peer_.set_header_table_size_limit(256);
+
+  // 48                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 8)
+  //                                         |     :status
+  // 82                                      |   Literal value (len = 3)
+  //                                         |     Huffman encoded:
+  // 6402                                    | d.
+  //                                         |     Decoded:
+  //                                         | 302
+  //                                         | -> :status: 302
+  // 58                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 24)
+  //                                         |     cache-control
+  // 85                                      |   Literal value (len = 7)
+  //                                         |     Huffman encoded:
+  // aec3 771a 4b                            | ..w.K
+  //                                         |     Decoded:
+  //                                         | private
+  //                                         | -> cache-control: private
+  // 61                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 33)
+  //                                         |     date
+  // 96                                      |   Literal value (len = 29)
+  //                                         |     Huffman encoded:
+  // d07a be94 1054 d444 a820 0595 040b 8166 | .z...T.D. .....f
+  // e082 a62d 1bff                          | ...-..
+  //                                         |     Decoded:
+  //                                         | Mon, 21 Oct 2013 20:13:21
+  //                                         | GMT
+  //                                         | -> date: Mon, 21 Oct 2013
+  //                                         |   20:13:21 GMT
+  // 6e                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 46)
+  //                                         |     location
+  // 91                                      |   Literal value (len = 23)
+  //                                         |     Huffman encoded:
+  // 9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 | .)...c.........C
+  // d3                                      | .
+  //                                         |     Decoded:
+  //                                         | https://www.example.com
+  //                                         | -> location: https://www.e
+  //                                         |    xample.com
+
+  SpdyString first = SpdyHexDecode(
+      "488264025885aec3771a4b6196d07abe"
+      "941054d444a8200595040b8166e082a6"
+      "2d1bff6e919d29ad171863c78f0b97c8"
+      "e9ae82ae43d3");
+  const SpdyHeaderBlock& first_header_set = DecodeBlockExpectingSuccess(first);
+
+  EXPECT_THAT(first_header_set,
+              ElementsAre(
+                  // clang-format off
+      Pair(":status", "302"),
+      Pair("cache-control", "private"),
+      Pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+      Pair("location", "https://www.example.com")));
+  // clang-format on
+
+  expectEntry(62, 63, "location", "https://www.example.com");
+  expectEntry(63, 65, "date", "Mon, 21 Oct 2013 20:13:21 GMT");
+  expectEntry(64, 52, "cache-control", "private");
+  expectEntry(65, 42, ":status", "302");
+  EXPECT_EQ(222u, decoder_peer_.current_header_table_size());
+
+  // 48                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 8)
+  //                                         |     :status
+  // 83                                      |   Literal value (len = 3)
+  //                                         |     Huffman encoded:
+  // 640e ff                                 | d..
+  //                                         |     Decoded:
+  //                                         | 307
+  //                                         | - evict: :status: 302
+  //                                         | -> :status: 307
+  // c1                                      | == Indexed - Add ==
+  //                                         |   idx = 65
+  //                                         | -> cache-control: private
+  // c0                                      | == Indexed - Add ==
+  //                                         |   idx = 64
+  //                                         | -> date: Mon, 21 Oct 2013
+  //                                         |   20:13:21 GMT
+  // bf                                      | == Indexed - Add ==
+  //                                         |   idx = 63
+  //                                         | -> location:
+  //                                         |   https://www.example.com
+  SpdyString second = SpdyHexDecode("4883640effc1c0bf");
+  const SpdyHeaderBlock& second_header_set =
+      DecodeBlockExpectingSuccess(second);
+
+  EXPECT_THAT(second_header_set,
+              ElementsAre(
+                  // clang-format off
+      Pair(":status", "307"),
+      Pair("cache-control", "private"),
+      Pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+      Pair("location", "https://www.example.com")));
+  // clang-format on
+
+  expectEntry(62, 42, ":status", "307");
+  expectEntry(63, 63, "location", "https://www.example.com");
+  expectEntry(64, 65, "date", "Mon, 21 Oct 2013 20:13:21 GMT");
+  expectEntry(65, 52, "cache-control", "private");
+  EXPECT_EQ(222u, decoder_peer_.current_header_table_size());
+
+  // 88                                      | == Indexed - Add ==
+  //                                         |   idx = 8
+  //                                         | -> :status: 200
+  // c1                                      | == Indexed - Add ==
+  //                                         |   idx = 65
+  //                                         | -> cache-control: private
+  // 61                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 33)
+  //                                         |     date
+  // 96                                      |   Literal value (len = 22)
+  //                                         |     Huffman encoded:
+  // d07a be94 1054 d444 a820 0595 040b 8166 | .z...T.D. .....f
+  // e084 a62d 1bff                          | ...-..
+  //                                         |     Decoded:
+  //                                         | Mon, 21 Oct 2013 20:13:22
+  //                                         | GMT
+  //                                         | - evict: cache-control:
+  //                                         |   private
+  //                                         | -> date: Mon, 21 Oct 2013
+  //                                         |   20:13:22 GMT
+  // c0                                      | == Indexed - Add ==
+  //                                         |   idx = 64
+  //                                         | -> location:
+  //                                         |    https://www.example.com
+  // 5a                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 26)
+  //                                         |     content-encoding
+  // 83                                      |   Literal value (len = 3)
+  //                                         |     Huffman encoded:
+  // 9bd9 ab                                 | ...
+  //                                         |     Decoded:
+  //                                         | gzip
+  //                                         | - evict: date: Mon, 21 Oct
+  //                                         |    2013 20:13:21 GMT
+  //                                         | -> content-encoding: gzip
+  // 77                                      | == Literal indexed ==
+  //                                         |   Indexed name (idx = 55)
+  //                                         |     set-cookie
+  // ad                                      |   Literal value (len = 45)
+  //                                         |     Huffman encoded:
+  // 94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 | .........5...[9`
+  // d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 | ..'..6r..'..)...
+  // 3160 65c0 03ed 4ee5 b106 3d50 07        | 1`e...N...=P.
+  //                                         |     Decoded:
+  //                                         | foo=ASDJKHQKBZXOQWEOPIUAXQ
+  //                                         | WEOIU; max-age=3600; versi
+  //                                         | on=1
+  //                                         | - evict: location:
+  //                                         |   https://www.example.com
+  //                                         | - evict: :status: 307
+  //                                         | -> set-cookie: foo=ASDJKHQ
+  //                                         |   KBZXOQWEOPIUAXQWEOIU;
+  //                                         |   max-age=3600; version=1
+  SpdyString third = SpdyHexDecode(
+      "88c16196d07abe941054d444a8200595"
+      "040b8166e084a62d1bffc05a839bd9ab"
+      "77ad94e7821dd7f2e6c7b335dfdfcd5b"
+      "3960d5af27087f3672c1ab270fb5291f"
+      "9587316065c003ed4ee5b1063d5007");
+  const SpdyHeaderBlock& third_header_set = DecodeBlockExpectingSuccess(third);
+
+  EXPECT_THAT(third_header_set,
+              ElementsAre(
+                  // clang-format off
+      Pair(":status", "200"),
+      Pair("cache-control", "private"),
+      Pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
+      Pair("location", "https://www.example.com"),
+      Pair("content-encoding", "gzip"),
+      Pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
+           " max-age=3600; version=1")));
+  // clang-format on
+
+  expectEntry(62, 98, "set-cookie",
+              "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
+              " max-age=3600; version=1");
+  expectEntry(63, 52, "content-encoding", "gzip");
+  expectEntry(64, 65, "date", "Mon, 21 Oct 2013 20:13:22 GMT");
+  EXPECT_EQ(215u, decoder_peer_.current_header_table_size());
+}
+
+// Regression test: Found that entries with dynamic indexed names and literal
+// values caused "use after free" MSAN failures if the name was evicted as it
+// was being re-used.
+TEST_P(HpackDecoderAdapterTest, ReuseNameOfEvictedEntry) {
+  // Each entry is measured as 32 bytes plus the sum of the lengths of the name
+  // and the value. Set the size big enough for at most one entry, and a fairly
+  // small one at that (31 ASCII characters).
+  decoder_.ApplyHeaderTableSizeSetting(63);
+
+  HpackBlockBuilder hbb;
+  hbb.AppendDynamicTableSizeUpdate(0);
+  hbb.AppendDynamicTableSizeUpdate(63);
+
+  const SpdyStringPiece name("some-name");
+  const SpdyStringPiece value1("some-value");
+  const SpdyStringPiece value2("another-value");
+  const SpdyStringPiece value3("yet-another-value");
+
+  // Add an entry that will become the first in the dynamic table, entry 62.
+  hbb.AppendLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader, false,
+                                name, false, value1);
+
+  // Confirm that entry has been added by re-using it.
+  hbb.AppendIndexedHeader(62);
+
+  // Add another entry referring to the name of the first. This will evict the
+  // first.
+  hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 62,
+                                     false, value2);
+
+  // Confirm that entry has been added by re-using it.
+  hbb.AppendIndexedHeader(62);
+
+  // Add another entry referring to the name of the second. This will evict the
+  // second.
+  hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 62,
+                                     false, value3);
+
+  // Confirm that entry has been added by re-using it.
+  hbb.AppendIndexedHeader(62);
+
+  // Can't have DecodeHeaderBlock do the default check for size of the decoded
+  // data because SpdyHeaderBlock will join multiple headers with the same
+  // name into a single entry, thus we won't see repeated occurrences of the
+  // name, instead seeing separators between values.
+  EXPECT_TRUE(DecodeHeaderBlock(hbb.buffer(), kNoCheckDecodedSize));
+
+  SpdyHeaderBlock expected_header_set;
+  expected_header_set.AppendValueOrAddHeader(name, value1);
+  expected_header_set.AppendValueOrAddHeader(name, value1);
+  expected_header_set.AppendValueOrAddHeader(name, value2);
+  expected_header_set.AppendValueOrAddHeader(name, value2);
+  expected_header_set.AppendValueOrAddHeader(name, value3);
+  expected_header_set.AppendValueOrAddHeader(name, value3);
+
+  // SpdyHeaderBlock stores these 6 strings as '\0' separated values.
+  // Make sure that is what happened.
+  SpdyString joined_values = expected_header_set[name].as_string();
+  EXPECT_EQ(joined_values.size(),
+            2 * value1.size() + 2 * value2.size() + 2 * value3.size() + 5);
+
+  EXPECT_EQ(expected_header_set, decoded_block());
+
+  if (start_choice_ == START_WITH_HANDLER) {
+    EXPECT_EQ(handler_.header_bytes_parsed(),
+              6 * name.size() + 2 * value1.size() + 2 * value2.size() +
+                  2 * value3.size());
+  }
+}
+
+// Regression test for https://crbug.com/747395.
+TEST_P(HpackDecoderAdapterTest, Cookies) {
+  SpdyHeaderBlock expected_header_set;
+  expected_header_set["cookie"] = "foo; bar";
+
+  EXPECT_TRUE(DecodeHeaderBlock(SpdyHexDecode("608294e76003626172")));
+  EXPECT_EQ(expected_header_set, decoded_block());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_encoder.cc b/spdy/core/hpack/hpack_encoder.cc
new file mode 100644
index 0000000..a9981b9
--- /dev/null
+++ b/spdy/core/hpack/hpack_encoder.cc
@@ -0,0 +1,365 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+
+namespace spdy {
+
+class HpackEncoder::RepresentationIterator {
+ public:
+  // |pseudo_headers| and |regular_headers| must outlive the iterator.
+  RepresentationIterator(const Representations& pseudo_headers,
+                         const Representations& regular_headers)
+      : pseudo_begin_(pseudo_headers.begin()),
+        pseudo_end_(pseudo_headers.end()),
+        regular_begin_(regular_headers.begin()),
+        regular_end_(regular_headers.end()) {}
+
+  // |headers| must outlive the iterator.
+  explicit RepresentationIterator(const Representations& headers)
+      : pseudo_begin_(headers.begin()),
+        pseudo_end_(headers.end()),
+        regular_begin_(headers.end()),
+        regular_end_(headers.end()) {}
+
+  bool HasNext() {
+    return pseudo_begin_ != pseudo_end_ || regular_begin_ != regular_end_;
+  }
+
+  const Representation Next() {
+    if (pseudo_begin_ != pseudo_end_) {
+      return *pseudo_begin_++;
+    } else {
+      return *regular_begin_++;
+    }
+  }
+
+ private:
+  Representations::const_iterator pseudo_begin_;
+  Representations::const_iterator pseudo_end_;
+  Representations::const_iterator regular_begin_;
+  Representations::const_iterator regular_end_;
+};
+
+namespace {
+
+// The default header listener.
+void NoOpListener(SpdyStringPiece /*name*/, SpdyStringPiece /*value*/) {}
+
+// The default HPACK indexing policy.
+bool DefaultPolicy(SpdyStringPiece name, SpdyStringPiece /* value */) {
+  if (name.empty()) {
+    return false;
+  }
+  // :authority is always present and rarely changes, and has moderate
+  // length, therefore it makes a lot of sense to index (insert in the
+  // dynamic table).
+  if (name[0] == kPseudoHeaderPrefix) {
+    return name == ":authority";
+  }
+  return true;
+}
+
+}  // namespace
+
+HpackEncoder::HpackEncoder(const HpackHuffmanTable& table)
+    : output_stream_(),
+      huffman_table_(table),
+      min_table_size_setting_received_(std::numeric_limits<size_t>::max()),
+      listener_(NoOpListener),
+      should_index_(DefaultPolicy),
+      enable_compression_(true),
+      should_emit_table_size_(false) {}
+
+HpackEncoder::~HpackEncoder() = default;
+
+bool HpackEncoder::EncodeHeaderSet(const SpdyHeaderBlock& header_set,
+                                   SpdyString* output) {
+  // Separate header set into pseudo-headers and regular headers.
+  Representations pseudo_headers;
+  Representations regular_headers;
+  bool found_cookie = false;
+  for (const auto& header : header_set) {
+    if (!found_cookie && header.first == "cookie") {
+      // Note that there can only be one "cookie" header, because header_set is
+      // a map.
+      found_cookie = true;
+      CookieToCrumbs(header, &regular_headers);
+    } else if (!header.first.empty() &&
+               header.first[0] == kPseudoHeaderPrefix) {
+      DecomposeRepresentation(header, &pseudo_headers);
+    } else {
+      DecomposeRepresentation(header, &regular_headers);
+    }
+  }
+
+  {
+    RepresentationIterator iter(pseudo_headers, regular_headers);
+    EncodeRepresentations(&iter, output);
+  }
+  return true;
+}
+
+void HpackEncoder::ApplyHeaderTableSizeSetting(size_t size_setting) {
+  if (size_setting == header_table_.settings_size_bound()) {
+    return;
+  }
+  if (size_setting < header_table_.settings_size_bound()) {
+    min_table_size_setting_received_ =
+        std::min(size_setting, min_table_size_setting_received_);
+  }
+  header_table_.SetSettingsHeaderTableSize(size_setting);
+  should_emit_table_size_ = true;
+}
+
+size_t HpackEncoder::EstimateMemoryUsage() const {
+  // |huffman_table_| is a singleton. It's accounted for in spdy_session_pool.cc
+  return SpdyEstimateMemoryUsage(header_table_) +
+         SpdyEstimateMemoryUsage(output_stream_);
+}
+
+void HpackEncoder::EncodeRepresentations(RepresentationIterator* iter,
+                                         SpdyString* output) {
+  MaybeEmitTableSize();
+  while (iter->HasNext()) {
+    const auto header = iter->Next();
+    listener_(header.first, header.second);
+    if (enable_compression_) {
+      const HpackEntry* entry =
+          header_table_.GetByNameAndValue(header.first, header.second);
+      if (entry != nullptr) {
+        EmitIndex(entry);
+      } else if (should_index_(header.first, header.second)) {
+        EmitIndexedLiteral(header);
+      } else {
+        EmitNonIndexedLiteral(header);
+      }
+    } else {
+      EmitNonIndexedLiteral(header);
+    }
+  }
+
+  output_stream_.TakeString(output);
+}
+
+void HpackEncoder::EmitIndex(const HpackEntry* entry) {
+  DVLOG(2) << "Emitting index " << header_table_.IndexOf(entry);
+  output_stream_.AppendPrefix(kIndexedOpcode);
+  output_stream_.AppendUint32(header_table_.IndexOf(entry));
+}
+
+void HpackEncoder::EmitIndexedLiteral(const Representation& representation) {
+  DVLOG(2) << "Emitting indexed literal: (" << representation.first << ", "
+           << representation.second << ")";
+  output_stream_.AppendPrefix(kLiteralIncrementalIndexOpcode);
+  EmitLiteral(representation);
+  header_table_.TryAddEntry(representation.first, representation.second);
+}
+
+void HpackEncoder::EmitNonIndexedLiteral(const Representation& representation) {
+  DVLOG(2) << "Emitting nonindexed literal: (" << representation.first << ", "
+           << representation.second << ")";
+  output_stream_.AppendPrefix(kLiteralNoIndexOpcode);
+  output_stream_.AppendUint32(0);
+  EmitString(representation.first);
+  EmitString(representation.second);
+}
+
+void HpackEncoder::EmitLiteral(const Representation& representation) {
+  const HpackEntry* name_entry = header_table_.GetByName(representation.first);
+  if (name_entry != nullptr) {
+    output_stream_.AppendUint32(header_table_.IndexOf(name_entry));
+  } else {
+    output_stream_.AppendUint32(0);
+    EmitString(representation.first);
+  }
+  EmitString(representation.second);
+}
+
+void HpackEncoder::EmitString(SpdyStringPiece str) {
+  size_t encoded_size =
+      enable_compression_ ? huffman_table_.EncodedSize(str) : str.size();
+  if (encoded_size < str.size()) {
+    DVLOG(2) << "Emitted Huffman-encoded string of length " << encoded_size;
+    output_stream_.AppendPrefix(kStringLiteralHuffmanEncoded);
+    output_stream_.AppendUint32(encoded_size);
+    huffman_table_.EncodeString(str, &output_stream_);
+  } else {
+    DVLOG(2) << "Emitted literal string of length " << str.size();
+    output_stream_.AppendPrefix(kStringLiteralIdentityEncoded);
+    output_stream_.AppendUint32(str.size());
+    output_stream_.AppendBytes(str);
+  }
+}
+
+void HpackEncoder::MaybeEmitTableSize() {
+  if (!should_emit_table_size_) {
+    return;
+  }
+  const size_t current_size = CurrentHeaderTableSizeSetting();
+  DVLOG(1) << "MaybeEmitTableSize current_size=" << current_size;
+  DVLOG(1) << "MaybeEmitTableSize min_table_size_setting_received_="
+           << min_table_size_setting_received_;
+  if (min_table_size_setting_received_ < current_size) {
+    output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    output_stream_.AppendUint32(min_table_size_setting_received_);
+  }
+  output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+  output_stream_.AppendUint32(current_size);
+  min_table_size_setting_received_ = std::numeric_limits<size_t>::max();
+  should_emit_table_size_ = false;
+}
+
+// static
+void HpackEncoder::CookieToCrumbs(const Representation& cookie,
+                                  Representations* out) {
+  // See Section 8.1.2.5. "Compressing the Cookie Header Field" in the HTTP/2
+  // specification at https://tools.ietf.org/html/draft-ietf-httpbis-http2-14.
+  // Cookie values are split into individually-encoded HPACK representations.
+  SpdyStringPiece cookie_value = cookie.second;
+  // Consume leading and trailing whitespace if present.
+  SpdyStringPiece::size_type first = cookie_value.find_first_not_of(" \t");
+  SpdyStringPiece::size_type last = cookie_value.find_last_not_of(" \t");
+  if (first == SpdyStringPiece::npos) {
+    cookie_value = SpdyStringPiece();
+  } else {
+    cookie_value = cookie_value.substr(first, (last - first) + 1);
+  }
+  for (size_t pos = 0;;) {
+    size_t end = cookie_value.find(";", pos);
+
+    if (end == SpdyStringPiece::npos) {
+      out->push_back(std::make_pair(cookie.first, cookie_value.substr(pos)));
+      break;
+    }
+    out->push_back(
+        std::make_pair(cookie.first, cookie_value.substr(pos, end - pos)));
+
+    // Consume next space if present.
+    pos = end + 1;
+    if (pos != cookie_value.size() && cookie_value[pos] == ' ') {
+      pos++;
+    }
+  }
+}
+
+// static
+void HpackEncoder::DecomposeRepresentation(const Representation& header_field,
+                                           Representations* out) {
+  size_t pos = 0;
+  size_t end = 0;
+  while (end != SpdyStringPiece::npos) {
+    end = header_field.second.find('\0', pos);
+    out->push_back(std::make_pair(
+        header_field.first,
+        header_field.second.substr(
+            pos, end == SpdyStringPiece::npos ? end : end - pos)));
+    pos = end + 1;
+  }
+}
+
+// static
+void HpackEncoder::GatherRepresentation(const Representation& header_field,
+                                        Representations* out) {
+  out->push_back(std::make_pair(header_field.first, header_field.second));
+}
+
+// Iteratively encodes a SpdyHeaderBlock.
+class HpackEncoder::Encoderator : public ProgressiveEncoder {
+ public:
+  Encoderator(const SpdyHeaderBlock& header_set, HpackEncoder* encoder);
+
+  // Encoderator is neither copyable nor movable.
+  Encoderator(const Encoderator&) = delete;
+  Encoderator& operator=(const Encoderator&) = delete;
+
+  // Returns true iff more remains to encode.
+  bool HasNext() const override { return has_next_; }
+
+  // Encodes up to max_encoded_bytes of the current header block into the
+  // given output string.
+  void Next(size_t max_encoded_bytes, SpdyString* output) override;
+
+ private:
+  HpackEncoder* encoder_;
+  std::unique_ptr<RepresentationIterator> header_it_;
+  Representations pseudo_headers_;
+  Representations regular_headers_;
+  bool has_next_;
+};
+
+HpackEncoder::Encoderator::Encoderator(const SpdyHeaderBlock& header_set,
+                                       HpackEncoder* encoder)
+    : encoder_(encoder), has_next_(true) {
+  // Separate header set into pseudo-headers and regular headers.
+  const bool use_compression = encoder_->enable_compression_;
+  bool found_cookie = false;
+  for (const auto& header : header_set) {
+    if (!found_cookie && header.first == "cookie") {
+      // Note that there can only be one "cookie" header, because header_set
+      // is a map.
+      found_cookie = true;
+      CookieToCrumbs(header, &regular_headers_);
+    } else if (!header.first.empty() &&
+               header.first[0] == kPseudoHeaderPrefix) {
+      use_compression ? DecomposeRepresentation(header, &pseudo_headers_)
+                      : GatherRepresentation(header, &pseudo_headers_);
+    } else {
+      use_compression ? DecomposeRepresentation(header, &regular_headers_)
+                      : GatherRepresentation(header, &regular_headers_);
+    }
+  }
+  header_it_ =
+      SpdyMakeUnique<RepresentationIterator>(pseudo_headers_, regular_headers_);
+
+  encoder_->MaybeEmitTableSize();
+}
+
+void HpackEncoder::Encoderator::Next(size_t max_encoded_bytes,
+                                     SpdyString* output) {
+  SPDY_BUG_IF(!has_next_)
+      << "Encoderator::Next called with nothing left to encode.";
+  const bool use_compression = encoder_->enable_compression_;
+
+  // Encode up to max_encoded_bytes of headers.
+  while (header_it_->HasNext() &&
+         encoder_->output_stream_.size() <= max_encoded_bytes) {
+    const Representation header = header_it_->Next();
+    encoder_->listener_(header.first, header.second);
+    if (use_compression) {
+      const HpackEntry* entry = encoder_->header_table_.GetByNameAndValue(
+          header.first, header.second);
+      if (entry != nullptr) {
+        encoder_->EmitIndex(entry);
+      } else if (encoder_->should_index_(header.first, header.second)) {
+        encoder_->EmitIndexedLiteral(header);
+      } else {
+        encoder_->EmitNonIndexedLiteral(header);
+      }
+    } else {
+      encoder_->EmitNonIndexedLiteral(header);
+    }
+  }
+
+  has_next_ = encoder_->output_stream_.size() > max_encoded_bytes;
+  encoder_->output_stream_.BoundedTakeString(max_encoded_bytes, output);
+}
+
+std::unique_ptr<HpackEncoder::ProgressiveEncoder> HpackEncoder::EncodeHeaderSet(
+    const SpdyHeaderBlock& header_set) {
+  return SpdyMakeUnique<Encoderator>(header_set, this);
+}
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_encoder.h b/spdy/core/hpack/hpack_encoder.h
new file mode 100644
index 0000000..486d536
--- /dev/null
+++ b/spdy/core/hpack/hpack_encoder.h
@@ -0,0 +1,152 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_ENCODER_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_ENCODER_H_
+
+#include <stddef.h>
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+// An HpackEncoder encodes header sets as outlined in
+// http://tools.ietf.org/html/rfc7541.
+
+namespace spdy {
+
+class HpackHuffmanTable;
+
+namespace test {
+class HpackEncoderPeer;
+}  // namespace test
+
+class SPDY_EXPORT_PRIVATE HpackEncoder {
+ public:
+  using Representation = std::pair<SpdyStringPiece, SpdyStringPiece>;
+  using Representations = std::vector<Representation>;
+
+  // Callers may provide a HeaderListener to be informed of header name-value
+  // pairs processed by this encoder.
+  using HeaderListener = std::function<void(SpdyStringPiece, SpdyStringPiece)>;
+
+  // An indexing policy should return true if the provided header name-value
+  // pair should be inserted into the HPACK dynamic table.
+  using IndexingPolicy = std::function<bool(SpdyStringPiece, SpdyStringPiece)>;
+
+  // |table| is an initialized HPACK Huffman table, having an
+  // externally-managed lifetime which spans beyond HpackEncoder.
+  explicit HpackEncoder(const HpackHuffmanTable& table);
+  HpackEncoder(const HpackEncoder&) = delete;
+  HpackEncoder& operator=(const HpackEncoder&) = delete;
+  ~HpackEncoder();
+
+  // Encodes the given header set into the given string. Returns
+  // whether or not the encoding was successful.
+  bool EncodeHeaderSet(const SpdyHeaderBlock& header_set, SpdyString* output);
+
+  class SPDY_EXPORT_PRIVATE ProgressiveEncoder {
+   public:
+    virtual ~ProgressiveEncoder() {}
+
+    // Returns true iff more remains to encode.
+    virtual bool HasNext() const = 0;
+
+    // Encodes up to max_encoded_bytes of the current header block into the
+    // given output string.
+    virtual void Next(size_t max_encoded_bytes, SpdyString* output) = 0;
+  };
+
+  // Returns a ProgressiveEncoder which must be outlived by both the given
+  // SpdyHeaderBlock and this object.
+  std::unique_ptr<ProgressiveEncoder> EncodeHeaderSet(
+      const SpdyHeaderBlock& header_set);
+
+  // Called upon a change to SETTINGS_HEADER_TABLE_SIZE. Specifically, this
+  // is to be called after receiving (and sending an acknowledgement for) a
+  // SETTINGS_HEADER_TABLE_SIZE update from the remote decoding endpoint.
+  void ApplyHeaderTableSizeSetting(size_t size_setting);
+
+  size_t CurrentHeaderTableSizeSetting() const {
+    return header_table_.settings_size_bound();
+  }
+
+  // This HpackEncoder will use |policy| to determine whether to insert header
+  // name-value pairs into the dynamic table.
+  void SetIndexingPolicy(IndexingPolicy policy) { should_index_ = policy; }
+
+  // |listener| will be invoked for each header name-value pair processed by
+  // this encoder.
+  void SetHeaderListener(HeaderListener listener) { listener_ = listener; }
+
+  void SetHeaderTableDebugVisitor(
+      std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) {
+    header_table_.set_debug_visitor(std::move(visitor));
+  }
+
+  void DisableCompression() { enable_compression_ = false; }
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+ private:
+  friend class test::HpackEncoderPeer;
+
+  class RepresentationIterator;
+  class Encoderator;
+
+  // Encodes a sequence of header name-value pairs as a single header block.
+  void EncodeRepresentations(RepresentationIterator* iter, SpdyString* output);
+
+  // Emits a static/dynamic indexed representation (Section 7.1).
+  void EmitIndex(const HpackEntry* entry);
+
+  // Emits a literal representation (Section 7.2).
+  void EmitIndexedLiteral(const Representation& representation);
+  void EmitNonIndexedLiteral(const Representation& representation);
+  void EmitLiteral(const Representation& representation);
+
+  // Emits a Huffman or identity string (whichever is smaller).
+  void EmitString(SpdyStringPiece str);
+
+  // Emits the current dynamic table size if the table size was recently
+  // updated and we have not yet emitted it (Section 6.3).
+  void MaybeEmitTableSize();
+
+  // Crumbles a cookie header into ";" delimited crumbs.
+  static void CookieToCrumbs(const Representation& cookie,
+                             Representations* crumbs_out);
+
+  // Crumbles other header field values at \0 delimiters.
+  static void DecomposeRepresentation(const Representation& header_field,
+                                      Representations* out);
+
+  // Gathers headers without crumbling. Used when compression is not enabled.
+  static void GatherRepresentation(const Representation& header_field,
+                                   Representations* out);
+
+  HpackHeaderTable header_table_;
+  HpackOutputStream output_stream_;
+
+  const HpackHuffmanTable& huffman_table_;
+  size_t min_table_size_setting_received_;
+  HeaderListener listener_;
+  IndexingPolicy should_index_;
+  bool enable_compression_;
+  bool should_emit_table_size_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_HPACK_HPACK_ENCODER_H_
diff --git a/spdy/core/hpack/hpack_encoder_test.cc b/spdy/core/hpack/hpack_encoder_test.cc
new file mode 100644
index 0000000..8ead1c2
--- /dev/null
+++ b/spdy/core/hpack/hpack_encoder_test.cc
@@ -0,0 +1,586 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+
+#include <cstdint>
+#include <map>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h"
+
+namespace spdy {
+
+namespace test {
+
+class HpackHeaderTablePeer {
+ public:
+  explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {}
+
+  HpackHeaderTable::EntryTable* dynamic_entries() {
+    return &table_->dynamic_entries_;
+  }
+
+ private:
+  HpackHeaderTable* table_;
+};
+
+class HpackEncoderPeer {
+ public:
+  typedef HpackEncoder::Representation Representation;
+  typedef HpackEncoder::Representations Representations;
+
+  explicit HpackEncoderPeer(HpackEncoder* encoder) : encoder_(encoder) {}
+
+  bool compression_enabled() const { return encoder_->enable_compression_; }
+  HpackHeaderTable* table() { return &encoder_->header_table_; }
+  HpackHeaderTablePeer table_peer() { return HpackHeaderTablePeer(table()); }
+  const HpackHuffmanTable& huffman_table() const {
+    return encoder_->huffman_table_;
+  }
+  void EmitString(SpdyStringPiece str) { encoder_->EmitString(str); }
+  void TakeString(SpdyString* out) { encoder_->output_stream_.TakeString(out); }
+  static void CookieToCrumbs(SpdyStringPiece cookie,
+                             std::vector<SpdyStringPiece>* out) {
+    Representations tmp;
+    HpackEncoder::CookieToCrumbs(std::make_pair("", cookie), &tmp);
+
+    out->clear();
+    for (size_t i = 0; i != tmp.size(); ++i) {
+      out->push_back(tmp[i].second);
+    }
+  }
+  static void DecomposeRepresentation(SpdyStringPiece value,
+                                      std::vector<SpdyStringPiece>* out) {
+    Representations tmp;
+    HpackEncoder::DecomposeRepresentation(std::make_pair("foobar", value),
+                                          &tmp);
+
+    out->clear();
+    for (size_t i = 0; i != tmp.size(); ++i) {
+      out->push_back(tmp[i].second);
+    }
+  }
+
+  // TODO(dahollings): Remove or clean up these methods when deprecating
+  // non-incremental encoding path.
+  static bool EncodeHeaderSet(HpackEncoder* encoder,
+                              const SpdyHeaderBlock& header_set,
+                              SpdyString* output,
+                              bool use_incremental) {
+    if (use_incremental) {
+      return EncodeIncremental(encoder, header_set, output);
+    } else {
+      return encoder->EncodeHeaderSet(header_set, output);
+    }
+  }
+
+  static bool EncodeIncremental(HpackEncoder* encoder,
+                                const SpdyHeaderBlock& header_set,
+                                SpdyString* output) {
+    std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator =
+        encoder->EncodeHeaderSet(header_set);
+    SpdyString output_buffer;
+    http2::test::Http2Random random;
+    encoderator->Next(random.UniformInRange(0, 16), &output_buffer);
+    while (encoderator->HasNext()) {
+      SpdyString second_buffer;
+      encoderator->Next(random.UniformInRange(0, 16), &second_buffer);
+      output_buffer.append(second_buffer);
+    }
+    *output = std::move(output_buffer);
+    return true;
+  }
+
+ private:
+  HpackEncoder* encoder_;
+};
+
+}  // namespace test
+
+namespace {
+
+using testing::ElementsAre;
+using testing::Pair;
+
+class HpackEncoderTest : public ::testing::TestWithParam<bool> {
+ protected:
+  typedef test::HpackEncoderPeer::Representations Representations;
+
+  HpackEncoderTest()
+      : encoder_(ObtainHpackHuffmanTable()),
+        peer_(&encoder_),
+        static_(peer_.table()->GetByIndex(1)),
+        headers_storage_(1024 /* block size */) {}
+
+  void SetUp() override {
+    use_incremental_ = GetParam();
+
+    // Populate dynamic entries into the table fixture. For simplicity each
+    // entry has name.size() + value.size() == 10.
+    key_1_ = peer_.table()->TryAddEntry("key1", "value1");
+    key_2_ = peer_.table()->TryAddEntry("key2", "value2");
+    cookie_a_ = peer_.table()->TryAddEntry("cookie", "a=bb");
+    cookie_c_ = peer_.table()->TryAddEntry("cookie", "c=dd");
+
+    // No further insertions may occur without evictions.
+    peer_.table()->SetMaxSize(peer_.table()->size());
+  }
+
+  void SaveHeaders(SpdyStringPiece name, SpdyStringPiece value) {
+    SpdyStringPiece n(headers_storage_.Memdup(name.data(), name.size()),
+                      name.size());
+    SpdyStringPiece v(headers_storage_.Memdup(value.data(), value.size()),
+                      value.size());
+    headers_observed_.push_back(std::make_pair(n, v));
+  }
+
+  void ExpectIndex(size_t index) {
+    expected_.AppendPrefix(kIndexedOpcode);
+    expected_.AppendUint32(index);
+  }
+  void ExpectIndexedLiteral(const HpackEntry* key_entry,
+                            SpdyStringPiece value) {
+    expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
+    expected_.AppendUint32(IndexOf(key_entry));
+    ExpectString(&expected_, value);
+  }
+  void ExpectIndexedLiteral(SpdyStringPiece name, SpdyStringPiece value) {
+    expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
+    expected_.AppendUint32(0);
+    ExpectString(&expected_, name);
+    ExpectString(&expected_, value);
+  }
+  void ExpectNonIndexedLiteral(SpdyStringPiece name, SpdyStringPiece value) {
+    expected_.AppendPrefix(kLiteralNoIndexOpcode);
+    expected_.AppendUint32(0);
+    ExpectString(&expected_, name);
+    ExpectString(&expected_, value);
+  }
+  void ExpectString(HpackOutputStream* stream, SpdyStringPiece str) {
+    const HpackHuffmanTable& huffman_table = peer_.huffman_table();
+    size_t encoded_size = peer_.compression_enabled()
+                              ? huffman_table.EncodedSize(str)
+                              : str.size();
+    if (encoded_size < str.size()) {
+      expected_.AppendPrefix(kStringLiteralHuffmanEncoded);
+      expected_.AppendUint32(encoded_size);
+      huffman_table.EncodeString(str, stream);
+    } else {
+      expected_.AppendPrefix(kStringLiteralIdentityEncoded);
+      expected_.AppendUint32(str.size());
+      expected_.AppendBytes(str);
+    }
+  }
+  void ExpectHeaderTableSizeUpdate(uint32_t size) {
+    expected_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+    expected_.AppendUint32(size);
+  }
+  void CompareWithExpectedEncoding(const SpdyHeaderBlock& header_set) {
+    SpdyString expected_out, actual_out;
+    expected_.TakeString(&expected_out);
+    EXPECT_TRUE(test::HpackEncoderPeer::EncodeHeaderSet(
+        &encoder_, header_set, &actual_out, use_incremental_));
+    EXPECT_EQ(expected_out, actual_out);
+  }
+  size_t IndexOf(const HpackEntry* entry) {
+    return peer_.table()->IndexOf(entry);
+  }
+
+  HpackEncoder encoder_;
+  test::HpackEncoderPeer peer_;
+
+  const HpackEntry* static_;
+  const HpackEntry* key_1_;
+  const HpackEntry* key_2_;
+  const HpackEntry* cookie_a_;
+  const HpackEntry* cookie_c_;
+
+  SpdyUnsafeArena headers_storage_;
+  std::vector<std::pair<SpdyStringPiece, SpdyStringPiece>> headers_observed_;
+
+  HpackOutputStream expected_;
+  bool use_incremental_;
+};
+
+INSTANTIATE_TEST_CASE_P(HpackEncoderTests, HpackEncoderTest, ::testing::Bool());
+
+TEST_P(HpackEncoderTest, SingleDynamicIndex) {
+  encoder_.SetHeaderListener(
+      [this](SpdyStringPiece name, SpdyStringPiece value) {
+        this->SaveHeaders(name, value);
+      });
+
+  ExpectIndex(IndexOf(key_2_));
+
+  SpdyHeaderBlock headers;
+  headers[key_2_->name()] = key_2_->value();
+  CompareWithExpectedEncoding(headers);
+  EXPECT_THAT(headers_observed_,
+              ElementsAre(Pair(key_2_->name(), key_2_->value())));
+}
+
+TEST_P(HpackEncoderTest, SingleStaticIndex) {
+  ExpectIndex(IndexOf(static_));
+
+  SpdyHeaderBlock headers;
+  headers[static_->name()] = static_->value();
+  CompareWithExpectedEncoding(headers);
+}
+
+TEST_P(HpackEncoderTest, SingleStaticIndexTooLarge) {
+  peer_.table()->SetMaxSize(1);  // Also evicts all fixtures.
+  ExpectIndex(IndexOf(static_));
+
+  SpdyHeaderBlock headers;
+  headers[static_->name()] = static_->value();
+  CompareWithExpectedEncoding(headers);
+
+  EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
+}
+
+TEST_P(HpackEncoderTest, SingleLiteralWithIndexName) {
+  ExpectIndexedLiteral(key_2_, "value3");
+
+  SpdyHeaderBlock headers;
+  headers[key_2_->name()] = "value3";
+  CompareWithExpectedEncoding(headers);
+
+  // A new entry was inserted and added to the reference set.
+  HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+  EXPECT_EQ(new_entry->name(), key_2_->name());
+  EXPECT_EQ(new_entry->value(), "value3");
+}
+
+TEST_P(HpackEncoderTest, SingleLiteralWithLiteralName) {
+  ExpectIndexedLiteral("key3", "value3");
+
+  SpdyHeaderBlock headers;
+  headers["key3"] = "value3";
+  CompareWithExpectedEncoding(headers);
+
+  HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+  EXPECT_EQ(new_entry->name(), "key3");
+  EXPECT_EQ(new_entry->value(), "value3");
+}
+
+TEST_P(HpackEncoderTest, SingleLiteralTooLarge) {
+  peer_.table()->SetMaxSize(1);  // Also evicts all fixtures.
+
+  ExpectIndexedLiteral("key3", "value3");
+
+  // A header overflowing the header table is still emitted.
+  // The header table is empty.
+  SpdyHeaderBlock headers;
+  headers["key3"] = "value3";
+  CompareWithExpectedEncoding(headers);
+
+  EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
+}
+
+TEST_P(HpackEncoderTest, EmitThanEvict) {
+  // |key_1_| is toggled and placed into the reference set,
+  // and then immediately evicted by "key3".
+  ExpectIndex(IndexOf(key_1_));
+  ExpectIndexedLiteral("key3", "value3");
+
+  SpdyHeaderBlock headers;
+  headers[key_1_->name()] = key_1_->value();
+  headers["key3"] = "value3";
+  CompareWithExpectedEncoding(headers);
+}
+
+TEST_P(HpackEncoderTest, CookieHeaderIsCrumbled) {
+  ExpectIndex(IndexOf(cookie_a_));
+  ExpectIndex(IndexOf(cookie_c_));
+  ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");
+
+  SpdyHeaderBlock headers;
+  headers["cookie"] = "a=bb; c=dd; e=ff";
+  CompareWithExpectedEncoding(headers);
+}
+
+TEST_P(HpackEncoderTest, StringsDynamicallySelectHuffmanCoding) {
+  // Compactable string. Uses Huffman coding.
+  peer_.EmitString("feedbeef");
+  expected_.AppendPrefix(kStringLiteralHuffmanEncoded);
+  expected_.AppendUint32(6);
+  expected_.AppendBytes("\x94\xA5\x92\x32\x96_");
+
+  // Non-compactable. Uses identity coding.
+  peer_.EmitString("@@@@@@");
+  expected_.AppendPrefix(kStringLiteralIdentityEncoded);
+  expected_.AppendUint32(6);
+  expected_.AppendBytes("@@@@@@");
+
+  SpdyString expected_out, actual_out;
+  expected_.TakeString(&expected_out);
+  peer_.TakeString(&actual_out);
+  EXPECT_EQ(expected_out, actual_out);
+}
+
+TEST_P(HpackEncoderTest, EncodingWithoutCompression) {
+  encoder_.SetHeaderListener(
+      [this](SpdyStringPiece name, SpdyStringPiece value) {
+        this->SaveHeaders(name, value);
+      });
+  encoder_.DisableCompression();
+
+  ExpectNonIndexedLiteral(":path", "/index.html");
+  ExpectNonIndexedLiteral("cookie", "foo=bar");
+  ExpectNonIndexedLiteral("cookie", "baz=bing");
+  ExpectNonIndexedLiteral("hello", "goodbye");
+
+  SpdyHeaderBlock headers;
+  headers[":path"] = "/index.html";
+  headers["cookie"] = "foo=bar; baz=bing";
+  headers["hello"] = "goodbye";
+
+  CompareWithExpectedEncoding(headers);
+
+  EXPECT_THAT(
+      headers_observed_,
+      ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"),
+                  Pair("cookie", "baz=bing"), Pair("hello", "goodbye")));
+}
+
+TEST_P(HpackEncoderTest, MultipleEncodingPasses) {
+  encoder_.SetHeaderListener(
+      [this](SpdyStringPiece name, SpdyStringPiece value) {
+        this->SaveHeaders(name, value);
+      });
+
+  // Pass 1.
+  {
+    SpdyHeaderBlock headers;
+    headers["key1"] = "value1";
+    headers["cookie"] = "a=bb";
+
+    ExpectIndex(IndexOf(key_1_));
+    ExpectIndex(IndexOf(cookie_a_));
+    CompareWithExpectedEncoding(headers);
+  }
+  // Header table is:
+  // 65: key1: value1
+  // 64: key2: value2
+  // 63: cookie: a=bb
+  // 62: cookie: c=dd
+  // Pass 2.
+  {
+    SpdyHeaderBlock headers;
+    headers["key2"] = "value2";
+    headers["cookie"] = "c=dd; e=ff";
+
+    // "key2: value2"
+    ExpectIndex(64);
+    // "cookie: c=dd"
+    ExpectIndex(62);
+    // This cookie evicts |key1| from the dynamic table.
+    ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");
+
+    CompareWithExpectedEncoding(headers);
+  }
+  // Header table is:
+  // 65: key2: value2
+  // 64: cookie: a=bb
+  // 63: cookie: c=dd
+  // 62: cookie: e=ff
+  // Pass 3.
+  {
+    SpdyHeaderBlock headers;
+    headers["key2"] = "value2";
+    headers["cookie"] = "a=bb; b=cc; c=dd";
+
+    // "key2: value2"
+    ExpectIndex(65);
+    // "cookie: a=bb"
+    ExpectIndex(64);
+    // This cookie evicts |key2| from the dynamic table.
+    ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "b=cc");
+    // "cookie: c=dd"
+    ExpectIndex(64);
+
+    CompareWithExpectedEncoding(headers);
+  }
+
+  // clang-format off
+  EXPECT_THAT(headers_observed_,
+              ElementsAre(Pair("key1", "value1"),
+                          Pair("cookie", "a=bb"),
+                          Pair("key2", "value2"),
+                          Pair("cookie", "c=dd"),
+                          Pair("cookie", "e=ff"),
+                          Pair("key2", "value2"),
+                          Pair("cookie", "a=bb"),
+                          Pair("cookie", "b=cc"),
+                          Pair("cookie", "c=dd")));
+  // clang-format on
+}
+
+TEST_P(HpackEncoderTest, PseudoHeadersFirst) {
+  SpdyHeaderBlock headers;
+  // A pseudo-header that should not be indexed.
+  headers[":path"] = "/spam/eggs.html";
+  // A pseudo-header to be indexed.
+  headers[":authority"] = "www.example.com";
+  // A regular header which precedes ":" alphabetically, should still be encoded
+  // after pseudo-headers.
+  headers["-foo"] = "bar";
+  headers["foo"] = "bar";
+  headers["cookie"] = "c=dd";
+
+  // Headers are indexed in the order in which they were added.
+  // This entry pushes "cookie: a=bb" back to 63.
+  ExpectNonIndexedLiteral(":path", "/spam/eggs.html");
+  ExpectIndexedLiteral(peer_.table()->GetByName(":authority"),
+                       "www.example.com");
+  ExpectIndexedLiteral("-foo", "bar");
+  ExpectIndexedLiteral("foo", "bar");
+  ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "c=dd");
+  CompareWithExpectedEncoding(headers);
+}
+
+TEST_P(HpackEncoderTest, CookieToCrumbs) {
+  test::HpackEncoderPeer peer(nullptr);
+  std::vector<SpdyStringPiece> out;
+
+  // Leading and trailing whitespace is consumed. A space after ';' is consumed.
+  // All other spaces remain. ';' at beginning and end of string produce empty
+  // crumbs.
+  // See section 8.1.3.4 "Compressing the Cookie Header Field" in the HTTP/2
+  // specification at http://tools.ietf.org/html/draft-ietf-httpbis-http2-11
+  peer.CookieToCrumbs(" foo=1;bar=2 ; bar=3;  bing=4; ", &out);
+  EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", " bing=4", ""));
+
+  peer.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out);
+  EXPECT_THAT(out, ElementsAre("", "", "foo = bar ", "", "", "baz =bing"));
+
+  peer.CookieToCrumbs("baz=bing; foo=bar; baz=bing", &out);
+  EXPECT_THAT(out, ElementsAre("baz=bing", "foo=bar", "baz=bing"));
+
+  peer.CookieToCrumbs("baz=bing", &out);
+  EXPECT_THAT(out, ElementsAre("baz=bing"));
+
+  peer.CookieToCrumbs("", &out);
+  EXPECT_THAT(out, ElementsAre(""));
+
+  peer.CookieToCrumbs("foo;bar; baz;baz;bing;", &out);
+  EXPECT_THAT(out, ElementsAre("foo", "bar", "baz", "baz", "bing", ""));
+
+  peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3;\t  ", &out);
+  EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", ""));
+
+  peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3 \t  ", &out);
+  EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3"));
+}
+
+TEST_P(HpackEncoderTest, DecomposeRepresentation) {
+  test::HpackEncoderPeer peer(nullptr);
+  std::vector<SpdyStringPiece> out;
+
+  peer.DecomposeRepresentation("", &out);
+  EXPECT_THAT(out, ElementsAre(""));
+
+  peer.DecomposeRepresentation("foobar", &out);
+  EXPECT_THAT(out, ElementsAre("foobar"));
+
+  peer.DecomposeRepresentation(SpdyStringPiece("foo\0bar", 7), &out);
+  EXPECT_THAT(out, ElementsAre("foo", "bar"));
+
+  peer.DecomposeRepresentation(SpdyStringPiece("\0foo\0bar", 8), &out);
+  EXPECT_THAT(out, ElementsAre("", "foo", "bar"));
+
+  peer.DecomposeRepresentation(SpdyStringPiece("foo\0bar\0", 8), &out);
+  EXPECT_THAT(out, ElementsAre("foo", "bar", ""));
+
+  peer.DecomposeRepresentation(SpdyStringPiece("\0foo\0bar\0", 9), &out);
+  EXPECT_THAT(out, ElementsAre("", "foo", "bar", ""));
+}
+
+// Test that encoded headers do not have \0-delimited multiple values, as this
+// became disallowed in HTTP/2 draft-14.
+TEST_P(HpackEncoderTest, CrumbleNullByteDelimitedValue) {
+  SpdyHeaderBlock headers;
+  // A header field to be crumbled: "spam: foo\0bar".
+  headers["spam"] = SpdyString("foo\0bar", 7);
+
+  ExpectIndexedLiteral("spam", "foo");
+  expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
+  expected_.AppendUint32(62);
+  expected_.AppendPrefix(kStringLiteralIdentityEncoded);
+  expected_.AppendUint32(3);
+  expected_.AppendBytes("bar");
+  CompareWithExpectedEncoding(headers);
+}
+
+TEST_P(HpackEncoderTest, HeaderTableSizeUpdate) {
+  encoder_.ApplyHeaderTableSizeSetting(1024);
+  ExpectHeaderTableSizeUpdate(1024);
+  ExpectIndexedLiteral("key3", "value3");
+
+  SpdyHeaderBlock headers;
+  headers["key3"] = "value3";
+  CompareWithExpectedEncoding(headers);
+
+  HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+  EXPECT_EQ(new_entry->name(), "key3");
+  EXPECT_EQ(new_entry->value(), "value3");
+}
+
+TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithMin) {
+  const size_t starting_size = peer_.table()->settings_size_bound();
+  encoder_.ApplyHeaderTableSizeSetting(starting_size - 2);
+  encoder_.ApplyHeaderTableSizeSetting(starting_size - 1);
+  // We must encode the low watermark, so the peer knows to evict entries
+  // if necessary.
+  ExpectHeaderTableSizeUpdate(starting_size - 2);
+  ExpectHeaderTableSizeUpdate(starting_size - 1);
+  ExpectIndexedLiteral("key3", "value3");
+
+  SpdyHeaderBlock headers;
+  headers["key3"] = "value3";
+  CompareWithExpectedEncoding(headers);
+
+  HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+  EXPECT_EQ(new_entry->name(), "key3");
+  EXPECT_EQ(new_entry->value(), "value3");
+}
+
+TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithExistingSize) {
+  encoder_.ApplyHeaderTableSizeSetting(peer_.table()->settings_size_bound());
+  // No encoded size update.
+  ExpectIndexedLiteral("key3", "value3");
+
+  SpdyHeaderBlock headers;
+  headers["key3"] = "value3";
+  CompareWithExpectedEncoding(headers);
+
+  HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+  EXPECT_EQ(new_entry->name(), "key3");
+  EXPECT_EQ(new_entry->value(), "value3");
+}
+
+TEST_P(HpackEncoderTest, HeaderTableSizeUpdatesWithGreaterSize) {
+  const size_t starting_size = peer_.table()->settings_size_bound();
+  encoder_.ApplyHeaderTableSizeSetting(starting_size + 1);
+  encoder_.ApplyHeaderTableSizeSetting(starting_size + 2);
+  // Only a single size update to the final size.
+  ExpectHeaderTableSizeUpdate(starting_size + 2);
+  ExpectIndexedLiteral("key3", "value3");
+
+  SpdyHeaderBlock headers;
+  headers["key3"] = "value3";
+  CompareWithExpectedEncoding(headers);
+
+  HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+  EXPECT_EQ(new_entry->name(), "key3");
+  EXPECT_EQ(new_entry->value(), "value3");
+}
+
+}  // namespace
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_entry.cc b/spdy/core/hpack/hpack_entry.cc
new file mode 100644
index 0000000..90d53e6
--- /dev/null
+++ b/spdy/core/hpack/hpack_entry.cc
@@ -0,0 +1,89 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+const size_t HpackEntry::kSizeOverhead = 32;
+
+HpackEntry::HpackEntry(SpdyStringPiece name,
+                       SpdyStringPiece value,
+                       bool is_static,
+                       size_t insertion_index)
+    : name_(name.data(), name.size()),
+      value_(value.data(), value.size()),
+      name_ref_(name_),
+      value_ref_(value_),
+      insertion_index_(insertion_index),
+      type_(is_static ? STATIC : DYNAMIC),
+      time_added_(0) {}
+
+HpackEntry::HpackEntry(SpdyStringPiece name, SpdyStringPiece value)
+    : name_ref_(name),
+      value_ref_(value),
+      insertion_index_(0),
+      type_(LOOKUP),
+      time_added_(0) {}
+
+HpackEntry::HpackEntry() : insertion_index_(0), type_(LOOKUP), time_added_(0) {}
+
+HpackEntry::HpackEntry(const HpackEntry& other)
+    : insertion_index_(other.insertion_index_),
+      type_(other.type_),
+      time_added_(0) {
+  if (type_ == LOOKUP) {
+    name_ref_ = other.name_ref_;
+    value_ref_ = other.value_ref_;
+  } else {
+    name_ = other.name_;
+    value_ = other.value_;
+    name_ref_ = SpdyStringPiece(name_.data(), name_.size());
+    value_ref_ = SpdyStringPiece(value_.data(), value_.size());
+  }
+}
+
+HpackEntry& HpackEntry::operator=(const HpackEntry& other) {
+  insertion_index_ = other.insertion_index_;
+  type_ = other.type_;
+  if (type_ == LOOKUP) {
+    name_.clear();
+    value_.clear();
+    name_ref_ = other.name_ref_;
+    value_ref_ = other.value_ref_;
+    return *this;
+  }
+  name_ = other.name_;
+  value_ = other.value_;
+  name_ref_ = SpdyStringPiece(name_.data(), name_.size());
+  value_ref_ = SpdyStringPiece(value_.data(), value_.size());
+  return *this;
+}
+
+HpackEntry::~HpackEntry() = default;
+
+// static
+size_t HpackEntry::Size(SpdyStringPiece name, SpdyStringPiece value) {
+  return name.size() + value.size() + kSizeOverhead;
+}
+size_t HpackEntry::Size() const {
+  return Size(name(), value());
+}
+
+SpdyString HpackEntry::GetDebugString() const {
+  return SpdyStrCat(
+      "{ name: \"", name_ref_, "\", value: \"", value_ref_,
+      "\", index: ", insertion_index_, " ",
+      (IsStatic() ? " static" : (IsLookup() ? " lookup" : " dynamic")), " }");
+}
+
+size_t HpackEntry::EstimateMemoryUsage() const {
+  return SpdyEstimateMemoryUsage(name_) + SpdyEstimateMemoryUsage(value_);
+}
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_entry.h b/spdy/core/hpack/hpack_entry.h
new file mode 100644
index 0000000..28dee47
--- /dev/null
+++ b/spdy/core/hpack/hpack_entry.h
@@ -0,0 +1,110 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_ENTRY_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_ENTRY_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+// All section references below are to
+// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08
+
+namespace spdy {
+
+// A structure for an entry in the static table (3.3.1)
+// and the header table (3.3.2).
+class SPDY_EXPORT_PRIVATE HpackEntry {
+ public:
+  // The constant amount added to name().size() and value().size() to
+  // get the size of an HpackEntry as defined in 5.1.
+  static const size_t kSizeOverhead;
+
+  // Creates an entry. Preconditions:
+  // - |is_static| captures whether this entry is a member of the static
+  //   or dynamic header table.
+  // - |insertion_index| is this entry's index in the total set of entries ever
+  //   inserted into the header table (including static entries).
+  //
+  // The combination of |is_static| and |insertion_index| allows an
+  // HpackEntryTable to determine the index of an HpackEntry in O(1) time.
+  // Copies |name| and |value|.
+  HpackEntry(SpdyStringPiece name,
+             SpdyStringPiece value,
+             bool is_static,
+             size_t insertion_index);
+
+  // Create a 'lookup' entry (only) suitable for querying a HpackEntrySet. The
+  // instance InsertionIndex() always returns 0 and IsLookup() returns true.
+  // The memory backing |name| and |value| must outlive this object.
+  HpackEntry(SpdyStringPiece name, SpdyStringPiece value);
+
+  HpackEntry(const HpackEntry& other);
+  HpackEntry& operator=(const HpackEntry& other);
+
+  // Creates an entry with empty name and value. Only defined so that
+  // entries can be stored in STL containers.
+  HpackEntry();
+
+  ~HpackEntry();
+
+  SpdyStringPiece name() const { return name_ref_; }
+  SpdyStringPiece value() const { return value_ref_; }
+
+  // Returns whether this entry is a member of the static (as opposed to
+  // dynamic) table.
+  bool IsStatic() const { return type_ == STATIC; }
+
+  // Returns whether this entry is a lookup-only entry.
+  bool IsLookup() const { return type_ == LOOKUP; }
+
+  // Used to compute the entry's index in the header table.
+  size_t InsertionIndex() const { return insertion_index_; }
+
+  // Returns the size of an entry as defined in 5.1.
+  static size_t Size(SpdyStringPiece name, SpdyStringPiece value);
+  size_t Size() const;
+
+  SpdyString GetDebugString() const;
+
+  int64_t time_added() const { return time_added_; }
+  void set_time_added(int64_t now) { time_added_ = now; }
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+ private:
+  enum EntryType {
+    LOOKUP,
+    DYNAMIC,
+    STATIC,
+  };
+
+  // These members are not used for LOOKUP entries.
+  SpdyString name_;
+  SpdyString value_;
+
+  // These members are always valid. For DYNAMIC and STATIC entries, they
+  // always point to |name_| and |value_|.
+  SpdyStringPiece name_ref_;
+  SpdyStringPiece value_ref_;
+
+  // The entry's index in the total set of entries ever inserted into the header
+  // table.
+  size_t insertion_index_;
+
+  EntryType type_;
+
+  // For HpackHeaderTable::DebugVisitorInterface
+  int64_t time_added_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_HPACK_HPACK_ENTRY_H_
diff --git a/spdy/core/hpack/hpack_entry_test.cc b/spdy/core/hpack/hpack_entry_test.cc
new file mode 100644
index 0000000..507c851
--- /dev/null
+++ b/spdy/core/hpack/hpack_entry_test.cc
@@ -0,0 +1,122 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace spdy {
+
+namespace {
+
+class HpackEntryTest : public ::testing::Test {
+ protected:
+  HpackEntryTest()
+      : name_("header-name"),
+        value_("header value"),
+        total_insertions_(0),
+        table_size_(0) {}
+
+  // These builders maintain the same external table invariants that a "real"
+  // table (ie HpackHeaderTable) would.
+  HpackEntry StaticEntry() {
+    return HpackEntry(name_, value_, true, total_insertions_++);
+  }
+  HpackEntry DynamicEntry() {
+    ++table_size_;
+    size_t index = total_insertions_++;
+    return HpackEntry(name_, value_, false, index);
+  }
+  void DropEntry() { --table_size_; }
+
+  size_t IndexOf(const HpackEntry& entry) const {
+    if (entry.IsStatic()) {
+      return 1 + entry.InsertionIndex() + table_size_;
+    } else {
+      return total_insertions_ - entry.InsertionIndex();
+    }
+  }
+
+  size_t Size() {
+    return name_.size() + value_.size() + HpackEntry::kSizeOverhead;
+  }
+
+  SpdyString name_, value_;
+
+ private:
+  // Referenced by HpackEntry instances.
+  size_t total_insertions_;
+  size_t table_size_;
+};
+
+TEST_F(HpackEntryTest, StaticConstructor) {
+  HpackEntry entry(StaticEntry());
+
+  EXPECT_EQ(name_, entry.name());
+  EXPECT_EQ(value_, entry.value());
+  EXPECT_TRUE(entry.IsStatic());
+  EXPECT_EQ(1u, IndexOf(entry));
+  EXPECT_EQ(Size(), entry.Size());
+}
+
+TEST_F(HpackEntryTest, DynamicConstructor) {
+  HpackEntry entry(DynamicEntry());
+
+  EXPECT_EQ(name_, entry.name());
+  EXPECT_EQ(value_, entry.value());
+  EXPECT_FALSE(entry.IsStatic());
+  EXPECT_EQ(1u, IndexOf(entry));
+  EXPECT_EQ(Size(), entry.Size());
+}
+
+TEST_F(HpackEntryTest, LookupConstructor) {
+  HpackEntry entry(name_, value_);
+
+  EXPECT_EQ(name_, entry.name());
+  EXPECT_EQ(value_, entry.value());
+  EXPECT_FALSE(entry.IsStatic());
+  EXPECT_EQ(0u, IndexOf(entry));
+  EXPECT_EQ(Size(), entry.Size());
+}
+
+TEST_F(HpackEntryTest, DefaultConstructor) {
+  HpackEntry entry;
+
+  EXPECT_TRUE(entry.name().empty());
+  EXPECT_TRUE(entry.value().empty());
+  EXPECT_EQ(HpackEntry::kSizeOverhead, entry.Size());
+}
+
+TEST_F(HpackEntryTest, IndexUpdate) {
+  HpackEntry static1(StaticEntry());
+  HpackEntry static2(StaticEntry());
+
+  EXPECT_EQ(1u, IndexOf(static1));
+  EXPECT_EQ(2u, IndexOf(static2));
+
+  HpackEntry dynamic1(DynamicEntry());
+  HpackEntry dynamic2(DynamicEntry());
+
+  EXPECT_EQ(1u, IndexOf(dynamic2));
+  EXPECT_EQ(2u, IndexOf(dynamic1));
+  EXPECT_EQ(3u, IndexOf(static1));
+  EXPECT_EQ(4u, IndexOf(static2));
+
+  DropEntry();  // Drops |dynamic1|.
+
+  EXPECT_EQ(1u, IndexOf(dynamic2));
+  EXPECT_EQ(2u, IndexOf(static1));
+  EXPECT_EQ(3u, IndexOf(static2));
+
+  HpackEntry dynamic3(DynamicEntry());
+
+  EXPECT_EQ(1u, IndexOf(dynamic3));
+  EXPECT_EQ(2u, IndexOf(dynamic2));
+  EXPECT_EQ(3u, IndexOf(static1));
+  EXPECT_EQ(4u, IndexOf(static2));
+}
+
+}  // namespace
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_header_table.cc b/spdy/core/hpack/hpack_header_table.cc
new file mode 100644
index 0000000..dbe565f
--- /dev/null
+++ b/spdy/core/hpack/hpack_header_table.cc
@@ -0,0 +1,274 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+
+namespace spdy {
+
+size_t HpackHeaderTable::EntryHasher::operator()(
+    const HpackEntry* entry) const {
+  return SpdyHashStringPair(entry->name(), entry->value());
+}
+
+bool HpackHeaderTable::EntriesEq::operator()(const HpackEntry* lhs,
+                                             const HpackEntry* rhs) const {
+  if (lhs == nullptr) {
+    return rhs == nullptr;
+  }
+  if (rhs == nullptr) {
+    return false;
+  }
+  return lhs->name() == rhs->name() && lhs->value() == rhs->value();
+}
+
+HpackHeaderTable::HpackHeaderTable()
+    : static_entries_(ObtainHpackStaticTable().GetStaticEntries()),
+      static_index_(ObtainHpackStaticTable().GetStaticIndex()),
+      static_name_index_(ObtainHpackStaticTable().GetStaticNameIndex()),
+      settings_size_bound_(kDefaultHeaderTableSizeSetting),
+      size_(0),
+      max_size_(kDefaultHeaderTableSizeSetting),
+      total_insertions_(static_entries_.size()) {}
+
+HpackHeaderTable::~HpackHeaderTable() = default;
+
+const HpackEntry* HpackHeaderTable::GetByIndex(size_t index) {
+  if (index == 0) {
+    return nullptr;
+  }
+  index -= 1;
+  if (index < static_entries_.size()) {
+    return &static_entries_[index];
+  }
+  index -= static_entries_.size();
+  if (index < dynamic_entries_.size()) {
+    const HpackEntry* result = &dynamic_entries_[index];
+    if (debug_visitor_ != nullptr) {
+      debug_visitor_->OnUseEntry(*result);
+    }
+    return result;
+  }
+  return nullptr;
+}
+
+const HpackEntry* HpackHeaderTable::GetByName(SpdyStringPiece name) {
+  {
+    auto it = static_name_index_.find(name);
+    if (it != static_name_index_.end()) {
+      return it->second;
+    }
+  }
+  {
+    NameToEntryMap::const_iterator it = dynamic_name_index_.find(name);
+    if (it != dynamic_name_index_.end()) {
+      const HpackEntry* result = it->second;
+      if (debug_visitor_ != nullptr) {
+        debug_visitor_->OnUseEntry(*result);
+      }
+      return result;
+    }
+  }
+  return nullptr;
+}
+
+const HpackEntry* HpackHeaderTable::GetByNameAndValue(SpdyStringPiece name,
+                                                      SpdyStringPiece value) {
+  HpackEntry query(name, value);
+  {
+    auto it = static_index_.find(&query);
+    if (it != static_index_.end()) {
+      return *it;
+    }
+  }
+  {
+    auto it = dynamic_index_.find(&query);
+    if (it != dynamic_index_.end()) {
+      const HpackEntry* result = *it;
+      if (debug_visitor_ != nullptr) {
+        debug_visitor_->OnUseEntry(*result);
+      }
+      return result;
+    }
+  }
+  return nullptr;
+}
+
+size_t HpackHeaderTable::IndexOf(const HpackEntry* entry) const {
+  if (entry->IsLookup()) {
+    return 0;
+  } else if (entry->IsStatic()) {
+    return 1 + entry->InsertionIndex();
+  } else {
+    return total_insertions_ - entry->InsertionIndex() + static_entries_.size();
+  }
+}
+
+void HpackHeaderTable::SetMaxSize(size_t max_size) {
+  CHECK_LE(max_size, settings_size_bound_);
+
+  max_size_ = max_size;
+  if (size_ > max_size_) {
+    Evict(EvictionCountToReclaim(size_ - max_size_));
+    CHECK_LE(size_, max_size_);
+  }
+}
+
+void HpackHeaderTable::SetSettingsHeaderTableSize(size_t settings_size) {
+  settings_size_bound_ = settings_size;
+  SetMaxSize(settings_size_bound_);
+}
+
+void HpackHeaderTable::EvictionSet(SpdyStringPiece name,
+                                   SpdyStringPiece value,
+                                   EntryTable::iterator* begin_out,
+                                   EntryTable::iterator* end_out) {
+  size_t eviction_count = EvictionCountForEntry(name, value);
+  *begin_out = dynamic_entries_.end() - eviction_count;
+  *end_out = dynamic_entries_.end();
+}
+
+size_t HpackHeaderTable::EvictionCountForEntry(SpdyStringPiece name,
+                                               SpdyStringPiece value) const {
+  size_t available_size = max_size_ - size_;
+  size_t entry_size = HpackEntry::Size(name, value);
+
+  if (entry_size <= available_size) {
+    // No evictions are required.
+    return 0;
+  }
+  return EvictionCountToReclaim(entry_size - available_size);
+}
+
+size_t HpackHeaderTable::EvictionCountToReclaim(size_t reclaim_size) const {
+  size_t count = 0;
+  for (auto it = dynamic_entries_.rbegin();
+       it != dynamic_entries_.rend() && reclaim_size != 0; ++it, ++count) {
+    reclaim_size -= std::min(reclaim_size, it->Size());
+  }
+  return count;
+}
+
+void HpackHeaderTable::Evict(size_t count) {
+  for (size_t i = 0; i != count; ++i) {
+    CHECK(!dynamic_entries_.empty());
+    HpackEntry* entry = &dynamic_entries_.back();
+
+    size_ -= entry->Size();
+    auto it = dynamic_index_.find(entry);
+    DCHECK(it != dynamic_index_.end());
+    // Only remove an entry from the index if its insertion index matches;
+    // otherwise, the index refers to another entry with the same name and
+    // value.
+    if ((*it)->InsertionIndex() == entry->InsertionIndex()) {
+      dynamic_index_.erase(it);
+    }
+    auto name_it = dynamic_name_index_.find(entry->name());
+    DCHECK(name_it != dynamic_name_index_.end());
+    // Only remove an entry from the literal index if its insertion index
+    /// matches; otherwise, the index refers to another entry with the same
+    // name.
+    if (name_it->second->InsertionIndex() == entry->InsertionIndex()) {
+      dynamic_name_index_.erase(name_it);
+    }
+    dynamic_entries_.pop_back();
+  }
+}
+
+const HpackEntry* HpackHeaderTable::TryAddEntry(SpdyStringPiece name,
+                                                SpdyStringPiece value) {
+  Evict(EvictionCountForEntry(name, value));
+
+  size_t entry_size = HpackEntry::Size(name, value);
+  if (entry_size > (max_size_ - size_)) {
+    // Entire table has been emptied, but there's still insufficient room.
+    DCHECK(dynamic_entries_.empty());
+    DCHECK_EQ(0u, size_);
+    return nullptr;
+  }
+  dynamic_entries_.push_front(HpackEntry(name, value,
+                                         false,  // is_static
+                                         total_insertions_));
+  HpackEntry* new_entry = &dynamic_entries_.front();
+  auto index_result = dynamic_index_.insert(new_entry);
+  if (!index_result.second) {
+    // An entry with the same name and value already exists in the dynamic
+    // index. We should replace it with the newly added entry.
+    DVLOG(1) << "Found existing entry: "
+             << (*index_result.first)->GetDebugString()
+             << " replacing with: " << new_entry->GetDebugString();
+    DCHECK_GT(new_entry->InsertionIndex(),
+              (*index_result.first)->InsertionIndex());
+    dynamic_index_.erase(index_result.first);
+    CHECK(dynamic_index_.insert(new_entry).second);
+  }
+
+  auto name_result =
+      dynamic_name_index_.insert(std::make_pair(new_entry->name(), new_entry));
+  if (!name_result.second) {
+    // An entry with the same name already exists in the dynamic index. We
+    // should replace it with the newly added entry.
+    DVLOG(1) << "Found existing entry: "
+             << name_result.first->second->GetDebugString()
+             << " replacing with: " << new_entry->GetDebugString();
+    DCHECK_GT(new_entry->InsertionIndex(),
+              name_result.first->second->InsertionIndex());
+    dynamic_name_index_.erase(name_result.first);
+    auto insert_result = dynamic_name_index_.insert(
+        std::make_pair(new_entry->name(), new_entry));
+    CHECK(insert_result.second);
+  }
+
+  size_ += entry_size;
+  ++total_insertions_;
+  if (debug_visitor_ != nullptr) {
+    // Call |debug_visitor_->OnNewEntry()| to get the current time.
+    HpackEntry& entry = dynamic_entries_.front();
+    entry.set_time_added(debug_visitor_->OnNewEntry(entry));
+    DVLOG(2) << "HpackHeaderTable::OnNewEntry: name=" << entry.name()
+             << ",  value=" << entry.value()
+             << ",  insert_index=" << entry.InsertionIndex()
+             << ",  time_added=" << entry.time_added();
+  }
+
+  return &dynamic_entries_.front();
+}
+
+void HpackHeaderTable::DebugLogTableState() const {
+  DVLOG(2) << "Dynamic table:";
+  for (auto it = dynamic_entries_.begin(); it != dynamic_entries_.end(); ++it) {
+    DVLOG(2) << "  " << it->GetDebugString();
+  }
+  DVLOG(2) << "Full Static Index:";
+  for (const auto* entry : static_index_) {
+    DVLOG(2) << "  " << entry->GetDebugString();
+  }
+  DVLOG(2) << "Full Static Name Index:";
+  for (const auto it : static_name_index_) {
+    DVLOG(2) << "  " << it.first << ": " << it.second->GetDebugString();
+  }
+  DVLOG(2) << "Full Dynamic Index:";
+  for (const auto* entry : dynamic_index_) {
+    DVLOG(2) << "  " << entry->GetDebugString();
+  }
+  DVLOG(2) << "Full Dynamic Name Index:";
+  for (const auto it : dynamic_name_index_) {
+    DVLOG(2) << "  " << it.first << ": " << it.second->GetDebugString();
+  }
+}
+
+size_t HpackHeaderTable::EstimateMemoryUsage() const {
+  return SpdyEstimateMemoryUsage(dynamic_entries_) +
+         SpdyEstimateMemoryUsage(dynamic_index_) +
+         SpdyEstimateMemoryUsage(dynamic_name_index_);
+}
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_header_table.h b/spdy/core/hpack/hpack_header_table.h
new file mode 100644
index 0000000..e85edb7
--- /dev/null
+++ b/spdy/core/hpack/hpack_header_table.h
@@ -0,0 +1,180 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_HEADER_TABLE_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_HEADER_TABLE_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <deque>
+#include <memory>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+// All section references below are to http://tools.ietf.org/html/rfc7541.
+
+namespace spdy {
+
+namespace test {
+class HpackHeaderTablePeer;
+}  // namespace test
+
+// A data structure for the static table (2.3.1) and the dynamic table (2.3.2).
+class SPDY_EXPORT_PRIVATE HpackHeaderTable {
+ public:
+  friend class test::HpackHeaderTablePeer;
+
+  // Debug visitor my be used to extract debug/internal information
+  // about the HpackHeaderTable as it operates.
+  //
+  // Most HpackHeaderTable implementations do not need to bother with
+  // this interface at all.
+  class DebugVisitorInterface {
+   public:
+    virtual ~DebugVisitorInterface() {}
+
+    // |OnNewEntry()| and |OnUseEntry()| can be used together to
+    // gather data about the distribution of time intervals between
+    // creation and reference of entries in the dynamic table.  The
+    // data is desired to sanity check a proposed extension to HPACK
+    // for QUIC that would eliminate inter-stream head of line
+    // blocking (due to standard HPACK).  The visitor should return
+    // the current time from |OnNewEntry()|, which will be passed
+    // to |OnUseEntry()| each time that particular entry is used to
+    // emit an indexed representation.
+    virtual int64_t OnNewEntry(const HpackEntry& entry) = 0;
+    virtual void OnUseEntry(const HpackEntry& entry) = 0;
+  };
+
+  // HpackHeaderTable takes advantage of the deque property that references
+  // remain valid, so long as insertions & deletions are at the head & tail.
+  // This precludes the use of base::circular_deque.
+  //
+  // If this changes (we want to change to circular_deque or we start to drop
+  // entries from the middle of the table), this should to be a std::list, in
+  // which case |*_index_| can be trivially extended to map to list iterators.
+  using EntryTable = std::deque<HpackEntry>;
+
+  struct SPDY_EXPORT_PRIVATE EntryHasher {
+    size_t operator()(const HpackEntry* entry) const;
+  };
+  struct SPDY_EXPORT_PRIVATE EntriesEq {
+    bool operator()(const HpackEntry* lhs, const HpackEntry* rhs) const;
+  };
+  using UnorderedEntrySet = SpdyHashSet<HpackEntry*, EntryHasher, EntriesEq>;
+  using NameToEntryMap =
+      SpdyHashMap<SpdyStringPiece, const HpackEntry*, SpdyStringPieceHash>;
+
+  HpackHeaderTable();
+  HpackHeaderTable(const HpackHeaderTable&) = delete;
+  HpackHeaderTable& operator=(const HpackHeaderTable&) = delete;
+
+  ~HpackHeaderTable();
+
+  // Last-acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
+  size_t settings_size_bound() const { return settings_size_bound_; }
+
+  // Current and maximum estimated byte size of the table, as described in
+  // 4.1. Notably, this is /not/ the number of entries in the table.
+  size_t size() const { return size_; }
+  size_t max_size() const { return max_size_; }
+
+  // Returns the entry matching the index, or NULL.
+  const HpackEntry* GetByIndex(size_t index);
+
+  // Returns the lowest-value entry having |name|, or NULL.
+  const HpackEntry* GetByName(SpdyStringPiece name);
+
+  // Returns the lowest-index matching entry, or NULL.
+  const HpackEntry* GetByNameAndValue(SpdyStringPiece name,
+                                      SpdyStringPiece value);
+
+  // Returns the index of an entry within this header table.
+  size_t IndexOf(const HpackEntry* entry) const;
+
+  // Sets the maximum size of the header table, evicting entries if
+  // necessary as described in 5.2.
+  void SetMaxSize(size_t max_size);
+
+  // Sets the SETTINGS_HEADER_TABLE_SIZE bound of the table. Will call
+  // SetMaxSize() as needed to preserve max_size() <= settings_size_bound().
+  void SetSettingsHeaderTableSize(size_t settings_size);
+
+  // Determine the set of entries which would be evicted by the insertion
+  // of |name| & |value| into the table, as per section 4.4. No eviction
+  // actually occurs. The set is returned via range [begin_out, end_out).
+  void EvictionSet(SpdyStringPiece name,
+                   SpdyStringPiece value,
+                   EntryTable::iterator* begin_out,
+                   EntryTable::iterator* end_out);
+
+  // Adds an entry for the representation, evicting entries as needed. |name|
+  // and |value| must not be owned by an entry which could be evicted. The
+  // added HpackEntry is returned, or NULL is returned if all entries were
+  // evicted and the empty table is of insufficent size for the representation.
+  const HpackEntry* TryAddEntry(SpdyStringPiece name, SpdyStringPiece value);
+
+  void DebugLogTableState() const SPDY_UNUSED;
+
+  void set_debug_visitor(std::unique_ptr<DebugVisitorInterface> visitor) {
+    debug_visitor_ = std::move(visitor);
+  }
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+ private:
+  // Returns number of evictions required to enter |name| & |value|.
+  size_t EvictionCountForEntry(SpdyStringPiece name,
+                               SpdyStringPiece value) const;
+
+  // Returns number of evictions required to reclaim |reclaim_size| table size.
+  size_t EvictionCountToReclaim(size_t reclaim_size) const;
+
+  // Evicts |count| oldest entries from the table.
+  void Evict(size_t count);
+
+  // |static_entries_|, |static_index_|, and |static_name_index_| are owned by
+  // HpackStaticTable singleton.
+
+  // Tracks HpackEntries by index.
+  const EntryTable& static_entries_;
+  EntryTable dynamic_entries_;
+
+  // Tracks the unique HpackEntry for a given header name and value.
+  const UnorderedEntrySet& static_index_;
+
+  // Tracks the first static entry for each name in the static table.
+  const NameToEntryMap& static_name_index_;
+
+  // Tracks the most recently inserted HpackEntry for a given header name and
+  // value.
+  UnorderedEntrySet dynamic_index_;
+
+  // Tracks the most recently inserted HpackEntry for a given header name.
+  NameToEntryMap dynamic_name_index_;
+
+  // Last acknowledged value for SETTINGS_HEADER_TABLE_SIZE.
+  size_t settings_size_bound_;
+
+  // Estimated current and maximum byte size of the table.
+  // |max_size_| <= |settings_size_bound_|
+  size_t size_;
+  size_t max_size_;
+
+  // Total number of table insertions which have occurred. Referenced by
+  // IndexOf() for determination of an HpackEntry's table index.
+  size_t total_insertions_;
+
+  std::unique_ptr<DebugVisitorInterface> debug_visitor_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_HPACK_HPACK_HEADER_TABLE_H_
diff --git a/spdy/core/hpack/hpack_header_table_test.cc b/spdy/core/hpack/hpack_header_table_test.cc
new file mode 100644
index 0000000..b032aba
--- /dev/null
+++ b/spdy/core/hpack/hpack_header_table_test.cc
@@ -0,0 +1,446 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <set>
+#include <vector>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+
+namespace spdy {
+
+using std::distance;
+
+namespace test {
+
+class HpackHeaderTablePeer {
+ public:
+  explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {}
+
+  const HpackHeaderTable::EntryTable& dynamic_entries() {
+    return table_->dynamic_entries_;
+  }
+  const HpackHeaderTable::EntryTable& static_entries() {
+    return table_->static_entries_;
+  }
+  size_t index_size() {
+    return table_->static_index_.size() + table_->dynamic_index_.size();
+  }
+  std::vector<HpackEntry*> EvictionSet(SpdyStringPiece name,
+                                       SpdyStringPiece value) {
+    HpackHeaderTable::EntryTable::iterator begin, end;
+    table_->EvictionSet(name, value, &begin, &end);
+    std::vector<HpackEntry*> result;
+    for (; begin != end; ++begin) {
+      result.push_back(&(*begin));
+    }
+    return result;
+  }
+  size_t total_insertions() { return table_->total_insertions_; }
+  size_t dynamic_entries_count() { return table_->dynamic_entries_.size(); }
+  size_t EvictionCountForEntry(SpdyStringPiece name, SpdyStringPiece value) {
+    return table_->EvictionCountForEntry(name, value);
+  }
+  size_t EvictionCountToReclaim(size_t reclaim_size) {
+    return table_->EvictionCountToReclaim(reclaim_size);
+  }
+  void Evict(size_t count) { return table_->Evict(count); }
+
+  void AddDynamicEntry(SpdyStringPiece name, SpdyStringPiece value) {
+    table_->dynamic_entries_.push_back(
+        HpackEntry(name, value, false, table_->total_insertions_++));
+  }
+
+ private:
+  HpackHeaderTable* table_;
+};
+
+}  // namespace test
+
+namespace {
+
+class HpackHeaderTableTest : public ::testing::Test {
+ protected:
+  typedef std::vector<HpackEntry> HpackEntryVector;
+
+  HpackHeaderTableTest() : table_(), peer_(&table_) {}
+
+  // Returns an entry whose Size() is equal to the given one.
+  static HpackEntry MakeEntryOfSize(uint32_t size) {
+    EXPECT_GE(size, HpackEntry::kSizeOverhead);
+    SpdyString name((size - HpackEntry::kSizeOverhead) / 2, 'n');
+    SpdyString value(size - HpackEntry::kSizeOverhead - name.size(), 'v');
+    HpackEntry entry(name, value, false, 0);
+    EXPECT_EQ(size, entry.Size());
+    return entry;
+  }
+
+  // Returns a vector of entries whose total size is equal to the given
+  // one.
+  static HpackEntryVector MakeEntriesOfTotalSize(uint32_t total_size) {
+    EXPECT_GE(total_size, HpackEntry::kSizeOverhead);
+    uint32_t entry_size = HpackEntry::kSizeOverhead;
+    uint32_t remaining_size = total_size;
+    HpackEntryVector entries;
+    while (remaining_size > 0) {
+      EXPECT_LE(entry_size, remaining_size);
+      entries.push_back(MakeEntryOfSize(entry_size));
+      remaining_size -= entry_size;
+      entry_size = std::min(remaining_size, entry_size + 32);
+    }
+    return entries;
+  }
+
+  // Adds the given vector of entries to the given header table,
+  // expecting no eviction to happen.
+  void AddEntriesExpectNoEviction(const HpackEntryVector& entries) {
+    for (auto it = entries.begin(); it != entries.end(); ++it) {
+      HpackHeaderTable::EntryTable::iterator begin, end;
+
+      table_.EvictionSet(it->name(), it->value(), &begin, &end);
+      EXPECT_EQ(0, distance(begin, end));
+
+      const HpackEntry* entry = table_.TryAddEntry(it->name(), it->value());
+      EXPECT_NE(entry, static_cast<HpackEntry*>(nullptr));
+    }
+
+    for (size_t i = 0; i != entries.size(); ++i) {
+      // Static table has 61 entries, dynamic entries follow those.
+      size_t index = 61 + entries.size() - i;
+      const HpackEntry* entry = table_.GetByIndex(index);
+      EXPECT_EQ(entries[i].name(), entry->name());
+      EXPECT_EQ(entries[i].value(), entry->value());
+      EXPECT_EQ(index, table_.IndexOf(entry));
+    }
+  }
+
+  HpackEntry DynamicEntry(const SpdyString& name, const SpdyString& value) {
+    peer_.AddDynamicEntry(name, value);
+    return peer_.dynamic_entries().back();
+  }
+
+  HpackHeaderTable table_;
+  test::HpackHeaderTablePeer peer_;
+};
+
+TEST_F(HpackHeaderTableTest, StaticTableInitialization) {
+  EXPECT_EQ(0u, table_.size());
+  EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.max_size());
+  EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.settings_size_bound());
+
+  EXPECT_EQ(0u, peer_.dynamic_entries_count());
+  EXPECT_EQ(peer_.static_entries().size(), peer_.total_insertions());
+
+  // Static entries have been populated and inserted into the table & index.
+  EXPECT_NE(0u, peer_.static_entries().size());
+  EXPECT_EQ(peer_.index_size(), peer_.static_entries().size());
+  for (size_t i = 0; i != peer_.static_entries().size(); ++i) {
+    const HpackEntry* entry = &peer_.static_entries()[i];
+
+    EXPECT_TRUE(entry->IsStatic());
+    EXPECT_EQ(entry, table_.GetByIndex(i + 1));
+    EXPECT_EQ(entry, table_.GetByNameAndValue(entry->name(), entry->value()));
+  }
+}
+
+TEST_F(HpackHeaderTableTest, BasicDynamicEntryInsertionAndEviction) {
+  size_t static_count = peer_.total_insertions();
+  const HpackEntry* first_static_entry = table_.GetByIndex(1);
+
+  EXPECT_EQ(1u, table_.IndexOf(first_static_entry));
+
+  const HpackEntry* entry = table_.TryAddEntry("header-key", "Header Value");
+  EXPECT_EQ("header-key", entry->name());
+  EXPECT_EQ("Header Value", entry->value());
+  EXPECT_FALSE(entry->IsStatic());
+
+  // Table counts were updated appropriately.
+  EXPECT_EQ(entry->Size(), table_.size());
+  EXPECT_EQ(1u, peer_.dynamic_entries_count());
+  EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count());
+  EXPECT_EQ(static_count + 1, peer_.total_insertions());
+  EXPECT_EQ(static_count + 1, peer_.index_size());
+
+  // Index() of entries reflects the insertion.
+  EXPECT_EQ(1u, table_.IndexOf(first_static_entry));
+  // Static table has 61 entries.
+  EXPECT_EQ(62u, table_.IndexOf(entry));
+  EXPECT_EQ(first_static_entry, table_.GetByIndex(1));
+  EXPECT_EQ(entry, table_.GetByIndex(62));
+
+  // Evict |entry|. Table counts are again updated appropriately.
+  peer_.Evict(1);
+  EXPECT_EQ(0u, table_.size());
+  EXPECT_EQ(0u, peer_.dynamic_entries_count());
+  EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count());
+  EXPECT_EQ(static_count + 1, peer_.total_insertions());
+  EXPECT_EQ(static_count, peer_.index_size());
+
+  // Index() of |first_static_entry| reflects the eviction.
+  EXPECT_EQ(1u, table_.IndexOf(first_static_entry));
+  EXPECT_EQ(first_static_entry, table_.GetByIndex(1));
+}
+
+TEST_F(HpackHeaderTableTest, EntryIndexing) {
+  const HpackEntry* first_static_entry = table_.GetByIndex(1);
+
+  // Static entries are queryable by name & value.
+  EXPECT_EQ(first_static_entry, table_.GetByName(first_static_entry->name()));
+  EXPECT_EQ(first_static_entry,
+            table_.GetByNameAndValue(first_static_entry->name(),
+                                     first_static_entry->value()));
+
+  // Create a mix of entries which duplicate names, and names & values of both
+  // dynamic and static entries.
+  const HpackEntry* entry1 = table_.TryAddEntry(first_static_entry->name(),
+                                                first_static_entry->value());
+  const HpackEntry* entry2 =
+      table_.TryAddEntry(first_static_entry->name(), "Value Four");
+  const HpackEntry* entry3 = table_.TryAddEntry("key-1", "Value One");
+  const HpackEntry* entry4 = table_.TryAddEntry("key-2", "Value Three");
+  const HpackEntry* entry5 = table_.TryAddEntry("key-1", "Value Two");
+  const HpackEntry* entry6 = table_.TryAddEntry("key-2", "Value Three");
+  const HpackEntry* entry7 = table_.TryAddEntry("key-2", "Value Four");
+
+  // Entries are queryable under their current index.
+  EXPECT_EQ(entry7, table_.GetByIndex(62));
+  EXPECT_EQ(entry6, table_.GetByIndex(63));
+  EXPECT_EQ(entry5, table_.GetByIndex(64));
+  EXPECT_EQ(entry4, table_.GetByIndex(65));
+  EXPECT_EQ(entry3, table_.GetByIndex(66));
+  EXPECT_EQ(entry2, table_.GetByIndex(67));
+  EXPECT_EQ(entry1, table_.GetByIndex(68));
+  EXPECT_EQ(first_static_entry, table_.GetByIndex(1));
+
+  // Querying by name returns the most recently added matching entry.
+  EXPECT_EQ(entry5, table_.GetByName("key-1"));
+  EXPECT_EQ(entry7, table_.GetByName("key-2"));
+  EXPECT_EQ(entry2->name(),
+            table_.GetByName(first_static_entry->name())->name());
+  EXPECT_EQ(nullptr, table_.GetByName("not-present"));
+
+  // Querying by name & value returns the lowest-index matching entry among
+  // static entries, and the highest-index one among dynamic entries.
+  EXPECT_EQ(entry3, table_.GetByNameAndValue("key-1", "Value One"));
+  EXPECT_EQ(entry5, table_.GetByNameAndValue("key-1", "Value Two"));
+  EXPECT_EQ(entry6, table_.GetByNameAndValue("key-2", "Value Three"));
+  EXPECT_EQ(entry7, table_.GetByNameAndValue("key-2", "Value Four"));
+  EXPECT_EQ(first_static_entry,
+            table_.GetByNameAndValue(first_static_entry->name(),
+                                     first_static_entry->value()));
+  EXPECT_EQ(entry2,
+            table_.GetByNameAndValue(first_static_entry->name(), "Value Four"));
+  EXPECT_EQ(nullptr, table_.GetByNameAndValue("key-1", "Not Present"));
+  EXPECT_EQ(nullptr, table_.GetByNameAndValue("not-present", "Value One"));
+
+  // Evict |entry1|. Queries for its name & value now return the static entry.
+  // |entry2| remains queryable.
+  peer_.Evict(1);
+  EXPECT_EQ(first_static_entry,
+            table_.GetByNameAndValue(first_static_entry->name(),
+                                     first_static_entry->value()));
+  EXPECT_EQ(entry2,
+            table_.GetByNameAndValue(first_static_entry->name(), "Value Four"));
+
+  // Evict |entry2|. Queries by its name & value are not found.
+  peer_.Evict(1);
+  EXPECT_EQ(nullptr,
+            table_.GetByNameAndValue(first_static_entry->name(), "Value Four"));
+}
+
+TEST_F(HpackHeaderTableTest, SetSizes) {
+  SpdyString key = "key", value = "value";
+  const HpackEntry* entry1 = table_.TryAddEntry(key, value);
+  const HpackEntry* entry2 = table_.TryAddEntry(key, value);
+  const HpackEntry* entry3 = table_.TryAddEntry(key, value);
+
+  // Set exactly large enough. No Evictions.
+  size_t max_size = entry1->Size() + entry2->Size() + entry3->Size();
+  table_.SetMaxSize(max_size);
+  EXPECT_EQ(3u, peer_.dynamic_entries().size());
+
+  // Set just too small. One eviction.
+  max_size = entry1->Size() + entry2->Size() + entry3->Size() - 1;
+  table_.SetMaxSize(max_size);
+  EXPECT_EQ(2u, peer_.dynamic_entries().size());
+
+  // Changing SETTINGS_HEADER_TABLE_SIZE.
+  EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.settings_size_bound());
+  // In production, the size passed to SetSettingsHeaderTableSize is never
+  // larger than table_.settings_size_bound().
+  table_.SetSettingsHeaderTableSize(kDefaultHeaderTableSizeSetting * 3 + 1);
+  EXPECT_EQ(kDefaultHeaderTableSizeSetting * 3 + 1, table_.max_size());
+
+  // SETTINGS_HEADER_TABLE_SIZE upper-bounds |table_.max_size()|,
+  // and will force evictions.
+  max_size = entry3->Size() - 1;
+  table_.SetSettingsHeaderTableSize(max_size);
+  EXPECT_EQ(max_size, table_.max_size());
+  EXPECT_EQ(max_size, table_.settings_size_bound());
+  EXPECT_EQ(0u, peer_.dynamic_entries().size());
+}
+
+TEST_F(HpackHeaderTableTest, EvictionCountForEntry) {
+  SpdyString key = "key", value = "value";
+  const HpackEntry* entry1 = table_.TryAddEntry(key, value);
+  const HpackEntry* entry2 = table_.TryAddEntry(key, value);
+  size_t entry3_size = HpackEntry::Size(key, value);
+
+  // Just enough capacity for third entry.
+  table_.SetMaxSize(entry1->Size() + entry2->Size() + entry3_size);
+  EXPECT_EQ(0u, peer_.EvictionCountForEntry(key, value));
+  EXPECT_EQ(1u, peer_.EvictionCountForEntry(key, value + "x"));
+
+  // No extra capacity. Third entry would force evictions.
+  table_.SetMaxSize(entry1->Size() + entry2->Size());
+  EXPECT_EQ(1u, peer_.EvictionCountForEntry(key, value));
+  EXPECT_EQ(2u, peer_.EvictionCountForEntry(key, value + "x"));
+}
+
+TEST_F(HpackHeaderTableTest, EvictionCountToReclaim) {
+  SpdyString key = "key", value = "value";
+  const HpackEntry* entry1 = table_.TryAddEntry(key, value);
+  const HpackEntry* entry2 = table_.TryAddEntry(key, value);
+
+  EXPECT_EQ(1u, peer_.EvictionCountToReclaim(1));
+  EXPECT_EQ(1u, peer_.EvictionCountToReclaim(entry1->Size()));
+  EXPECT_EQ(2u, peer_.EvictionCountToReclaim(entry1->Size() + 1));
+  EXPECT_EQ(2u, peer_.EvictionCountToReclaim(entry1->Size() + entry2->Size()));
+}
+
+// Fill a header table with entries. Make sure the entries are in
+// reverse order in the header table.
+TEST_F(HpackHeaderTableTest, TryAddEntryBasic) {
+  EXPECT_EQ(0u, table_.size());
+  EXPECT_EQ(table_.settings_size_bound(), table_.max_size());
+
+  HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size());
+
+  // Most of the checks are in AddEntriesExpectNoEviction().
+  AddEntriesExpectNoEviction(entries);
+  EXPECT_EQ(table_.max_size(), table_.size());
+  EXPECT_EQ(table_.settings_size_bound(), table_.size());
+}
+
+// Fill a header table with entries, and then ramp the table's max
+// size down to evict an entry one at a time. Make sure the eviction
+// happens as expected.
+TEST_F(HpackHeaderTableTest, SetMaxSize) {
+  HpackEntryVector entries =
+      MakeEntriesOfTotalSize(kDefaultHeaderTableSizeSetting / 2);
+  AddEntriesExpectNoEviction(entries);
+
+  for (auto it = entries.begin(); it != entries.end(); ++it) {
+    size_t expected_count = distance(it, entries.end());
+    EXPECT_EQ(expected_count, peer_.dynamic_entries().size());
+
+    table_.SetMaxSize(table_.size() + 1);
+    EXPECT_EQ(expected_count, peer_.dynamic_entries().size());
+
+    table_.SetMaxSize(table_.size());
+    EXPECT_EQ(expected_count, peer_.dynamic_entries().size());
+
+    --expected_count;
+    table_.SetMaxSize(table_.size() - 1);
+    EXPECT_EQ(expected_count, peer_.dynamic_entries().size());
+  }
+  EXPECT_EQ(0u, table_.size());
+}
+
+// Fill a header table with entries, and then add an entry just big
+// enough to cause eviction of all but one entry. Make sure the
+// eviction happens as expected and the long entry is inserted into
+// the table.
+TEST_F(HpackHeaderTableTest, TryAddEntryEviction) {
+  HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size());
+  AddEntriesExpectNoEviction(entries);
+
+  const HpackEntry* survivor_entry = table_.GetByIndex(61 + 1);
+  HpackEntry long_entry =
+      MakeEntryOfSize(table_.max_size() - survivor_entry->Size());
+
+  // All dynamic entries but the first are to be evicted.
+  EXPECT_EQ(peer_.dynamic_entries().size() - 1,
+            peer_.EvictionSet(long_entry.name(), long_entry.value()).size());
+
+  const HpackEntry* new_entry =
+      table_.TryAddEntry(long_entry.name(), long_entry.value());
+  EXPECT_EQ(62u, table_.IndexOf(new_entry));
+  EXPECT_EQ(2u, peer_.dynamic_entries().size());
+  EXPECT_EQ(table_.GetByIndex(63), survivor_entry);
+  EXPECT_EQ(table_.GetByIndex(62), new_entry);
+}
+
+// Fill a header table with entries, and then add an entry bigger than
+// the entire table. Make sure no entry remains in the table.
+TEST_F(HpackHeaderTableTest, TryAddTooLargeEntry) {
+  HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size());
+  AddEntriesExpectNoEviction(entries);
+
+  const HpackEntry long_entry = MakeEntryOfSize(table_.max_size() + 1);
+
+  // All entries are to be evicted.
+  EXPECT_EQ(peer_.dynamic_entries().size(),
+            peer_.EvictionSet(long_entry.name(), long_entry.value()).size());
+
+  const HpackEntry* new_entry =
+      table_.TryAddEntry(long_entry.name(), long_entry.value());
+  EXPECT_EQ(new_entry, static_cast<HpackEntry*>(nullptr));
+  EXPECT_EQ(0u, peer_.dynamic_entries().size());
+}
+
+TEST_F(HpackHeaderTableTest, EntryNamesDiffer) {
+  HpackEntry entry1("header", "value");
+  HpackEntry entry2("HEADER", "value");
+
+  HpackHeaderTable::EntryHasher hasher;
+  EXPECT_NE(hasher(&entry1), hasher(&entry2));
+
+  HpackHeaderTable::EntriesEq eq;
+  EXPECT_FALSE(eq(&entry1, &entry2));
+}
+
+TEST_F(HpackHeaderTableTest, EntryValuesDiffer) {
+  HpackEntry entry1("header", "value");
+  HpackEntry entry2("header", "VALUE");
+
+  HpackHeaderTable::EntryHasher hasher;
+  EXPECT_NE(hasher(&entry1), hasher(&entry2));
+
+  HpackHeaderTable::EntriesEq eq;
+  EXPECT_FALSE(eq(&entry1, &entry2));
+}
+
+TEST_F(HpackHeaderTableTest, EntriesEqual) {
+  HpackEntry entry1(DynamicEntry("name", "value"));
+  HpackEntry entry2(DynamicEntry("name", "value"));
+
+  HpackHeaderTable::EntryHasher hasher;
+  EXPECT_EQ(hasher(&entry1), hasher(&entry2));
+
+  HpackHeaderTable::EntriesEq eq;
+  EXPECT_TRUE(eq(&entry1, &entry2));
+}
+
+TEST_F(HpackHeaderTableTest, StaticAndDynamicEntriesEqual) {
+  HpackEntry entry1("name", "value");
+  HpackEntry entry2(DynamicEntry("name", "value"));
+
+  HpackHeaderTable::EntryHasher hasher;
+  EXPECT_EQ(hasher(&entry1), hasher(&entry2));
+
+  HpackHeaderTable::EntriesEq eq;
+  EXPECT_TRUE(eq(&entry1, &entry2));
+}
+
+}  // namespace
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_huffman_table.cc b/spdy/core/hpack/hpack_huffman_table.cc
new file mode 100644
index 0000000..c917767
--- /dev/null
+++ b/spdy/core/hpack/hpack_huffman_table.cc
@@ -0,0 +1,151 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <memory>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+
+namespace spdy {
+
+namespace {
+
+bool SymbolLengthAndIdCompare(const HpackHuffmanSymbol& a,
+                              const HpackHuffmanSymbol& b) {
+  if (a.length == b.length) {
+    return a.id < b.id;
+  }
+  return a.length < b.length;
+}
+bool SymbolIdCompare(const HpackHuffmanSymbol& a, const HpackHuffmanSymbol& b) {
+  return a.id < b.id;
+}
+
+}  // namespace
+
+HpackHuffmanTable::HpackHuffmanTable() : pad_bits_(0), failed_symbol_id_(0) {}
+
+HpackHuffmanTable::~HpackHuffmanTable() = default;
+
+bool HpackHuffmanTable::Initialize(const HpackHuffmanSymbol* input_symbols,
+                                   size_t symbol_count) {
+  CHECK(!IsInitialized());
+  DCHECK_LE(symbol_count, std::numeric_limits<uint16_t>::max());
+
+  std::vector<Symbol> symbols(symbol_count);
+  // Validate symbol id sequence, and copy into |symbols|.
+  for (uint16_t i = 0; i < symbol_count; i++) {
+    if (i != input_symbols[i].id) {
+      failed_symbol_id_ = i;
+      return false;
+    }
+    symbols[i] = input_symbols[i];
+  }
+  // Order on length and ID ascending, to verify symbol codes are canonical.
+  std::sort(symbols.begin(), symbols.end(), SymbolLengthAndIdCompare);
+  if (symbols[0].code != 0) {
+    failed_symbol_id_ = 0;
+    return false;
+  }
+  for (size_t i = 1; i != symbols.size(); i++) {
+    unsigned code_shift = 32 - symbols[i - 1].length;
+    uint32_t code = symbols[i - 1].code + (1 << code_shift);
+
+    if (code != symbols[i].code) {
+      failed_symbol_id_ = symbols[i].id;
+      return false;
+    }
+    if (code < symbols[i - 1].code) {
+      // An integer overflow occurred. This implies the input
+      // lengths do not represent a valid Huffman code.
+      failed_symbol_id_ = symbols[i].id;
+      return false;
+    }
+  }
+  if (symbols.back().length < 8) {
+    // At least one code (such as an EOS symbol) must be 8 bits or longer.
+    // Without this, some inputs will not be encodable in a whole number
+    // of bytes.
+    return false;
+  }
+  pad_bits_ = static_cast<uint8_t>(symbols.back().code >> 24);
+
+  // Order on symbol ID ascending.
+  std::sort(symbols.begin(), symbols.end(), SymbolIdCompare);
+  BuildEncodeTable(symbols);
+  return true;
+}
+
+void HpackHuffmanTable::BuildEncodeTable(const std::vector<Symbol>& symbols) {
+  for (size_t i = 0; i != symbols.size(); i++) {
+    const Symbol& symbol = symbols[i];
+    CHECK_EQ(i, symbol.id);
+    code_by_id_.push_back(symbol.code);
+    length_by_id_.push_back(symbol.length);
+  }
+}
+
+bool HpackHuffmanTable::IsInitialized() const {
+  return !code_by_id_.empty();
+}
+
+void HpackHuffmanTable::EncodeString(SpdyStringPiece in,
+                                     HpackOutputStream* out) const {
+  size_t bit_remnant = 0;
+  for (size_t i = 0; i != in.size(); i++) {
+    uint16_t symbol_id = static_cast<uint8_t>(in[i]);
+    CHECK_GT(code_by_id_.size(), symbol_id);
+
+    // Load, and shift code to low bits.
+    unsigned length = length_by_id_[symbol_id];
+    uint32_t code = code_by_id_[symbol_id] >> (32 - length);
+
+    bit_remnant = (bit_remnant + length) % 8;
+
+    if (length > 24) {
+      out->AppendBits(static_cast<uint8_t>(code >> 24), length - 24);
+      length = 24;
+    }
+    if (length > 16) {
+      out->AppendBits(static_cast<uint8_t>(code >> 16), length - 16);
+      length = 16;
+    }
+    if (length > 8) {
+      out->AppendBits(static_cast<uint8_t>(code >> 8), length - 8);
+      length = 8;
+    }
+    out->AppendBits(static_cast<uint8_t>(code), length);
+  }
+  if (bit_remnant != 0) {
+    // Pad current byte as required.
+    out->AppendBits(pad_bits_ >> bit_remnant, 8 - bit_remnant);
+  }
+}
+
+size_t HpackHuffmanTable::EncodedSize(SpdyStringPiece in) const {
+  size_t bit_count = 0;
+  for (size_t i = 0; i != in.size(); i++) {
+    uint16_t symbol_id = static_cast<uint8_t>(in[i]);
+    CHECK_GT(code_by_id_.size(), symbol_id);
+
+    bit_count += length_by_id_[symbol_id];
+  }
+  if (bit_count % 8 != 0) {
+    bit_count += 8 - bit_count % 8;
+  }
+  return bit_count / 8;
+}
+
+size_t HpackHuffmanTable::EstimateMemoryUsage() const {
+  return SpdyEstimateMemoryUsage(code_by_id_) +
+         SpdyEstimateMemoryUsage(length_by_id_);
+}
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_huffman_table.h b/spdy/core/hpack/hpack_huffman_table.h
new file mode 100644
index 0000000..80d9e52
--- /dev/null
+++ b/spdy/core/hpack/hpack_huffman_table.h
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_HUFFMAN_TABLE_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_HUFFMAN_TABLE_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+class HpackHuffmanTablePeer;
+}  // namespace test
+
+class HpackOutputStream;
+
+// HpackHuffmanTable encodes string literals using a constructed canonical
+// Huffman code. Once initialized, an instance is read only and may be accessed
+// only through its const interface.
+class SPDY_EXPORT_PRIVATE HpackHuffmanTable {
+ public:
+  friend class test::HpackHuffmanTablePeer;
+
+  typedef HpackHuffmanSymbol Symbol;
+
+  HpackHuffmanTable();
+  ~HpackHuffmanTable();
+
+  // Prepares HpackHuffmanTable to encode the canonical Huffman code as
+  // determined by the given symbols. Must be called exactly once.
+  // Returns false if the input symbols define an invalid coding, and true
+  // otherwise. Symbols must be presented in ascending ID order with no gaps,
+  // and |symbol_count| must fit in a uint16_t.
+  bool Initialize(const Symbol* input_symbols, size_t symbol_count);
+
+  // Returns whether Initialize() has been successfully called.
+  bool IsInitialized() const;
+
+  // Encodes the input string to the output stream using the table's Huffman
+  // context.
+  void EncodeString(SpdyStringPiece in, HpackOutputStream* out) const;
+
+  // Returns the encoded size of the input string.
+  size_t EncodedSize(SpdyStringPiece in) const;
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+ private:
+  // Expects symbols ordered on ID ascending.
+  void BuildEncodeTable(const std::vector<Symbol>& symbols);
+
+  // Symbol code and code length, in ascending symbol ID order.
+  // Codes are stored in the most-significant bits of the word.
+  std::vector<uint32_t> code_by_id_;
+  std::vector<uint8_t> length_by_id_;
+
+  // The first 8 bits of the longest code. Applied when generating padding bits.
+  uint8_t pad_bits_;
+
+  // If initialization fails, preserve the symbol ID which failed validation
+  // for examination in tests.
+  uint16_t failed_symbol_id_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_HPACK_HPACK_HUFFMAN_TABLE_H_
diff --git a/spdy/core/hpack/hpack_huffman_table_test.cc b/spdy/core/hpack/hpack_huffman_table_test.cc
new file mode 100644
index 0000000..f30926c
--- /dev/null
+++ b/spdy/core/hpack/hpack_huffman_table_test.cc
@@ -0,0 +1,309 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
+
+#include <utility>
+
+#include "base/macros.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+namespace test {
+
+class HpackHuffmanTablePeer {
+ public:
+  explicit HpackHuffmanTablePeer(const HpackHuffmanTable& table)
+      : table_(table) {}
+
+  const std::vector<uint32_t>& code_by_id() const { return table_.code_by_id_; }
+  const std::vector<uint8_t>& length_by_id() const {
+    return table_.length_by_id_;
+  }
+  uint8_t pad_bits() const { return table_.pad_bits_; }
+  uint16_t failed_symbol_id() const { return table_.failed_symbol_id_; }
+
+ private:
+  const HpackHuffmanTable& table_;
+};
+
+namespace {
+
+// Tests of the ability to encode some canonical Huffman code,
+// not just the one defined in the RFC 7541.
+class GenericHuffmanTableTest : public ::testing::Test {
+ protected:
+  GenericHuffmanTableTest() : table_(), peer_(table_) {}
+
+  SpdyString EncodeString(SpdyStringPiece input) {
+    SpdyString result;
+    HpackOutputStream output_stream;
+    table_.EncodeString(input, &output_stream);
+
+    output_stream.TakeString(&result);
+    // Verify EncodedSize() agrees with EncodeString().
+    EXPECT_EQ(result.size(), table_.EncodedSize(input));
+    return result;
+  }
+
+  HpackHuffmanTable table_;
+  HpackHuffmanTablePeer peer_;
+};
+
+TEST_F(GenericHuffmanTableTest, InitializeEdgeCases) {
+  {
+    // Verify eight symbols can be encoded with 3 bits per symbol.
+    HpackHuffmanSymbol code[] = {{0b00000000000000000000000000000000, 3, 0},
+                                 {0b00100000000000000000000000000000, 3, 1},
+                                 {0b01000000000000000000000000000000, 3, 2},
+                                 {0b01100000000000000000000000000000, 3, 3},
+                                 {0b10000000000000000000000000000000, 3, 4},
+                                 {0b10100000000000000000000000000000, 3, 5},
+                                 {0b11000000000000000000000000000000, 3, 6},
+                                 {0b11100000000000000000000000000000, 8, 7}};
+    HpackHuffmanTable table;
+    EXPECT_TRUE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+  }
+  {
+    // But using 2 bits with one symbol overflows the code.
+    HpackHuffmanSymbol code[] = {
+        {0b01000000000000000000000000000000, 3, 0},
+        {0b01100000000000000000000000000000, 3, 1},
+        {0b00000000000000000000000000000000, 2, 2},
+        {0b10000000000000000000000000000000, 3, 3},
+        {0b10100000000000000000000000000000, 3, 4},
+        {0b11000000000000000000000000000000, 3, 5},
+        {0b11100000000000000000000000000000, 3, 6},
+        {0b00000000000000000000000000000000, 8, 7}};  // Overflow.
+    HpackHuffmanTable table;
+    EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+    EXPECT_EQ(7, HpackHuffmanTablePeer(table).failed_symbol_id());
+  }
+  {
+    // Verify four symbols can be encoded with incremental bits per symbol.
+    HpackHuffmanSymbol code[] = {{0b00000000000000000000000000000000, 1, 0},
+                                 {0b10000000000000000000000000000000, 2, 1},
+                                 {0b11000000000000000000000000000000, 3, 2},
+                                 {0b11100000000000000000000000000000, 8, 3}};
+    HpackHuffmanTable table;
+    EXPECT_TRUE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+  }
+  {
+    // But repeating a length overflows the code.
+    HpackHuffmanSymbol code[] = {
+        {0b00000000000000000000000000000000, 1, 0},
+        {0b10000000000000000000000000000000, 2, 1},
+        {0b11000000000000000000000000000000, 2, 2},
+        {0b00000000000000000000000000000000, 8, 3}};  // Overflow.
+    HpackHuffmanTable table;
+    EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+    EXPECT_EQ(3, HpackHuffmanTablePeer(table).failed_symbol_id());
+  }
+  {
+    // Symbol IDs must be assigned sequentially with no gaps.
+    HpackHuffmanSymbol code[] = {
+        {0b00000000000000000000000000000000, 1, 0},
+        {0b10000000000000000000000000000000, 2, 1},
+        {0b11000000000000000000000000000000, 3, 1},  // Repeat.
+        {0b11100000000000000000000000000000, 8, 3}};
+    HpackHuffmanTable table;
+    EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+    EXPECT_EQ(2, HpackHuffmanTablePeer(table).failed_symbol_id());
+  }
+  {
+    // Canonical codes must begin with zero.
+    HpackHuffmanSymbol code[] = {{0b10000000000000000000000000000000, 4, 0},
+                                 {0b10010000000000000000000000000000, 4, 1},
+                                 {0b10100000000000000000000000000000, 4, 2},
+                                 {0b10110000000000000000000000000000, 8, 3}};
+    HpackHuffmanTable table;
+    EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+    EXPECT_EQ(0, HpackHuffmanTablePeer(table).failed_symbol_id());
+  }
+  {
+    // Codes must match the expected canonical sequence.
+    HpackHuffmanSymbol code[] = {
+        {0b00000000000000000000000000000000, 2, 0},
+        {0b01000000000000000000000000000000, 2, 1},
+        {0b11000000000000000000000000000000, 2, 2},  // Code not canonical.
+        {0b10000000000000000000000000000000, 8, 3}};
+    HpackHuffmanTable table;
+    EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+    EXPECT_EQ(2, HpackHuffmanTablePeer(table).failed_symbol_id());
+  }
+  {
+    // At least one code must have a length of 8 bits (to ensure pad-ability).
+    HpackHuffmanSymbol code[] = {{0b00000000000000000000000000000000, 1, 0},
+                                 {0b10000000000000000000000000000000, 2, 1},
+                                 {0b11000000000000000000000000000000, 3, 2},
+                                 {0b11100000000000000000000000000000, 7, 3}};
+    HpackHuffmanTable table;
+    EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+  }
+}
+
+TEST_F(GenericHuffmanTableTest, ValidateInternalsWithSmallCode) {
+  HpackHuffmanSymbol code[] = {
+      {0b01100000000000000000000000000000, 4, 0},   // 3rd.
+      {0b01110000000000000000000000000000, 4, 1},   // 4th.
+      {0b00000000000000000000000000000000, 2, 2},   // 1st assigned code.
+      {0b01000000000000000000000000000000, 3, 3},   // 2nd.
+      {0b10000000000000000000000000000000, 5, 4},   // 5th.
+      {0b10001000000000000000000000000000, 5, 5},   // 6th.
+      {0b10011000000000000000000000000000, 8, 6},   // 8th.
+      {0b10010000000000000000000000000000, 5, 7}};  // 7th.
+  EXPECT_TRUE(table_.Initialize(code, SPDY_ARRAYSIZE(code)));
+
+  ASSERT_EQ(SPDY_ARRAYSIZE(code), peer_.code_by_id().size());
+  ASSERT_EQ(SPDY_ARRAYSIZE(code), peer_.length_by_id().size());
+  for (size_t i = 0; i < SPDY_ARRAYSIZE(code); ++i) {
+    EXPECT_EQ(code[i].code, peer_.code_by_id()[i]);
+    EXPECT_EQ(code[i].length, peer_.length_by_id()[i]);
+  }
+
+  EXPECT_EQ(0b10011000, peer_.pad_bits());
+
+  char input_storage[] = {2, 3, 2, 7, 4};
+  SpdyStringPiece input(input_storage, SPDY_ARRAYSIZE(input_storage));
+  // By symbol: (2) 00 (3) 010 (2) 00 (7) 10010 (4) 10000 (6 as pad) 1001100.
+  char expect_storage[] = {0b00010001, 0b00101000, 0b01001100};
+  SpdyStringPiece expect(expect_storage, SPDY_ARRAYSIZE(expect_storage));
+  EXPECT_EQ(expect, EncodeString(input));
+}
+
+// Tests of the ability to encode the HPACK Huffman Code, defined in:
+//     https://httpwg.github.io/specs/rfc7541.html#huffman.code
+class HpackHuffmanTableTest : public GenericHuffmanTableTest {
+ protected:
+  void SetUp() override {
+    EXPECT_TRUE(table_.Initialize(HpackHuffmanCodeVector().data(),
+                                  HpackHuffmanCodeVector().size()));
+    EXPECT_TRUE(table_.IsInitialized());
+  }
+
+  // Use http2::HpackHuffmanDecoder for roundtrip tests.
+  void DecodeString(const SpdyString& encoded, SpdyString* out) {
+    http2::HpackHuffmanDecoder decoder;
+    out->clear();
+    EXPECT_TRUE(decoder.Decode(encoded, out));
+  }
+};
+
+TEST_F(HpackHuffmanTableTest, InitializeHpackCode) {
+  EXPECT_EQ(peer_.pad_bits(), 0b11111111);  // First 8 bits of EOS.
+}
+
+TEST_F(HpackHuffmanTableTest, SpecRequestExamples) {
+  SpdyString buffer;
+  SpdyString test_table[] = {
+      SpdyHexDecode("f1e3c2e5f23a6ba0ab90f4ff"),
+      "www.example.com",
+      SpdyHexDecode("a8eb10649cbf"),
+      "no-cache",
+      SpdyHexDecode("25a849e95ba97d7f"),
+      "custom-key",
+      SpdyHexDecode("25a849e95bb8e8b4bf"),
+      "custom-value",
+  };
+  // Round-trip each test example.
+  for (size_t i = 0; i != SPDY_ARRAYSIZE(test_table); i += 2) {
+    const SpdyString& encodedFixture(test_table[i]);
+    const SpdyString& decodedFixture(test_table[i + 1]);
+    DecodeString(encodedFixture, &buffer);
+    EXPECT_EQ(decodedFixture, buffer);
+    buffer = EncodeString(decodedFixture);
+    EXPECT_EQ(encodedFixture, buffer);
+  }
+}
+
+TEST_F(HpackHuffmanTableTest, SpecResponseExamples) {
+  SpdyString buffer;
+  SpdyString test_table[] = {
+      SpdyHexDecode("6402"),
+      "302",
+      SpdyHexDecode("aec3771a4b"),
+      "private",
+      SpdyHexDecode("d07abe941054d444a8200595040b8166"
+                    "e082a62d1bff"),
+      "Mon, 21 Oct 2013 20:13:21 GMT",
+      SpdyHexDecode("9d29ad171863c78f0b97c8e9ae82ae43"
+                    "d3"),
+      "https://www.example.com",
+      SpdyHexDecode("94e7821dd7f2e6c7b335dfdfcd5b3960"
+                    "d5af27087f3672c1ab270fb5291f9587"
+                    "316065c003ed4ee5b1063d5007"),
+      "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+  };
+  // Round-trip each test example.
+  for (size_t i = 0; i != SPDY_ARRAYSIZE(test_table); i += 2) {
+    const SpdyString& encodedFixture(test_table[i]);
+    const SpdyString& decodedFixture(test_table[i + 1]);
+    DecodeString(encodedFixture, &buffer);
+    EXPECT_EQ(decodedFixture, buffer);
+    buffer = EncodeString(decodedFixture);
+    EXPECT_EQ(encodedFixture, buffer);
+  }
+}
+
+TEST_F(HpackHuffmanTableTest, RoundTripIndividualSymbols) {
+  for (size_t i = 0; i != 256; i++) {
+    char c = static_cast<char>(i);
+    char storage[3] = {c, c, c};
+    SpdyStringPiece input(storage, SPDY_ARRAYSIZE(storage));
+    SpdyString buffer_in = EncodeString(input);
+    SpdyString buffer_out;
+    DecodeString(buffer_in, &buffer_out);
+    EXPECT_EQ(input, buffer_out);
+  }
+}
+
+TEST_F(HpackHuffmanTableTest, RoundTripSymbolSequence) {
+  char storage[512];
+  for (size_t i = 0; i != 256; i++) {
+    storage[i] = static_cast<char>(i);
+    storage[511 - i] = static_cast<char>(i);
+  }
+  SpdyStringPiece input(storage, SPDY_ARRAYSIZE(storage));
+  SpdyString buffer_in = EncodeString(input);
+  SpdyString buffer_out;
+  DecodeString(buffer_in, &buffer_out);
+  EXPECT_EQ(input, buffer_out);
+}
+
+TEST_F(HpackHuffmanTableTest, EncodedSizeAgreesWithEncodeString) {
+  SpdyString test_table[] = {
+      "",
+      "Mon, 21 Oct 2013 20:13:21 GMT",
+      "https://www.example.com",
+      "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+      SpdyString(1, '\0'),
+      SpdyString("foo\0bar", 7),
+      SpdyString(256, '\0'),
+  };
+  for (size_t i = 0; i != 256; ++i) {
+    // Expand last |test_table| entry to cover all codes.
+    test_table[SPDY_ARRAYSIZE(test_table) - 1][i] = static_cast<char>(i);
+  }
+
+  HpackOutputStream output_stream;
+  SpdyString encoding;
+  for (size_t i = 0; i != SPDY_ARRAYSIZE(test_table); ++i) {
+    table_.EncodeString(test_table[i], &output_stream);
+    output_stream.TakeString(&encoding);
+    EXPECT_EQ(encoding.size(), table_.EncodedSize(test_table[i]));
+  }
+}
+
+}  // namespace
+
+}  // namespace test
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_output_stream.cc b/spdy/core/hpack/hpack_output_stream.cc
new file mode 100644
index 0000000..30b5662
--- /dev/null
+++ b/spdy/core/hpack/hpack_output_stream.cc
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+
+namespace spdy {
+
+HpackOutputStream::HpackOutputStream() : bit_offset_(0) {}
+
+HpackOutputStream::~HpackOutputStream() = default;
+
+void HpackOutputStream::AppendBits(uint8_t bits, size_t bit_size) {
+  DCHECK_GT(bit_size, 0u);
+  DCHECK_LE(bit_size, 8u);
+  DCHECK_EQ(bits >> bit_size, 0);
+  size_t new_bit_offset = bit_offset_ + bit_size;
+  if (bit_offset_ == 0) {
+    // Buffer ends on a byte boundary.
+    DCHECK_LE(bit_size, 8u);
+    buffer_.append(1, bits << (8 - bit_size));
+  } else if (new_bit_offset <= 8) {
+    // Buffer does not end on a byte boundary but the given bits fit
+    // in the remainder of the last byte.
+    buffer_.back() |= bits << (8 - new_bit_offset);
+  } else {
+    // Buffer does not end on a byte boundary and the given bits do
+    // not fit in the remainder of the last byte.
+    buffer_.back() |= bits >> (new_bit_offset - 8);
+    buffer_.append(1, bits << (16 - new_bit_offset));
+  }
+  bit_offset_ = new_bit_offset % 8;
+}
+
+void HpackOutputStream::AppendPrefix(HpackPrefix prefix) {
+  AppendBits(prefix.bits, prefix.bit_size);
+}
+
+void HpackOutputStream::AppendBytes(SpdyStringPiece buffer) {
+  DCHECK_EQ(bit_offset_, 0u);
+  buffer_.append(buffer.data(), buffer.size());
+}
+
+void HpackOutputStream::AppendUint32(uint32_t I) {
+  // The algorithm below is adapted from the pseudocode in 6.1.
+  size_t N = 8 - bit_offset_;
+  uint8_t max_first_byte = static_cast<uint8_t>((1 << N) - 1);
+  if (I < max_first_byte) {
+    AppendBits(static_cast<uint8_t>(I), N);
+  } else {
+    AppendBits(max_first_byte, N);
+    I -= max_first_byte;
+    while ((I & ~0x7f) != 0) {
+      buffer_.append(1, (I & 0x7f) | 0x80);
+      I >>= 7;
+    }
+    AppendBits(static_cast<uint8_t>(I), 8);
+  }
+}
+
+void HpackOutputStream::TakeString(SpdyString* output) {
+  // This must hold, since all public functions cause the buffer to
+  // end on a byte boundary.
+  DCHECK_EQ(bit_offset_, 0u);
+  buffer_.swap(*output);
+  buffer_.clear();
+  bit_offset_ = 0;
+}
+
+void HpackOutputStream::BoundedTakeString(size_t max_size, SpdyString* output) {
+  if (buffer_.size() > max_size) {
+    // Save off overflow bytes to temporary string (causes a copy).
+    SpdyString overflow(buffer_.data() + max_size, buffer_.size() - max_size);
+
+    // Resize buffer down to the given limit.
+    buffer_.resize(max_size);
+
+    // Give buffer to output string.
+    *output = std::move(buffer_);
+
+    // Reset to contain overflow.
+    buffer_ = std::move(overflow);
+  } else {
+    TakeString(output);
+  }
+}
+
+size_t HpackOutputStream::EstimateMemoryUsage() const {
+  return SpdyEstimateMemoryUsage(buffer_);
+}
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_output_stream.h b/spdy/core/hpack/hpack_output_stream.h
new file mode 100644
index 0000000..450803f
--- /dev/null
+++ b/spdy/core/hpack/hpack_output_stream.h
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_OUTPUT_STREAM_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_OUTPUT_STREAM_H_
+
+#include <cstdint>
+#include <map>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+// All section references below are to
+// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08
+
+namespace spdy {
+
+// An HpackOutputStream handles all the low-level details of encoding
+// header fields.
+class SPDY_EXPORT_PRIVATE HpackOutputStream {
+ public:
+  HpackOutputStream();
+  HpackOutputStream(const HpackOutputStream&) = delete;
+  HpackOutputStream& operator=(const HpackOutputStream&) = delete;
+  ~HpackOutputStream();
+
+  // Appends the lower |bit_size| bits of |bits| to the internal buffer.
+  //
+  // |bit_size| must be > 0 and <= 8. |bits| must not have any bits
+  // set other than the lower |bit_size| bits.
+  void AppendBits(uint8_t bits, size_t bit_size);
+
+  // Simply forwards to AppendBits(prefix.bits, prefix.bit-size).
+  void AppendPrefix(HpackPrefix prefix);
+
+  // Directly appends |buffer|.
+  void AppendBytes(SpdyStringPiece buffer);
+
+  // Appends the given integer using the representation described in
+  // 6.1. If the internal buffer ends on a byte boundary, the prefix
+  // length N is taken to be 8; otherwise, it is taken to be the
+  // number of bits to the next byte boundary.
+  //
+  // It is guaranteed that the internal buffer will end on a byte
+  // boundary after this function is called.
+  void AppendUint32(uint32_t I);
+
+  // Swaps the internal buffer with |output|, then resets state.
+  void TakeString(SpdyString* output);
+
+  // Gives up to |max_size| bytes of the internal buffer to |output|. Resets
+  // internal state with the overflow.
+  void BoundedTakeString(size_t max_size, SpdyString* output);
+
+  // Size in bytes of stream's internal buffer.
+  size_t size() const { return buffer_.size(); }
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+ private:
+  // The internal bit buffer.
+  SpdyString buffer_;
+
+  // If 0, the buffer ends on a byte boundary. If non-zero, the buffer
+  // ends on the nth most significant bit. Guaranteed to be < 8.
+  size_t bit_offset_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_HPACK_HPACK_OUTPUT_STREAM_H_
diff --git a/spdy/core/hpack/hpack_output_stream_test.cc b/spdy/core/hpack/hpack_output_stream_test.cc
new file mode 100644
index 0000000..823c41b
--- /dev/null
+++ b/spdy/core/hpack/hpack_output_stream_test.cc
@@ -0,0 +1,276 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+
+#include <cstddef>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace spdy {
+
+namespace {
+
+// Make sure that AppendBits() appends bits starting from the most
+// significant bit, and that it can handle crossing a byte boundary.
+TEST(HpackOutputStreamTest, AppendBits) {
+  HpackOutputStream output_stream;
+  SpdyString expected_str;
+
+  output_stream.AppendBits(0x1, 1);
+  expected_str.append(1, 0x00);
+  expected_str.back() |= (0x1 << 7);
+
+  output_stream.AppendBits(0x0, 1);
+
+  output_stream.AppendBits(0x3, 2);
+  *expected_str.rbegin() |= (0x3 << 4);
+
+  output_stream.AppendBits(0x0, 2);
+
+  // Byte-crossing append.
+  output_stream.AppendBits(0x7, 3);
+  *expected_str.rbegin() |= (0x7 >> 1);
+  expected_str.append(1, 0x00);
+  expected_str.back() |= (0x7 << 7);
+
+  output_stream.AppendBits(0x0, 7);
+
+  SpdyString str;
+  output_stream.TakeString(&str);
+  EXPECT_EQ(expected_str, str);
+}
+
+// Utility function to return I as a string encoded with an N-bit
+// prefix.
+SpdyString EncodeUint32(uint8_t N, uint32_t I) {
+  HpackOutputStream output_stream;
+  if (N < 8) {
+    output_stream.AppendBits(0x00, 8 - N);
+  }
+  output_stream.AppendUint32(I);
+  SpdyString str;
+  output_stream.TakeString(&str);
+  return str;
+}
+
+// The {Number}ByteIntegersEightBitPrefix tests below test that
+// certain integers are encoded correctly with an 8-bit prefix in
+// exactly {Number} bytes.
+
+TEST(HpackOutputStreamTest, OneByteIntegersEightBitPrefix) {
+  // Minimum.
+  EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(8, 0x00));
+  EXPECT_EQ("\x7f", EncodeUint32(8, 0x7f));
+  // Maximum.
+  EXPECT_EQ("\xfe", EncodeUint32(8, 0xfe));
+}
+
+TEST(HpackOutputStreamTest, TwoByteIntegersEightBitPrefix) {
+  // Minimum.
+  EXPECT_EQ(SpdyString("\xff\x00", 2), EncodeUint32(8, 0xff));
+  EXPECT_EQ("\xff\x01", EncodeUint32(8, 0x0100));
+  // Maximum.
+  EXPECT_EQ("\xff\x7f", EncodeUint32(8, 0x017e));
+}
+
+TEST(HpackOutputStreamTest, ThreeByteIntegersEightBitPrefix) {
+  // Minimum.
+  EXPECT_EQ("\xff\x80\x01", EncodeUint32(8, 0x017f));
+  EXPECT_EQ("\xff\x80\x1e", EncodeUint32(8, 0x0fff));
+  // Maximum.
+  EXPECT_EQ("\xff\xff\x7f", EncodeUint32(8, 0x40fe));
+}
+
+TEST(HpackOutputStreamTest, FourByteIntegersEightBitPrefix) {
+  // Minimum.
+  EXPECT_EQ("\xff\x80\x80\x01", EncodeUint32(8, 0x40ff));
+  EXPECT_EQ("\xff\x80\xfe\x03", EncodeUint32(8, 0xffff));
+  // Maximum.
+  EXPECT_EQ("\xff\xff\xff\x7f", EncodeUint32(8, 0x002000fe));
+}
+
+TEST(HpackOutputStreamTest, FiveByteIntegersEightBitPrefix) {
+  // Minimum.
+  EXPECT_EQ("\xff\x80\x80\x80\x01", EncodeUint32(8, 0x002000ff));
+  EXPECT_EQ("\xff\x80\xfe\xff\x07", EncodeUint32(8, 0x00ffffff));
+  // Maximum.
+  EXPECT_EQ("\xff\xff\xff\xff\x7f", EncodeUint32(8, 0x100000fe));
+}
+
+TEST(HpackOutputStreamTest, SixByteIntegersEightBitPrefix) {
+  // Minimum.
+  EXPECT_EQ("\xff\x80\x80\x80\x80\x01", EncodeUint32(8, 0x100000ff));
+  // Maximum.
+  EXPECT_EQ("\xff\x80\xfe\xff\xff\x0f", EncodeUint32(8, 0xffffffff));
+}
+
+// The {Number}ByteIntegersOneToSevenBitPrefix tests below test that
+// certain integers are encoded correctly with an N-bit prefix in
+// exactly {Number} bytes for N in {1, 2, ..., 7}.
+
+TEST(HpackOutputStreamTest, OneByteIntegersOneToSevenBitPrefixes) {
+  // Minimums.
+  EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(7, 0x00));
+  EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(6, 0x00));
+  EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(5, 0x00));
+  EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(4, 0x00));
+  EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(3, 0x00));
+  EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(2, 0x00));
+  EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(1, 0x00));
+
+  // Maximums.
+  EXPECT_EQ("\x7e", EncodeUint32(7, 0x7e));
+  EXPECT_EQ("\x3e", EncodeUint32(6, 0x3e));
+  EXPECT_EQ("\x1e", EncodeUint32(5, 0x1e));
+  EXPECT_EQ("\x0e", EncodeUint32(4, 0x0e));
+  EXPECT_EQ("\x06", EncodeUint32(3, 0x06));
+  EXPECT_EQ("\x02", EncodeUint32(2, 0x02));
+  EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(1, 0x00));
+}
+
+TEST(HpackOutputStreamTest, TwoByteIntegersOneToSevenBitPrefixes) {
+  // Minimums.
+  EXPECT_EQ(SpdyString("\x7f\x00", 2), EncodeUint32(7, 0x7f));
+  EXPECT_EQ(SpdyString("\x3f\x00", 2), EncodeUint32(6, 0x3f));
+  EXPECT_EQ(SpdyString("\x1f\x00", 2), EncodeUint32(5, 0x1f));
+  EXPECT_EQ(SpdyString("\x0f\x00", 2), EncodeUint32(4, 0x0f));
+  EXPECT_EQ(SpdyString("\x07\x00", 2), EncodeUint32(3, 0x07));
+  EXPECT_EQ(SpdyString("\x03\x00", 2), EncodeUint32(2, 0x03));
+  EXPECT_EQ(SpdyString("\x01\x00", 2), EncodeUint32(1, 0x01));
+
+  // Maximums.
+  EXPECT_EQ("\x7f\x7f", EncodeUint32(7, 0xfe));
+  EXPECT_EQ("\x3f\x7f", EncodeUint32(6, 0xbe));
+  EXPECT_EQ("\x1f\x7f", EncodeUint32(5, 0x9e));
+  EXPECT_EQ("\x0f\x7f", EncodeUint32(4, 0x8e));
+  EXPECT_EQ("\x07\x7f", EncodeUint32(3, 0x86));
+  EXPECT_EQ("\x03\x7f", EncodeUint32(2, 0x82));
+  EXPECT_EQ("\x01\x7f", EncodeUint32(1, 0x80));
+}
+
+TEST(HpackOutputStreamTest, ThreeByteIntegersOneToSevenBitPrefixes) {
+  // Minimums.
+  EXPECT_EQ("\x7f\x80\x01", EncodeUint32(7, 0xff));
+  EXPECT_EQ("\x3f\x80\x01", EncodeUint32(6, 0xbf));
+  EXPECT_EQ("\x1f\x80\x01", EncodeUint32(5, 0x9f));
+  EXPECT_EQ("\x0f\x80\x01", EncodeUint32(4, 0x8f));
+  EXPECT_EQ("\x07\x80\x01", EncodeUint32(3, 0x87));
+  EXPECT_EQ("\x03\x80\x01", EncodeUint32(2, 0x83));
+  EXPECT_EQ("\x01\x80\x01", EncodeUint32(1, 0x81));
+
+  // Maximums.
+  EXPECT_EQ("\x7f\xff\x7f", EncodeUint32(7, 0x407e));
+  EXPECT_EQ("\x3f\xff\x7f", EncodeUint32(6, 0x403e));
+  EXPECT_EQ("\x1f\xff\x7f", EncodeUint32(5, 0x401e));
+  EXPECT_EQ("\x0f\xff\x7f", EncodeUint32(4, 0x400e));
+  EXPECT_EQ("\x07\xff\x7f", EncodeUint32(3, 0x4006));
+  EXPECT_EQ("\x03\xff\x7f", EncodeUint32(2, 0x4002));
+  EXPECT_EQ("\x01\xff\x7f", EncodeUint32(1, 0x4000));
+}
+
+TEST(HpackOutputStreamTest, FourByteIntegersOneToSevenBitPrefixes) {
+  // Minimums.
+  EXPECT_EQ("\x7f\x80\x80\x01", EncodeUint32(7, 0x407f));
+  EXPECT_EQ("\x3f\x80\x80\x01", EncodeUint32(6, 0x403f));
+  EXPECT_EQ("\x1f\x80\x80\x01", EncodeUint32(5, 0x401f));
+  EXPECT_EQ("\x0f\x80\x80\x01", EncodeUint32(4, 0x400f));
+  EXPECT_EQ("\x07\x80\x80\x01", EncodeUint32(3, 0x4007));
+  EXPECT_EQ("\x03\x80\x80\x01", EncodeUint32(2, 0x4003));
+  EXPECT_EQ("\x01\x80\x80\x01", EncodeUint32(1, 0x4001));
+
+  // Maximums.
+  EXPECT_EQ("\x7f\xff\xff\x7f", EncodeUint32(7, 0x20007e));
+  EXPECT_EQ("\x3f\xff\xff\x7f", EncodeUint32(6, 0x20003e));
+  EXPECT_EQ("\x1f\xff\xff\x7f", EncodeUint32(5, 0x20001e));
+  EXPECT_EQ("\x0f\xff\xff\x7f", EncodeUint32(4, 0x20000e));
+  EXPECT_EQ("\x07\xff\xff\x7f", EncodeUint32(3, 0x200006));
+  EXPECT_EQ("\x03\xff\xff\x7f", EncodeUint32(2, 0x200002));
+  EXPECT_EQ("\x01\xff\xff\x7f", EncodeUint32(1, 0x200000));
+}
+
+TEST(HpackOutputStreamTest, FiveByteIntegersOneToSevenBitPrefixes) {
+  // Minimums.
+  EXPECT_EQ("\x7f\x80\x80\x80\x01", EncodeUint32(7, 0x20007f));
+  EXPECT_EQ("\x3f\x80\x80\x80\x01", EncodeUint32(6, 0x20003f));
+  EXPECT_EQ("\x1f\x80\x80\x80\x01", EncodeUint32(5, 0x20001f));
+  EXPECT_EQ("\x0f\x80\x80\x80\x01", EncodeUint32(4, 0x20000f));
+  EXPECT_EQ("\x07\x80\x80\x80\x01", EncodeUint32(3, 0x200007));
+  EXPECT_EQ("\x03\x80\x80\x80\x01", EncodeUint32(2, 0x200003));
+  EXPECT_EQ("\x01\x80\x80\x80\x01", EncodeUint32(1, 0x200001));
+
+  // Maximums.
+  EXPECT_EQ("\x7f\xff\xff\xff\x7f", EncodeUint32(7, 0x1000007e));
+  EXPECT_EQ("\x3f\xff\xff\xff\x7f", EncodeUint32(6, 0x1000003e));
+  EXPECT_EQ("\x1f\xff\xff\xff\x7f", EncodeUint32(5, 0x1000001e));
+  EXPECT_EQ("\x0f\xff\xff\xff\x7f", EncodeUint32(4, 0x1000000e));
+  EXPECT_EQ("\x07\xff\xff\xff\x7f", EncodeUint32(3, 0x10000006));
+  EXPECT_EQ("\x03\xff\xff\xff\x7f", EncodeUint32(2, 0x10000002));
+  EXPECT_EQ("\x01\xff\xff\xff\x7f", EncodeUint32(1, 0x10000000));
+}
+
+TEST(HpackOutputStreamTest, SixByteIntegersOneToSevenBitPrefixes) {
+  // Minimums.
+  EXPECT_EQ("\x7f\x80\x80\x80\x80\x01", EncodeUint32(7, 0x1000007f));
+  EXPECT_EQ("\x3f\x80\x80\x80\x80\x01", EncodeUint32(6, 0x1000003f));
+  EXPECT_EQ("\x1f\x80\x80\x80\x80\x01", EncodeUint32(5, 0x1000001f));
+  EXPECT_EQ("\x0f\x80\x80\x80\x80\x01", EncodeUint32(4, 0x1000000f));
+  EXPECT_EQ("\x07\x80\x80\x80\x80\x01", EncodeUint32(3, 0x10000007));
+  EXPECT_EQ("\x03\x80\x80\x80\x80\x01", EncodeUint32(2, 0x10000003));
+  EXPECT_EQ("\x01\x80\x80\x80\x80\x01", EncodeUint32(1, 0x10000001));
+
+  // Maximums.
+  EXPECT_EQ("\x7f\x80\xff\xff\xff\x0f", EncodeUint32(7, 0xffffffff));
+  EXPECT_EQ("\x3f\xc0\xff\xff\xff\x0f", EncodeUint32(6, 0xffffffff));
+  EXPECT_EQ("\x1f\xe0\xff\xff\xff\x0f", EncodeUint32(5, 0xffffffff));
+  EXPECT_EQ("\x0f\xf0\xff\xff\xff\x0f", EncodeUint32(4, 0xffffffff));
+  EXPECT_EQ("\x07\xf8\xff\xff\xff\x0f", EncodeUint32(3, 0xffffffff));
+  EXPECT_EQ("\x03\xfc\xff\xff\xff\x0f", EncodeUint32(2, 0xffffffff));
+  EXPECT_EQ("\x01\xfe\xff\xff\xff\x0f", EncodeUint32(1, 0xffffffff));
+}
+
+// Test that encoding an integer with an N-bit prefix preserves the
+// upper (8-N) bits of the first byte.
+TEST(HpackOutputStreamTest, AppendUint32PreservesUpperBits) {
+  HpackOutputStream output_stream;
+  output_stream.AppendBits(0x7f, 7);
+  output_stream.AppendUint32(0x01);
+  SpdyString str;
+  output_stream.TakeString(&str);
+  EXPECT_EQ(SpdyString("\xff\x00", 2), str);
+}
+
+TEST(HpackOutputStreamTest, AppendBytes) {
+  HpackOutputStream output_stream;
+
+  output_stream.AppendBytes("buffer1");
+  output_stream.AppendBytes("buffer2");
+
+  SpdyString str;
+  output_stream.TakeString(&str);
+  EXPECT_EQ("buffer1buffer2", str);
+}
+
+TEST(HpackOutputStreamTest, BoundedTakeString) {
+  HpackOutputStream output_stream;
+
+  output_stream.AppendBytes("buffer12");
+  output_stream.AppendBytes("buffer456");
+
+  SpdyString str;
+  output_stream.BoundedTakeString(9, &str);
+  EXPECT_EQ("buffer12b", str);
+
+  output_stream.AppendBits(0x7f, 7);
+  output_stream.AppendUint32(0x11);
+  output_stream.BoundedTakeString(9, &str);
+  EXPECT_EQ("uffer456\xff", str);
+
+  output_stream.BoundedTakeString(9, &str);
+  EXPECT_EQ("\x10", str);
+}
+
+}  // namespace
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_round_trip_test.cc b/spdy/core/hpack/hpack_round_trip_test.cc
new file mode 100644
index 0000000..4b3a848
--- /dev/null
+++ b/spdy/core/hpack/hpack_round_trip_test.cc
@@ -0,0 +1,227 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cmath>
+#include <cstdint>
+#include <ctime>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+
+namespace spdy {
+namespace test {
+
+namespace {
+
+// Supports testing with the input split at every byte boundary.
+enum InputSizeParam { ALL_INPUT, ONE_BYTE, ZERO_THEN_ONE_BYTE };
+
+class HpackRoundTripTest : public ::testing::TestWithParam<InputSizeParam> {
+ protected:
+  HpackRoundTripTest() : encoder_(ObtainHpackHuffmanTable()), decoder_() {}
+
+  void SetUp() override {
+    // Use a small table size to tickle eviction handling.
+    encoder_.ApplyHeaderTableSizeSetting(256);
+    decoder_.ApplyHeaderTableSizeSetting(256);
+  }
+
+  bool RoundTrip(const SpdyHeaderBlock& header_set) {
+    SpdyString encoded;
+    encoder_.EncodeHeaderSet(header_set, &encoded);
+
+    bool success = true;
+    if (GetParam() == ALL_INPUT) {
+      // Pass all the input to the decoder at once.
+      success = decoder_.HandleControlFrameHeadersData(encoded.data(),
+                                                       encoded.size());
+    } else if (GetParam() == ONE_BYTE) {
+      // Pass the input to the decoder one byte at a time.
+      const char* data = encoded.data();
+      for (size_t ndx = 0; ndx < encoded.size() && success; ++ndx) {
+        success = decoder_.HandleControlFrameHeadersData(data + ndx, 1);
+      }
+    } else if (GetParam() == ZERO_THEN_ONE_BYTE) {
+      // Pass the input to the decoder one byte at a time, but before each
+      // byte pass an empty buffer.
+      const char* data = encoded.data();
+      for (size_t ndx = 0; ndx < encoded.size() && success; ++ndx) {
+        success = (decoder_.HandleControlFrameHeadersData(data + ndx, 0) &&
+                   decoder_.HandleControlFrameHeadersData(data + ndx, 1));
+      }
+    } else {
+      ADD_FAILURE() << "Unknown param: " << GetParam();
+    }
+
+    if (success) {
+      success = decoder_.HandleControlFrameHeadersComplete(nullptr);
+    }
+
+    EXPECT_EQ(header_set, decoder_.decoded_block());
+    return success;
+  }
+
+  size_t SampleExponential(size_t mean, size_t sanity_bound) {
+    return std::min<size_t>(-std::log(random_.RandDouble()) * mean,
+                            sanity_bound);
+  }
+
+  http2::test::Http2Random random_;
+  HpackEncoder encoder_;
+  HpackDecoderAdapter decoder_;
+};
+
+INSTANTIATE_TEST_CASE_P(Tests,
+                        HpackRoundTripTest,
+                        ::testing::Values(ALL_INPUT,
+                                          ONE_BYTE,
+                                          ZERO_THEN_ONE_BYTE));
+
+TEST_P(HpackRoundTripTest, ResponseFixtures) {
+  {
+    SpdyHeaderBlock headers;
+    headers[":status"] = "302";
+    headers["cache-control"] = "private";
+    headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
+    headers["location"] = "https://www.example.com";
+    EXPECT_TRUE(RoundTrip(headers));
+  }
+  {
+    SpdyHeaderBlock headers;
+    headers[":status"] = "200";
+    headers["cache-control"] = "private";
+    headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
+    headers["location"] = "https://www.example.com";
+    EXPECT_TRUE(RoundTrip(headers));
+  }
+  {
+    SpdyHeaderBlock headers;
+    headers[":status"] = "200";
+    headers["cache-control"] = "private";
+    headers["content-encoding"] = "gzip";
+    headers["date"] = "Mon, 21 Oct 2013 20:13:22 GMT";
+    headers["location"] = "https://www.example.com";
+    headers["set-cookie"] =
+        "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
+        " max-age=3600; version=1";
+    headers["multivalue"] = SpdyString("foo\0bar", 7);
+    EXPECT_TRUE(RoundTrip(headers));
+  }
+}
+
+TEST_P(HpackRoundTripTest, RequestFixtures) {
+  {
+    SpdyHeaderBlock headers;
+    headers[":authority"] = "www.example.com";
+    headers[":method"] = "GET";
+    headers[":path"] = "/";
+    headers[":scheme"] = "http";
+    headers["cookie"] = "baz=bing; foo=bar";
+    EXPECT_TRUE(RoundTrip(headers));
+  }
+  {
+    SpdyHeaderBlock headers;
+    headers[":authority"] = "www.example.com";
+    headers[":method"] = "GET";
+    headers[":path"] = "/";
+    headers[":scheme"] = "http";
+    headers["cache-control"] = "no-cache";
+    headers["cookie"] = "foo=bar; spam=eggs";
+    EXPECT_TRUE(RoundTrip(headers));
+  }
+  {
+    SpdyHeaderBlock headers;
+    headers[":authority"] = "www.example.com";
+    headers[":method"] = "GET";
+    headers[":path"] = "/index.html";
+    headers[":scheme"] = "https";
+    headers["custom-key"] = "custom-value";
+    headers["cookie"] = "baz=bing; fizzle=fazzle; garbage";
+    headers["multivalue"] = SpdyString("foo\0bar", 7);
+    EXPECT_TRUE(RoundTrip(headers));
+  }
+}
+
+TEST_P(HpackRoundTripTest, RandomizedExamples) {
+  // Grow vectors of names & values, which are seeded with fixtures and then
+  // expanded with dynamically generated data. Samples are taken using the
+  // exponential distribution.
+  std::vector<SpdyString> pseudo_header_names, random_header_names;
+  pseudo_header_names.push_back(":authority");
+  pseudo_header_names.push_back(":path");
+  pseudo_header_names.push_back(":status");
+
+  // TODO(jgraettinger): Enable "cookie" as a name fixture. Crumbs may be
+  // reconstructed in any order, which breaks the simple validation used here.
+
+  std::vector<SpdyString> values;
+  values.push_back("/");
+  values.push_back("/index.html");
+  values.push_back("200");
+  values.push_back("404");
+  values.push_back("");
+  values.push_back("baz=bing; foo=bar; garbage");
+  values.push_back("baz=bing; fizzle=fazzle; garbage");
+
+  for (size_t i = 0; i != 2000; ++i) {
+    SpdyHeaderBlock headers;
+
+    // Choose a random number of headers to add, and of these a random subset
+    // will be HTTP/2 pseudo headers.
+    size_t header_count = 1 + SampleExponential(7, 50);
+    size_t pseudo_header_count =
+        std::min(header_count, 1 + SampleExponential(7, 50));
+    EXPECT_LE(pseudo_header_count, header_count);
+    for (size_t j = 0; j != header_count; ++j) {
+      SpdyString name, value;
+      // Pseudo headers must be added before regular headers.
+      if (j < pseudo_header_count) {
+        // Choose one of the defined pseudo headers at random.
+        size_t name_index = random_.Uniform(pseudo_header_names.size());
+        name = pseudo_header_names[name_index];
+      } else {
+        // Randomly reuse an existing header name, or generate a new one.
+        size_t name_index = SampleExponential(20, 200);
+        if (name_index >= random_header_names.size()) {
+          name = random_.RandString(1 + SampleExponential(5, 30));
+          // A regular header cannot begin with the pseudo header prefix ":".
+          if (name[0] == ':') {
+            name[0] = 'x';
+          }
+          random_header_names.push_back(name);
+        } else {
+          name = random_header_names[name_index];
+        }
+      }
+
+      // Randomly reuse an existing value, or generate a new one.
+      size_t value_index = SampleExponential(20, 200);
+      if (value_index >= values.size()) {
+        SpdyString newvalue = random_.RandString(1 + SampleExponential(15, 75));
+        // Currently order is not preserved in the encoder.  In particular,
+        // when a value is decomposed at \0 delimiters, its parts might get
+        // encoded out of order if some but not all of them already exist in
+        // the header table.  For now, avoid \0 bytes in values.
+        std::replace(newvalue.begin(), newvalue.end(), '\x00', '\x01');
+        values.push_back(newvalue);
+        value = values.back();
+      } else {
+        value = values[value_index];
+      }
+      headers[name] = value;
+    }
+    EXPECT_TRUE(RoundTrip(headers));
+  }
+}
+
+}  // namespace
+
+}  // namespace test
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_static_table.cc b/spdy/core/hpack/hpack_static_table.cc
new file mode 100644
index 0000000..14e282e
--- /dev/null
+++ b/spdy/core/hpack/hpack_static_table.cc
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+HpackStaticTable::HpackStaticTable() = default;
+
+HpackStaticTable::~HpackStaticTable() = default;
+
+void HpackStaticTable::Initialize(const HpackStaticEntry* static_entry_table,
+                                  size_t static_entry_count) {
+  CHECK(!IsInitialized());
+
+  int total_insertions = 0;
+  for (const HpackStaticEntry* it = static_entry_table;
+       it != static_entry_table + static_entry_count; ++it) {
+    static_entries_.push_back(
+        HpackEntry(SpdyStringPiece(it->name, it->name_len),
+                   SpdyStringPiece(it->value, it->value_len),
+                   true,  // is_static
+                   total_insertions));
+    HpackEntry* entry = &static_entries_.back();
+    CHECK(static_index_.insert(entry).second);
+    // Multiple static entries may have the same name, so inserts may fail.
+    static_name_index_.insert(std::make_pair(entry->name(), entry));
+
+    ++total_insertions;
+  }
+}
+
+bool HpackStaticTable::IsInitialized() const {
+  return !static_entries_.empty();
+}
+
+size_t HpackStaticTable::EstimateMemoryUsage() const {
+  return SpdyEstimateMemoryUsage(static_entries_) +
+         SpdyEstimateMemoryUsage(static_index_) +
+         SpdyEstimateMemoryUsage(static_name_index_);
+}
+
+}  // namespace spdy
diff --git a/spdy/core/hpack/hpack_static_table.h b/spdy/core/hpack/hpack_static_table.h
new file mode 100644
index 0000000..f354a12
--- /dev/null
+++ b/spdy/core/hpack/hpack_static_table.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_STATIC_TABLE_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_STATIC_TABLE_H_
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+
+namespace spdy {
+
+struct HpackStaticEntry;
+
+// HpackStaticTable provides |static_entries_| and |static_index_| for HPACK
+// encoding and decoding contexts.  Once initialized, an instance is read only
+// and may be accessed only through its const interface.  Such an instance may
+// be shared accross multiple HPACK contexts.
+class SPDY_EXPORT_PRIVATE HpackStaticTable {
+ public:
+  HpackStaticTable();
+  ~HpackStaticTable();
+
+  // Prepares HpackStaticTable by filling up static_entries_ and static_index_
+  // from an array of struct HpackStaticEntry.  Must be called exactly once.
+  void Initialize(const HpackStaticEntry* static_entry_table,
+                  size_t static_entry_count);
+
+  // Returns whether Initialize() has been called.
+  bool IsInitialized() const;
+
+  // Accessors.
+  const HpackHeaderTable::EntryTable& GetStaticEntries() const {
+    return static_entries_;
+  }
+  const HpackHeaderTable::UnorderedEntrySet& GetStaticIndex() const {
+    return static_index_;
+  }
+  const HpackHeaderTable::NameToEntryMap& GetStaticNameIndex() const {
+    return static_name_index_;
+  }
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+ private:
+  HpackHeaderTable::EntryTable static_entries_;
+  HpackHeaderTable::UnorderedEntrySet static_index_;
+  HpackHeaderTable::NameToEntryMap static_name_index_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_HPACK_HPACK_STATIC_TABLE_H_
diff --git a/spdy/core/hpack/hpack_static_table_test.cc b/spdy/core/hpack/hpack_static_table_test.cc
new file mode 100644
index 0000000..4550be0
--- /dev/null
+++ b/spdy/core/hpack/hpack_static_table_test.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h"
+
+#include <set>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+
+namespace {
+
+class HpackStaticTableTest : public ::testing::Test {
+ protected:
+  HpackStaticTableTest() : table_() {}
+
+  HpackStaticTable table_;
+};
+
+// Check that an initialized instance has the right number of entries.
+TEST_F(HpackStaticTableTest, Initialize) {
+  EXPECT_FALSE(table_.IsInitialized());
+  table_.Initialize(HpackStaticTableVector().data(),
+                    HpackStaticTableVector().size());
+  EXPECT_TRUE(table_.IsInitialized());
+
+  HpackHeaderTable::EntryTable static_entries = table_.GetStaticEntries();
+  EXPECT_EQ(HpackStaticTableVector().size(), static_entries.size());
+
+  HpackHeaderTable::UnorderedEntrySet static_index = table_.GetStaticIndex();
+  EXPECT_EQ(HpackStaticTableVector().size(), static_index.size());
+
+  HpackHeaderTable::NameToEntryMap static_name_index =
+      table_.GetStaticNameIndex();
+  std::set<SpdyStringPiece> names;
+  for (auto* entry : static_index) {
+    names.insert(entry->name());
+  }
+  EXPECT_EQ(names.size(), static_name_index.size());
+}
+
+// Test that ObtainHpackStaticTable returns the same instance every time.
+TEST_F(HpackStaticTableTest, IsSingleton) {
+  const HpackStaticTable* static_table_one = &ObtainHpackStaticTable();
+  const HpackStaticTable* static_table_two = &ObtainHpackStaticTable();
+  EXPECT_EQ(static_table_one, static_table_two);
+}
+
+}  // namespace
+
+}  // namespace test
+
+}  // namespace spdy
diff --git a/spdy/core/http2_frame_decoder_adapter.cc b/spdy/core/http2_frame_decoder_adapter.cc
new file mode 100644
index 0000000..c4e728d
--- /dev/null
+++ b/spdy/core/http2_frame_decoder_adapter.cc
@@ -0,0 +1,1022 @@
+// 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/spdy/core/http2_frame_decoder_adapter.h"
+
+// Logging policy: If an error in the input is detected, VLOG(n) is used so that
+// the option exists to debug the situation. Otherwise, this code mostly uses
+// DVLOG so that the logging does not slow down production code when things are
+// working OK.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <cstring>
+#include <utility>
+
+#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.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_string.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_flags.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+using ::spdy::ExtensionVisitorInterface;
+using ::spdy::HpackDecoderAdapter;
+using ::spdy::HpackHeaderTable;
+using ::spdy::ParseErrorCode;
+using ::spdy::ParseFrameType;
+using ::spdy::SpdyAltSvcWireFormat;
+using ::spdy::SpdyErrorCode;
+using ::spdy::SpdyEstimateMemoryUsage;
+using ::spdy::SpdyFramerDebugVisitorInterface;
+using ::spdy::SpdyFramerVisitorInterface;
+using ::spdy::SpdyFrameType;
+using ::spdy::SpdyHeadersHandlerInterface;
+using ::spdy::SpdyKnownSettingsId;
+using ::spdy::SpdyMakeUnique;
+using ::spdy::SpdySettingsId;
+
+namespace http2 {
+namespace {
+
+const bool kHasPriorityFields = true;
+const bool kNotHasPriorityFields = false;
+
+bool IsPaddable(Http2FrameType type) {
+  return type == Http2FrameType::DATA || type == Http2FrameType::HEADERS ||
+         type == Http2FrameType::PUSH_PROMISE;
+}
+
+SpdyFrameType ToSpdyFrameType(Http2FrameType type) {
+  return ParseFrameType(static_cast<uint8_t>(type));
+}
+
+uint64_t ToSpdyPingId(const Http2PingFields& ping) {
+  uint64_t v;
+  std::memcpy(&v, ping.opaque_bytes, Http2PingFields::EncodedSize());
+  return spdy::SpdyNetToHost64(v);
+}
+
+// Overwrites the fields of the header with invalid values, for the purpose
+// of identifying reading of unset fields. Only takes effect for debug builds.
+// In Address Sanatizer builds, it also marks the fields as un-readable.
+void CorruptFrameHeader(Http2FrameHeader* header) {
+#ifndef NDEBUG
+  // Beyond a valid payload length, which is 2^24 - 1.
+  header->payload_length = 0x1010dead;
+  // An unsupported frame type.
+  header->type = Http2FrameType(0x80);
+  DCHECK(!IsSupportedHttp2FrameType(header->type));
+  // Frame flag bits that aren't used by any supported frame type.
+  header->flags = Http2FrameFlag(0xd2);
+  // A stream id with the reserved high-bit (R in the RFC) set.
+  // 2129510127 when the high-bit is cleared.
+  header->stream_id = 0xfeedbeef;
+#endif
+}
+
+}  // namespace
+
+const char* Http2DecoderAdapter::StateToString(int state) {
+  switch (state) {
+    case SPDY_ERROR:
+      return "ERROR";
+    case SPDY_FRAME_COMPLETE:
+      return "FRAME_COMPLETE";
+    case SPDY_READY_FOR_FRAME:
+      return "READY_FOR_FRAME";
+    case SPDY_READING_COMMON_HEADER:
+      return "READING_COMMON_HEADER";
+    case SPDY_CONTROL_FRAME_PAYLOAD:
+      return "CONTROL_FRAME_PAYLOAD";
+    case SPDY_READ_DATA_FRAME_PADDING_LENGTH:
+      return "SPDY_READ_DATA_FRAME_PADDING_LENGTH";
+    case SPDY_CONSUME_PADDING:
+      return "SPDY_CONSUME_PADDING";
+    case SPDY_IGNORE_REMAINING_PAYLOAD:
+      return "IGNORE_REMAINING_PAYLOAD";
+    case SPDY_FORWARD_STREAM_FRAME:
+      return "FORWARD_STREAM_FRAME";
+    case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK:
+      return "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK";
+    case SPDY_CONTROL_FRAME_HEADER_BLOCK:
+      return "SPDY_CONTROL_FRAME_HEADER_BLOCK";
+    case SPDY_GOAWAY_FRAME_PAYLOAD:
+      return "SPDY_GOAWAY_FRAME_PAYLOAD";
+    case SPDY_SETTINGS_FRAME_HEADER:
+      return "SPDY_SETTINGS_FRAME_HEADER";
+    case SPDY_SETTINGS_FRAME_PAYLOAD:
+      return "SPDY_SETTINGS_FRAME_PAYLOAD";
+    case SPDY_ALTSVC_FRAME_PAYLOAD:
+      return "SPDY_ALTSVC_FRAME_PAYLOAD";
+  }
+  return "UNKNOWN_STATE";
+}
+
+const char* Http2DecoderAdapter::SpdyFramerErrorToString(
+    SpdyFramerError spdy_framer_error) {
+  switch (spdy_framer_error) {
+    case SPDY_NO_ERROR:
+      return "NO_ERROR";
+    case SPDY_INVALID_STREAM_ID:
+      return "INVALID_STREAM_ID";
+    case SPDY_INVALID_CONTROL_FRAME:
+      return "INVALID_CONTROL_FRAME";
+    case SPDY_CONTROL_PAYLOAD_TOO_LARGE:
+      return "CONTROL_PAYLOAD_TOO_LARGE";
+    case SPDY_ZLIB_INIT_FAILURE:
+      return "ZLIB_INIT_FAILURE";
+    case SPDY_UNSUPPORTED_VERSION:
+      return "UNSUPPORTED_VERSION";
+    case SPDY_DECOMPRESS_FAILURE:
+      return "DECOMPRESS_FAILURE";
+    case SPDY_COMPRESS_FAILURE:
+      return "COMPRESS_FAILURE";
+    case SPDY_GOAWAY_FRAME_CORRUPT:
+      return "GOAWAY_FRAME_CORRUPT";
+    case SPDY_RST_STREAM_FRAME_CORRUPT:
+      return "RST_STREAM_FRAME_CORRUPT";
+    case SPDY_INVALID_PADDING:
+      return "INVALID_PADDING";
+    case SPDY_INVALID_DATA_FRAME_FLAGS:
+      return "INVALID_DATA_FRAME_FLAGS";
+    case SPDY_INVALID_CONTROL_FRAME_FLAGS:
+      return "INVALID_CONTROL_FRAME_FLAGS";
+    case SPDY_UNEXPECTED_FRAME:
+      return "UNEXPECTED_FRAME";
+    case SPDY_INTERNAL_FRAMER_ERROR:
+      return "INTERNAL_FRAMER_ERROR";
+    case SPDY_INVALID_CONTROL_FRAME_SIZE:
+      return "INVALID_CONTROL_FRAME_SIZE";
+    case SPDY_OVERSIZED_PAYLOAD:
+      return "OVERSIZED_PAYLOAD";
+    case LAST_ERROR:
+      return "UNKNOWN_ERROR";
+  }
+  return "UNKNOWN_ERROR";
+}
+
+Http2DecoderAdapter::Http2DecoderAdapter() {
+  DVLOG(1) << "Http2DecoderAdapter ctor";
+  ResetInternal();
+}
+
+Http2DecoderAdapter::~Http2DecoderAdapter() = default;
+
+void Http2DecoderAdapter::set_visitor(SpdyFramerVisitorInterface* visitor) {
+  visitor_ = visitor;
+}
+
+void Http2DecoderAdapter::set_debug_visitor(
+    SpdyFramerDebugVisitorInterface* debug_visitor) {
+  debug_visitor_ = debug_visitor;
+}
+
+void Http2DecoderAdapter::set_process_single_input_frame(bool v) {
+  process_single_input_frame_ = v;
+}
+
+void Http2DecoderAdapter::set_extension_visitor(
+    ExtensionVisitorInterface* visitor) {
+  extension_ = visitor;
+}
+
+// Passes the call on to the HPACK decoder.
+void Http2DecoderAdapter::SetDecoderHeaderTableDebugVisitor(
+    std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) {
+  GetHpackDecoder()->SetHeaderTableDebugVisitor(std::move(visitor));
+}
+
+size_t Http2DecoderAdapter::ProcessInput(const char* data, size_t len) {
+  size_t limit = recv_frame_size_limit_;
+  frame_decoder_->set_maximum_payload_size(limit);
+
+  size_t total_processed = 0;
+  while (len > 0 && spdy_state_ != SPDY_ERROR) {
+    // Process one at a time so that we update the adapter's internal
+    // state appropriately.
+    const size_t processed = ProcessInputFrame(data, len);
+
+    // We had some data, and weren't in an error state, so should have
+    // processed/consumed at least one byte of it, even if we then ended up
+    // in an error state.
+    DCHECK(processed > 0) << "processed=" << processed
+                          << "   spdy_state_=" << spdy_state_
+                          << "   spdy_framer_error_=" << spdy_framer_error_;
+
+    data += processed;
+    len -= processed;
+    total_processed += processed;
+    if (process_single_input_frame() || processed == 0) {
+      break;
+    }
+  }
+  return total_processed;
+}
+
+void Http2DecoderAdapter::Reset() {
+  ResetInternal();
+}
+
+Http2DecoderAdapter::SpdyState Http2DecoderAdapter::state() const {
+  return spdy_state_;
+}
+
+Http2DecoderAdapter::SpdyFramerError Http2DecoderAdapter::spdy_framer_error()
+    const {
+  return spdy_framer_error_;
+}
+
+bool Http2DecoderAdapter::probable_http_response() const {
+  return latched_probable_http_response_;
+}
+
+size_t Http2DecoderAdapter::EstimateMemoryUsage() const {
+  // Skip |frame_decoder_|, |frame_header_| and |hpack_first_frame_header_| as
+  // they don't allocate.
+  return SpdyEstimateMemoryUsage(alt_svc_origin_) +
+         SpdyEstimateMemoryUsage(alt_svc_value_);
+}
+
+// ===========================================================================
+// Implementations of the methods declared by Http2FrameDecoderListener.
+
+// Called once the common frame header has been decoded for any frame.
+// This function is largely based on Http2DecoderAdapter::ValidateFrameHeader
+// and some parts of Http2DecoderAdapter::ProcessCommonHeader.
+bool Http2DecoderAdapter::OnFrameHeader(const Http2FrameHeader& header) {
+  DVLOG(1) << "OnFrameHeader: " << header;
+  decoded_frame_header_ = true;
+  if (!latched_probable_http_response_) {
+    latched_probable_http_response_ = header.IsProbableHttpResponse();
+  }
+  const uint8_t raw_frame_type = static_cast<uint8_t>(header.type);
+  visitor()->OnCommonHeader(header.stream_id, header.payload_length,
+                            raw_frame_type, header.flags);
+  if (has_expected_frame_type_ && header.type != expected_frame_type_) {
+    // Report an unexpected frame error and close the connection if we
+    // expect a known frame type (probably CONTINUATION) and receive an
+    // unknown frame.
+    VLOG(1) << "The framer was expecting to receive a " << expected_frame_type_
+            << " frame, but instead received an unknown frame of type "
+            << header.type;
+    SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME);
+    return false;
+  }
+  if (!IsSupportedHttp2FrameType(header.type)) {
+    if (extension_ != nullptr) {
+      // Unknown frames will be passed to the registered extension.
+      return true;
+    }
+    // In HTTP2 we ignore unknown frame types for extensibility, as long as
+    // the rest of the control frame header is valid.
+    // We rely on the visitor to check validity of stream_id.
+    bool valid_stream =
+        visitor()->OnUnknownFrame(header.stream_id, raw_frame_type);
+    if (!valid_stream) {
+      // Report an invalid frame error if the stream_id is not valid.
+      VLOG(1) << "Unknown control frame type " << header.type
+              << " received on invalid stream " << header.stream_id;
+      SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME);
+      return false;
+    } else {
+      DVLOG(1) << "Ignoring unknown frame type " << header.type;
+      return true;
+    }
+  }
+
+  SpdyFrameType frame_type = ToSpdyFrameType(header.type);
+  if (!IsValidHTTP2FrameStreamId(header.stream_id, frame_type)) {
+    VLOG(1) << "The framer received an invalid streamID of " << header.stream_id
+            << " for a frame of type " << header.type;
+    SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_STREAM_ID);
+    return false;
+  }
+
+  if (has_expected_frame_type_ && header.type != expected_frame_type_) {
+    VLOG(1) << "Expected frame type " << expected_frame_type_ << ", not "
+            << header.type;
+    SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME);
+    return false;
+  }
+
+  if (!has_expected_frame_type_ &&
+      header.type == Http2FrameType::CONTINUATION) {
+    VLOG(1) << "Got CONTINUATION frame when not expected.";
+    SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME);
+    return false;
+  }
+
+  if (header.type == Http2FrameType::DATA) {
+    // For some reason SpdyFramer still rejects invalid DATA frame flags.
+    uint8_t valid_flags = Http2FrameFlag::PADDED | Http2FrameFlag::END_STREAM;
+    if (header.HasAnyFlags(~valid_flags)) {
+      SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_DATA_FRAME_FLAGS);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void Http2DecoderAdapter::OnDataStart(const Http2FrameHeader& header) {
+  DVLOG(1) << "OnDataStart: " << header;
+
+  if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+    frame_header_ = header;
+    has_frame_header_ = true;
+    visitor()->OnDataFrameHeader(header.stream_id, header.payload_length,
+                                 header.IsEndStream());
+  }
+}
+
+void Http2DecoderAdapter::OnDataPayload(const char* data, size_t len) {
+  DVLOG(1) << "OnDataPayload: len=" << len;
+  DCHECK(has_frame_header_);
+  DCHECK_EQ(frame_header_.type, Http2FrameType::DATA);
+  visitor()->OnStreamFrameData(frame_header().stream_id, data, len);
+}
+
+void Http2DecoderAdapter::OnDataEnd() {
+  DVLOG(1) << "OnDataEnd";
+  DCHECK(has_frame_header_);
+  DCHECK_EQ(frame_header_.type, Http2FrameType::DATA);
+  if (frame_header().IsEndStream()) {
+    visitor()->OnStreamEnd(frame_header().stream_id);
+  }
+  opt_pad_length_.reset();
+}
+
+void Http2DecoderAdapter::OnHeadersStart(const Http2FrameHeader& header) {
+  DVLOG(1) << "OnHeadersStart: " << header;
+  if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+    frame_header_ = header;
+    has_frame_header_ = true;
+    if (header.HasPriority()) {
+      // Once we've got the priority fields, then we can report the arrival
+      // of this HEADERS frame.
+      on_headers_called_ = false;
+      return;
+    }
+    on_headers_called_ = true;
+    ReportReceiveCompressedFrame(header);
+    visitor()->OnHeaders(header.stream_id, kNotHasPriorityFields,
+                         0,      // priority
+                         0,      // parent_stream_id
+                         false,  // exclusive
+                         header.IsEndStream(), header.IsEndHeaders());
+    CommonStartHpackBlock();
+  }
+}
+
+void Http2DecoderAdapter::OnHeadersPriority(
+    const Http2PriorityFields& priority) {
+  DVLOG(1) << "OnHeadersPriority: " << priority;
+  DCHECK(has_frame_header_);
+  DCHECK_EQ(frame_type(), Http2FrameType::HEADERS) << frame_header_;
+  DCHECK(frame_header_.HasPriority());
+  DCHECK(!on_headers_called_);
+  on_headers_called_ = true;
+  ReportReceiveCompressedFrame(frame_header_);
+  visitor()->OnHeaders(frame_header_.stream_id, kHasPriorityFields,
+                       priority.weight, priority.stream_dependency,
+                       priority.is_exclusive, frame_header_.IsEndStream(),
+                       frame_header_.IsEndHeaders());
+  CommonStartHpackBlock();
+}
+
+void Http2DecoderAdapter::OnHpackFragment(const char* data, size_t len) {
+  DVLOG(1) << "OnHpackFragment: len=" << len;
+  on_hpack_fragment_called_ = true;
+  if (!GetHpackDecoder()->HandleControlFrameHeadersData(data, len)) {
+    SetSpdyErrorAndNotify(SpdyFramerError::SPDY_DECOMPRESS_FAILURE);
+    return;
+  }
+}
+
+void Http2DecoderAdapter::OnHeadersEnd() {
+  DVLOG(1) << "OnHeadersEnd";
+  CommonHpackFragmentEnd();
+  opt_pad_length_.reset();
+}
+
+void Http2DecoderAdapter::OnPriorityFrame(const Http2FrameHeader& header,
+                                          const Http2PriorityFields& priority) {
+  DVLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority;
+  if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+    visitor()->OnPriority(header.stream_id, priority.stream_dependency,
+                          priority.weight, priority.is_exclusive);
+  }
+}
+
+void Http2DecoderAdapter::OnContinuationStart(const Http2FrameHeader& header) {
+  DVLOG(1) << "OnContinuationStart: " << header;
+  if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+    DCHECK(has_hpack_first_frame_header_);
+    if (header.stream_id != hpack_first_frame_header_.stream_id) {
+      SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME);
+      return;
+    }
+    frame_header_ = header;
+    has_frame_header_ = true;
+    ReportReceiveCompressedFrame(header);
+    visitor()->OnContinuation(header.stream_id, header.IsEndHeaders());
+  }
+}
+
+void Http2DecoderAdapter::OnContinuationEnd() {
+  DVLOG(1) << "OnContinuationEnd";
+  CommonHpackFragmentEnd();
+}
+
+void Http2DecoderAdapter::OnPadLength(size_t trailing_length) {
+  DVLOG(1) << "OnPadLength: " << trailing_length;
+  opt_pad_length_ = trailing_length;
+  DCHECK_LT(trailing_length, 256u);
+  if (frame_header_.type == Http2FrameType::DATA) {
+    visitor()->OnStreamPadLength(stream_id(), trailing_length);
+  }
+}
+
+void Http2DecoderAdapter::OnPadding(const char* padding,
+                                    size_t skipped_length) {
+  DVLOG(1) << "OnPadding: " << skipped_length;
+  if (frame_header_.type == Http2FrameType::DATA) {
+    visitor()->OnStreamPadding(stream_id(), skipped_length);
+  } else {
+    MaybeAnnounceEmptyFirstHpackFragment();
+  }
+}
+
+void Http2DecoderAdapter::OnRstStream(const Http2FrameHeader& header,
+                                      Http2ErrorCode http2_error_code) {
+  DVLOG(1) << "OnRstStream: " << header << "; code=" << http2_error_code;
+  if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+    SpdyErrorCode error_code =
+        ParseErrorCode(static_cast<uint32_t>(http2_error_code));
+    visitor()->OnRstStream(header.stream_id, error_code);
+  }
+}
+
+void Http2DecoderAdapter::OnSettingsStart(const Http2FrameHeader& header) {
+  DVLOG(1) << "OnSettingsStart: " << header;
+  if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) {
+    frame_header_ = header;
+    has_frame_header_ = true;
+    visitor()->OnSettings();
+  }
+}
+
+void Http2DecoderAdapter::OnSetting(const Http2SettingFields& setting_fields) {
+  DVLOG(1) << "OnSetting: " << setting_fields;
+  const auto parameter = static_cast<SpdySettingsId>(setting_fields.parameter);
+  visitor()->OnSetting(parameter, setting_fields.value);
+  if (extension_ != nullptr) {
+    extension_->OnSetting(parameter, setting_fields.value);
+  }
+}
+
+void Http2DecoderAdapter::OnSettingsEnd() {
+  DVLOG(1) << "OnSettingsEnd";
+  visitor()->OnSettingsEnd();
+}
+
+void Http2DecoderAdapter::OnSettingsAck(const Http2FrameHeader& header) {
+  DVLOG(1) << "OnSettingsAck: " << header;
+  if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) {
+    visitor()->OnSettingsAck();
+  }
+}
+
+void Http2DecoderAdapter::OnPushPromiseStart(
+    const Http2FrameHeader& header,
+    const Http2PushPromiseFields& promise,
+    size_t total_padding_length) {
+  DVLOG(1) << "OnPushPromiseStart: " << header << "; promise: " << promise
+           << "; total_padding_length: " << total_padding_length;
+  if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+    if (promise.promised_stream_id == 0) {
+      SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME);
+      return;
+    }
+    frame_header_ = header;
+    has_frame_header_ = true;
+    ReportReceiveCompressedFrame(header);
+    visitor()->OnPushPromise(header.stream_id, promise.promised_stream_id,
+                             header.IsEndHeaders());
+    CommonStartHpackBlock();
+  }
+}
+
+void Http2DecoderAdapter::OnPushPromiseEnd() {
+  DVLOG(1) << "OnPushPromiseEnd";
+  CommonHpackFragmentEnd();
+  opt_pad_length_.reset();
+}
+
+void Http2DecoderAdapter::OnPing(const Http2FrameHeader& header,
+                                 const Http2PingFields& ping) {
+  DVLOG(1) << "OnPing: " << header << "; ping: " << ping;
+  if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) {
+    visitor()->OnPing(ToSpdyPingId(ping), false);
+  }
+}
+
+void Http2DecoderAdapter::OnPingAck(const Http2FrameHeader& header,
+                                    const Http2PingFields& ping) {
+  DVLOG(1) << "OnPingAck: " << header << "; ping: " << ping;
+  if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) {
+    visitor()->OnPing(ToSpdyPingId(ping), true);
+  }
+}
+
+void Http2DecoderAdapter::OnGoAwayStart(const Http2FrameHeader& header,
+                                        const Http2GoAwayFields& goaway) {
+  DVLOG(1) << "OnGoAwayStart: " << header << "; goaway: " << goaway;
+  if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) {
+    frame_header_ = header;
+    has_frame_header_ = true;
+    SpdyErrorCode error_code =
+        ParseErrorCode(static_cast<uint32_t>(goaway.error_code));
+    visitor()->OnGoAway(goaway.last_stream_id, error_code);
+  }
+}
+
+void Http2DecoderAdapter::OnGoAwayOpaqueData(const char* data, size_t len) {
+  DVLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+  visitor()->OnGoAwayFrameData(data, len);
+}
+
+void Http2DecoderAdapter::OnGoAwayEnd() {
+  DVLOG(1) << "OnGoAwayEnd";
+  visitor()->OnGoAwayFrameData(nullptr, 0);
+}
+
+void Http2DecoderAdapter::OnWindowUpdate(const Http2FrameHeader& header,
+                                         uint32_t increment) {
+  DVLOG(1) << "OnWindowUpdate: " << header << "; increment=" << increment;
+  if (IsOkToStartFrame(header)) {
+    visitor()->OnWindowUpdate(header.stream_id, increment);
+  }
+}
+
+// Per RFC7838, an ALTSVC frame on stream 0 with origin_length == 0, or one on
+// a stream other than stream 0 with origin_length != 0 MUST be ignored.  All
+// frames are decoded by Http2DecoderAdapter, and it is left to the consumer
+// (listener) to implement this behavior.
+void Http2DecoderAdapter::OnAltSvcStart(const Http2FrameHeader& header,
+                                        size_t origin_length,
+                                        size_t value_length) {
+  DVLOG(1) << "OnAltSvcStart: " << header
+           << "; origin_length: " << origin_length
+           << "; value_length: " << value_length;
+  if (!IsOkToStartFrame(header)) {
+    return;
+  }
+  frame_header_ = header;
+  has_frame_header_ = true;
+  alt_svc_origin_.clear();
+  alt_svc_value_.clear();
+}
+
+void Http2DecoderAdapter::OnAltSvcOriginData(const char* data, size_t len) {
+  DVLOG(1) << "OnAltSvcOriginData: len=" << len;
+  alt_svc_origin_.append(data, len);
+}
+
+// Called when decoding the Alt-Svc-Field-Value of an ALTSVC;
+// the field is uninterpreted.
+void Http2DecoderAdapter::OnAltSvcValueData(const char* data, size_t len) {
+  DVLOG(1) << "OnAltSvcValueData: len=" << len;
+  alt_svc_value_.append(data, len);
+}
+
+void Http2DecoderAdapter::OnAltSvcEnd() {
+  DVLOG(1) << "OnAltSvcEnd: origin.size(): " << alt_svc_origin_.size()
+           << "; value.size(): " << alt_svc_value_.size();
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+  if (!SpdyAltSvcWireFormat::ParseHeaderFieldValue(alt_svc_value_,
+                                                   &altsvc_vector)) {
+    DLOG(ERROR) << "SpdyAltSvcWireFormat::ParseHeaderFieldValue failed.";
+    SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME);
+    return;
+  }
+  visitor()->OnAltSvc(frame_header_.stream_id, alt_svc_origin_, altsvc_vector);
+  // We assume that ALTSVC frames are rare, so get rid of the storage.
+  alt_svc_origin_.clear();
+  alt_svc_origin_.shrink_to_fit();
+  alt_svc_value_.clear();
+  alt_svc_value_.shrink_to_fit();
+}
+
+// Except for BLOCKED frames, all other unknown frames are either dropped or
+// passed to a registered extension.
+void Http2DecoderAdapter::OnUnknownStart(const Http2FrameHeader& header) {
+  DVLOG(1) << "OnUnknownStart: " << header;
+  if (IsOkToStartFrame(header)) {
+    if (extension_ != nullptr) {
+      const uint8_t type = static_cast<uint8_t>(header.type);
+      const uint8_t flags = static_cast<uint8_t>(header.flags);
+      handling_extension_payload_ = extension_->OnFrameHeader(
+          header.stream_id, header.payload_length, type, flags);
+    }
+  }
+}
+
+void Http2DecoderAdapter::OnUnknownPayload(const char* data, size_t len) {
+  if (handling_extension_payload_) {
+    extension_->OnFramePayload(data, len);
+  } else {
+    DVLOG(1) << "OnUnknownPayload: len=" << len;
+  }
+}
+
+void Http2DecoderAdapter::OnUnknownEnd() {
+  DVLOG(1) << "OnUnknownEnd";
+  handling_extension_payload_ = false;
+}
+
+void Http2DecoderAdapter::OnPaddingTooLong(const Http2FrameHeader& header,
+                                           size_t missing_length) {
+  DVLOG(1) << "OnPaddingTooLong: " << header
+           << "; missing_length: " << missing_length;
+  if (header.type == Http2FrameType::DATA) {
+    if (header.payload_length == 0) {
+      DCHECK_EQ(1u, missing_length);
+      SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_DATA_FRAME_FLAGS);
+      return;
+    }
+    visitor()->OnStreamPadding(header.stream_id, 1);
+  }
+  SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_PADDING);
+}
+
+void Http2DecoderAdapter::OnFrameSizeError(const Http2FrameHeader& header) {
+  DVLOG(1) << "OnFrameSizeError: " << header;
+  size_t recv_limit = recv_frame_size_limit_;
+  if (header.payload_length > recv_limit) {
+    SetSpdyErrorAndNotify(SpdyFramerError::SPDY_OVERSIZED_PAYLOAD);
+    return;
+  }
+  if (header.type != Http2FrameType::DATA &&
+      header.payload_length > recv_limit) {
+    SetSpdyErrorAndNotify(SpdyFramerError::SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+    return;
+  }
+  switch (header.type) {
+    case Http2FrameType::GOAWAY:
+    case Http2FrameType::ALTSVC:
+      SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME);
+      break;
+    default:
+      SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME_SIZE);
+  }
+}
+
+// Decodes the input up to the next frame boundary (i.e. at most one frame),
+// stopping early if an error is detected.
+size_t Http2DecoderAdapter::ProcessInputFrame(const char* data, size_t len) {
+  DCHECK_NE(spdy_state_, SpdyState::SPDY_ERROR);
+  DecodeBuffer db(data, len);
+  DecodeStatus status = frame_decoder_->DecodeFrame(&db);
+  if (spdy_state_ != SpdyState::SPDY_ERROR) {
+    DetermineSpdyState(status);
+  } else {
+    VLOG(1) << "ProcessInputFrame spdy_framer_error_="
+            << SpdyFramerErrorToString(spdy_framer_error_);
+    if (spdy_framer_error_ == SpdyFramerError::SPDY_INVALID_PADDING &&
+        has_frame_header_ && frame_type() != Http2FrameType::DATA) {
+      // spdy_framer_test checks that all of the available frame payload
+      // has been consumed, so do that.
+      size_t total = remaining_total_payload();
+      if (total <= frame_header().payload_length) {
+        size_t avail = db.MinLengthRemaining(total);
+        VLOG(1) << "Skipping past " << avail << " bytes, of " << total
+                << " total remaining in the frame's payload.";
+        db.AdvanceCursor(avail);
+      } else {
+        SPDY_BUG << "Total remaining (" << total
+                 << ") should not be greater than the payload length; "
+                 << frame_header();
+      }
+    }
+  }
+  return db.Offset();
+}
+
+// After decoding, determine the next SpdyState. Only called if the current
+// state is NOT SpdyState::SPDY_ERROR (i.e. if none of the callback methods
+// detected an error condition), because otherwise we assume that the callback
+// method has set spdy_framer_error_ appropriately.
+void Http2DecoderAdapter::DetermineSpdyState(DecodeStatus status) {
+  DCHECK_EQ(spdy_framer_error_, SPDY_NO_ERROR);
+  DCHECK(!HasError()) << spdy_framer_error_;
+  switch (status) {
+    case DecodeStatus::kDecodeDone:
+      DVLOG(1) << "ProcessInputFrame -> DecodeStatus::kDecodeDone";
+      ResetBetweenFrames();
+      break;
+    case DecodeStatus::kDecodeInProgress:
+      DVLOG(1) << "ProcessInputFrame -> DecodeStatus::kDecodeInProgress";
+      if (decoded_frame_header_) {
+        if (IsDiscardingPayload()) {
+          set_spdy_state(SpdyState::SPDY_IGNORE_REMAINING_PAYLOAD);
+        } else if (has_frame_header_ && frame_type() == Http2FrameType::DATA) {
+          if (IsReadingPaddingLength()) {
+            set_spdy_state(SpdyState::SPDY_READ_DATA_FRAME_PADDING_LENGTH);
+          } else if (IsSkippingPadding()) {
+            set_spdy_state(SpdyState::SPDY_CONSUME_PADDING);
+          } else {
+            set_spdy_state(SpdyState::SPDY_FORWARD_STREAM_FRAME);
+          }
+        } else {
+          set_spdy_state(SpdyState::SPDY_CONTROL_FRAME_PAYLOAD);
+        }
+      } else {
+        set_spdy_state(SpdyState::SPDY_READING_COMMON_HEADER);
+      }
+      break;
+    case DecodeStatus::kDecodeError:
+      VLOG(1) << "ProcessInputFrame -> DecodeStatus::kDecodeError";
+      if (IsDiscardingPayload()) {
+        if (remaining_total_payload() == 0) {
+          // Push the Http2FrameDecoder out of state kDiscardPayload now
+          // since doing so requires no input.
+          DecodeBuffer tmp("", 0);
+          DecodeStatus status = frame_decoder_->DecodeFrame(&tmp);
+          if (status != DecodeStatus::kDecodeDone) {
+            SPDY_BUG << "Expected to be done decoding the frame, not "
+                     << status;
+            SetSpdyErrorAndNotify(SPDY_INTERNAL_FRAMER_ERROR);
+          } else if (spdy_framer_error_ != SPDY_NO_ERROR) {
+            SPDY_BUG << "Expected to have no error, not "
+                     << SpdyFramerErrorToString(spdy_framer_error_);
+          } else {
+            ResetBetweenFrames();
+          }
+        } else {
+          set_spdy_state(SpdyState::SPDY_IGNORE_REMAINING_PAYLOAD);
+        }
+      } else {
+        SetSpdyErrorAndNotify(SPDY_INVALID_CONTROL_FRAME);
+      }
+      break;
+  }
+}
+
+void Http2DecoderAdapter::ResetBetweenFrames() {
+  CorruptFrameHeader(&frame_header_);
+  decoded_frame_header_ = false;
+  has_frame_header_ = false;
+  set_spdy_state(SpdyState::SPDY_READY_FOR_FRAME);
+}
+
+// ResetInternal is called from the constructor, and during tests, but not
+// otherwise (i.e. not between every frame).
+void Http2DecoderAdapter::ResetInternal() {
+  set_spdy_state(SpdyState::SPDY_READY_FOR_FRAME);
+  spdy_framer_error_ = SpdyFramerError::SPDY_NO_ERROR;
+
+  decoded_frame_header_ = false;
+  has_frame_header_ = false;
+  on_headers_called_ = false;
+  on_hpack_fragment_called_ = false;
+  latched_probable_http_response_ = false;
+  has_expected_frame_type_ = false;
+
+  CorruptFrameHeader(&frame_header_);
+  CorruptFrameHeader(&hpack_first_frame_header_);
+
+  frame_decoder_ = SpdyMakeUnique<Http2FrameDecoder>(this);
+  hpack_decoder_ = nullptr;
+}
+
+void Http2DecoderAdapter::set_spdy_state(SpdyState v) {
+  DVLOG(2) << "set_spdy_state(" << StateToString(v) << ")";
+  spdy_state_ = v;
+}
+
+void Http2DecoderAdapter::SetSpdyErrorAndNotify(SpdyFramerError error) {
+  if (HasError()) {
+    DCHECK_EQ(spdy_state_, SpdyState::SPDY_ERROR);
+  } else {
+    VLOG(2) << "SetSpdyErrorAndNotify(" << SpdyFramerErrorToString(error)
+            << ")";
+    DCHECK_NE(error, SpdyFramerError::SPDY_NO_ERROR);
+    spdy_framer_error_ = error;
+    set_spdy_state(SpdyState::SPDY_ERROR);
+    frame_decoder_->set_listener(&no_op_listener_);
+    visitor()->OnError(error);
+  }
+}
+
+bool Http2DecoderAdapter::HasError() const {
+  if (spdy_state_ == SpdyState::SPDY_ERROR) {
+    DCHECK_NE(spdy_framer_error(), SpdyFramerError::SPDY_NO_ERROR);
+    return true;
+  } else {
+    DCHECK_EQ(spdy_framer_error(), SpdyFramerError::SPDY_NO_ERROR);
+    return false;
+  }
+}
+
+const Http2FrameHeader& Http2DecoderAdapter::frame_header() const {
+  DCHECK(has_frame_header_);
+  return frame_header_;
+}
+
+uint32_t Http2DecoderAdapter::stream_id() const {
+  return frame_header().stream_id;
+}
+
+Http2FrameType Http2DecoderAdapter::frame_type() const {
+  return frame_header().type;
+}
+
+size_t Http2DecoderAdapter::remaining_total_payload() const {
+  DCHECK(has_frame_header_);
+  size_t remaining = frame_decoder_->remaining_payload();
+  if (IsPaddable(frame_type()) && frame_header_.IsPadded()) {
+    remaining += frame_decoder_->remaining_padding();
+  }
+  return remaining;
+}
+
+bool Http2DecoderAdapter::IsReadingPaddingLength() {
+  bool result = frame_header_.IsPadded() && !opt_pad_length_;
+  DVLOG(2) << "Http2DecoderAdapter::IsReadingPaddingLength: " << result;
+  return result;
+}
+bool Http2DecoderAdapter::IsSkippingPadding() {
+  bool result = frame_header_.IsPadded() && opt_pad_length_ &&
+                frame_decoder_->remaining_payload() == 0 &&
+                frame_decoder_->remaining_padding() > 0;
+  DVLOG(2) << "Http2DecoderAdapter::IsSkippingPadding: " << result;
+  return result;
+}
+bool Http2DecoderAdapter::IsDiscardingPayload() {
+  bool result = decoded_frame_header_ && frame_decoder_->IsDiscardingPayload();
+  DVLOG(2) << "Http2DecoderAdapter::IsDiscardingPayload: " << result;
+  return result;
+}
+// Called from OnXyz or OnXyzStart methods to decide whether it is OK to
+// handle the callback.
+bool Http2DecoderAdapter::IsOkToStartFrame(const Http2FrameHeader& header) {
+  DVLOG(3) << "IsOkToStartFrame";
+  if (HasError()) {
+    VLOG(2) << "HasError()";
+    return false;
+  }
+  DCHECK(!has_frame_header_);
+  if (has_expected_frame_type_ && header.type != expected_frame_type_) {
+    VLOG(1) << "Expected frame type " << expected_frame_type_ << ", not "
+            << header.type;
+    SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME);
+    return false;
+  }
+
+  return true;
+}
+
+bool Http2DecoderAdapter::HasRequiredStreamId(uint32_t stream_id) {
+  DVLOG(3) << "HasRequiredStreamId: " << stream_id;
+  if (HasError()) {
+    VLOG(2) << "HasError()";
+    return false;
+  }
+  if (stream_id != 0) {
+    return true;
+  }
+  VLOG(1) << "Stream Id is required, but zero provided";
+  SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_STREAM_ID);
+  return false;
+}
+
+bool Http2DecoderAdapter::HasRequiredStreamId(const Http2FrameHeader& header) {
+  return HasRequiredStreamId(header.stream_id);
+}
+
+bool Http2DecoderAdapter::HasRequiredStreamIdZero(uint32_t stream_id) {
+  DVLOG(3) << "HasRequiredStreamIdZero: " << stream_id;
+  if (HasError()) {
+    VLOG(2) << "HasError()";
+    return false;
+  }
+  if (stream_id == 0) {
+    return true;
+  }
+  VLOG(1) << "Stream Id was not zero, as required: " << stream_id;
+  SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_STREAM_ID);
+  return false;
+}
+
+bool Http2DecoderAdapter::HasRequiredStreamIdZero(
+    const Http2FrameHeader& header) {
+  return HasRequiredStreamIdZero(header.stream_id);
+}
+
+void Http2DecoderAdapter::ReportReceiveCompressedFrame(
+    const Http2FrameHeader& header) {
+  if (debug_visitor() != nullptr) {
+    size_t total = header.payload_length + Http2FrameHeader::EncodedSize();
+    debug_visitor()->OnReceiveCompressedFrame(
+        header.stream_id, ToSpdyFrameType(header.type), total);
+  }
+}
+
+HpackDecoderAdapter* Http2DecoderAdapter::GetHpackDecoder() {
+  if (hpack_decoder_ == nullptr) {
+    hpack_decoder_ = SpdyMakeUnique<HpackDecoderAdapter>();
+  }
+  return hpack_decoder_.get();
+}
+
+void Http2DecoderAdapter::CommonStartHpackBlock() {
+  DVLOG(1) << "CommonStartHpackBlock";
+  DCHECK(!has_hpack_first_frame_header_);
+  if (!frame_header_.IsEndHeaders()) {
+    hpack_first_frame_header_ = frame_header_;
+    has_hpack_first_frame_header_ = true;
+  } else {
+    CorruptFrameHeader(&hpack_first_frame_header_);
+  }
+  on_hpack_fragment_called_ = false;
+  SpdyHeadersHandlerInterface* handler =
+      visitor()->OnHeaderFrameStart(stream_id());
+  if (handler == nullptr) {
+    SPDY_BUG << "visitor_->OnHeaderFrameStart returned nullptr";
+    SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INTERNAL_FRAMER_ERROR);
+    return;
+  }
+  GetHpackDecoder()->HandleControlFrameHeadersStart(handler);
+}
+
+// SpdyFramer calls HandleControlFrameHeadersData even if there are zero
+// fragment bytes in the first frame, so do the same.
+void Http2DecoderAdapter::MaybeAnnounceEmptyFirstHpackFragment() {
+  if (!on_hpack_fragment_called_) {
+    OnHpackFragment(nullptr, 0);
+    DCHECK(on_hpack_fragment_called_);
+  }
+}
+
+void Http2DecoderAdapter::CommonHpackFragmentEnd() {
+  DVLOG(1) << "CommonHpackFragmentEnd: stream_id=" << stream_id();
+  if (HasError()) {
+    VLOG(1) << "HasError(), returning";
+    return;
+  }
+  DCHECK(has_frame_header_);
+  MaybeAnnounceEmptyFirstHpackFragment();
+  if (frame_header_.IsEndHeaders()) {
+    DCHECK_EQ(has_hpack_first_frame_header_,
+              frame_type() == Http2FrameType::CONTINUATION)
+        << frame_header();
+    has_expected_frame_type_ = false;
+    if (GetHpackDecoder()->HandleControlFrameHeadersComplete(nullptr)) {
+      visitor()->OnHeaderFrameEnd(stream_id());
+    } else {
+      SetSpdyErrorAndNotify(SpdyFramerError::SPDY_DECOMPRESS_FAILURE);
+      return;
+    }
+    const Http2FrameHeader& first = frame_type() == Http2FrameType::CONTINUATION
+                                        ? hpack_first_frame_header_
+                                        : frame_header_;
+    if (first.type == Http2FrameType::HEADERS && first.IsEndStream()) {
+      visitor()->OnStreamEnd(first.stream_id);
+    }
+    has_hpack_first_frame_header_ = false;
+    CorruptFrameHeader(&hpack_first_frame_header_);
+  } else {
+    DCHECK(has_hpack_first_frame_header_);
+    has_expected_frame_type_ = true;
+    expected_frame_type_ = Http2FrameType::CONTINUATION;
+  }
+}
+
+}  // namespace http2
+
+namespace spdy {
+
+bool SpdyFramerVisitorInterface::OnGoAwayFrameData(const char* goaway_data,
+                                                   size_t len) {
+  return true;
+}
+
+}  // namespace spdy
diff --git a/spdy/core/http2_frame_decoder_adapter.h b/spdy/core/http2_frame_decoder_adapter.h
new file mode 100644
index 0000000..02deafd
--- /dev/null
+++ b/spdy/core/http2_frame_decoder_adapter.h
@@ -0,0 +1,514 @@
+// 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_SPDY_CORE_HTTP2_FRAME_DECODER_ADAPTER_H_
+#define QUICHE_SPDY_CORE_HTTP2_FRAME_DECODER_ADAPTER_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_optional.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+class SpdyFramerVisitorInterface;
+class ExtensionVisitorInterface;
+
+}  // namespace spdy
+
+// TODO(dahollings): Perform various renames/moves suggested in cl/164660364.
+
+namespace http2 {
+
+// Adapts SpdyFramer interface to use Http2FrameDecoder.
+class SPDY_EXPORT_PRIVATE Http2DecoderAdapter
+    : public http2::Http2FrameDecoderListener {
+ public:
+  // HTTP2 states.
+  enum SpdyState {
+    SPDY_ERROR,
+    SPDY_READY_FOR_FRAME,  // Framer is ready for reading the next frame.
+    SPDY_FRAME_COMPLETE,  // Framer has finished reading a frame, need to reset.
+    SPDY_READING_COMMON_HEADER,
+    SPDY_CONTROL_FRAME_PAYLOAD,
+    SPDY_READ_DATA_FRAME_PADDING_LENGTH,
+    SPDY_CONSUME_PADDING,
+    SPDY_IGNORE_REMAINING_PAYLOAD,
+    SPDY_FORWARD_STREAM_FRAME,
+    SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK,
+    SPDY_CONTROL_FRAME_HEADER_BLOCK,
+    SPDY_GOAWAY_FRAME_PAYLOAD,
+    SPDY_SETTINGS_FRAME_HEADER,
+    SPDY_SETTINGS_FRAME_PAYLOAD,
+    SPDY_ALTSVC_FRAME_PAYLOAD,
+    SPDY_EXTENSION_FRAME_PAYLOAD,
+  };
+
+  // Framer error codes.
+  enum SpdyFramerError {
+    SPDY_NO_ERROR,
+    SPDY_INVALID_STREAM_ID,            // Stream ID is invalid
+    SPDY_INVALID_CONTROL_FRAME,        // Control frame is mal-formatted.
+    SPDY_CONTROL_PAYLOAD_TOO_LARGE,    // Control frame payload was too large.
+    SPDY_ZLIB_INIT_FAILURE,            // The Zlib library could not initialize.
+    SPDY_UNSUPPORTED_VERSION,          // Control frame has unsupported version.
+    SPDY_DECOMPRESS_FAILURE,           // There was an error decompressing.
+    SPDY_COMPRESS_FAILURE,             // There was an error compressing.
+    SPDY_GOAWAY_FRAME_CORRUPT,         // GOAWAY frame could not be parsed.
+    SPDY_RST_STREAM_FRAME_CORRUPT,     // RST_STREAM frame could not be parsed.
+    SPDY_INVALID_PADDING,              // HEADERS or DATA frame padding invalid
+    SPDY_INVALID_DATA_FRAME_FLAGS,     // Data frame has invalid flags.
+    SPDY_INVALID_CONTROL_FRAME_FLAGS,  // Control frame has invalid flags.
+    SPDY_UNEXPECTED_FRAME,             // Frame received out of order.
+    SPDY_INTERNAL_FRAMER_ERROR,        // SpdyFramer was used incorrectly.
+    SPDY_INVALID_CONTROL_FRAME_SIZE,   // Control frame not sized to spec
+    SPDY_OVERSIZED_PAYLOAD,            // Payload size was too large
+
+    LAST_ERROR,  // Must be the last entry in the enum.
+  };
+
+  // For debugging.
+  static const char* StateToString(int state);
+  static const char* SpdyFramerErrorToString(SpdyFramerError spdy_framer_error);
+
+  Http2DecoderAdapter();
+  ~Http2DecoderAdapter() override;
+
+  // Set callbacks to be called from the framer.  A visitor must be set, or
+  // else the framer will likely crash.  It is acceptable for the visitor
+  // to do nothing.  If this is called multiple times, only the last visitor
+  // will be used.
+  void set_visitor(spdy::SpdyFramerVisitorInterface* visitor);
+  spdy::SpdyFramerVisitorInterface* visitor() const { return visitor_; }
+
+  // Set extension callbacks to be called from the framer or decoder. Optional.
+  // If called multiple times, only the last visitor will be used.
+  void set_extension_visitor(spdy::ExtensionVisitorInterface* visitor);
+
+  // Set debug callbacks to be called from the framer. The debug visitor is
+  // completely optional and need not be set in order for normal operation.
+  // If this is called multiple times, only the last visitor will be used.
+  void set_debug_visitor(spdy::SpdyFramerDebugVisitorInterface* debug_visitor);
+  spdy::SpdyFramerDebugVisitorInterface* debug_visitor() const {
+    return debug_visitor_;
+  }
+
+  // Set debug callbacks to be called from the HPACK decoder.
+  void SetDecoderHeaderTableDebugVisitor(
+      std::unique_ptr<spdy::HpackHeaderTable::DebugVisitorInterface> visitor);
+
+  // Sets whether or not ProcessInput returns after finishing a frame, or
+  // continues processing additional frames. Normally ProcessInput processes
+  // all input, but this method enables the caller (and visitor) to work with
+  // a single frame at a time (or that portion of the frame which is provided
+  // as input). Reset() does not change the value of this flag.
+  void set_process_single_input_frame(bool v);
+  bool process_single_input_frame() const {
+    return process_single_input_frame_;
+  }
+
+  // Decode the |len| bytes of encoded HTTP/2 starting at |*data|. Returns
+  // the number of bytes consumed. It is safe to pass more bytes in than
+  // may be consumed. Should process (or otherwise buffer) as much as
+  // available, unless process_single_input_frame is true.
+  size_t ProcessInput(const char* data, size_t len);
+
+  // Reset the decoder (used just for tests at this time).
+  void Reset();
+
+  // Current state of the decoder.
+  SpdyState state() const;
+
+  // Current error code (NO_ERROR if state != ERROR).
+  SpdyFramerError spdy_framer_error() const;
+
+  // Has any frame header looked like the start of an HTTP/1.1 (or earlier)
+  // response? Used to detect if a backend/server that we sent a request to
+  // has responded with an HTTP/1.1 (or earlier) response.
+  bool probable_http_response() const;
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+  spdy::HpackDecoderAdapter* GetHpackDecoder();
+
+  bool HasError() const;
+
+ private:
+  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 http2_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;
+
+  size_t ProcessInputFrame(const char* data, size_t len);
+
+  void DetermineSpdyState(DecodeStatus status);
+  void ResetBetweenFrames();
+
+  // ResetInternal is called from the constructor, and during tests, but not
+  // otherwise (i.e. not between every frame).
+  void ResetInternal();
+
+  void set_spdy_state(SpdyState v);
+
+  void SetSpdyErrorAndNotify(SpdyFramerError error);
+
+  const Http2FrameHeader& frame_header() const;
+
+  uint32_t stream_id() const;
+  Http2FrameType frame_type() const;
+
+  size_t remaining_total_payload() const;
+
+  bool IsReadingPaddingLength();
+  bool IsSkippingPadding();
+  bool IsDiscardingPayload();
+  // Called from OnXyz or OnXyzStart methods to decide whether it is OK to
+  // handle the callback.
+  bool IsOkToStartFrame(const Http2FrameHeader& header);
+  bool HasRequiredStreamId(uint32_t stream_id);
+
+  bool HasRequiredStreamId(const Http2FrameHeader& header);
+
+  bool HasRequiredStreamIdZero(uint32_t stream_id);
+
+  bool HasRequiredStreamIdZero(const Http2FrameHeader& header);
+
+  void ReportReceiveCompressedFrame(const Http2FrameHeader& header);
+
+  void CommonStartHpackBlock();
+
+  // SpdyFramer calls HandleControlFrameHeadersData even if there are zero
+  // fragment bytes in the first frame, so do the same.
+  void MaybeAnnounceEmptyFirstHpackFragment();
+  void CommonHpackFragmentEnd();
+
+  // The most recently decoded frame header; invalid after we reached the end
+  // of that frame.
+  Http2FrameHeader frame_header_;
+
+  // If decoding an HPACK block that is split across multiple frames, this holds
+  // the frame header of the HEADERS or PUSH_PROMISE that started the block.
+  Http2FrameHeader hpack_first_frame_header_;
+
+  // Amount of trailing padding. Currently used just as an indicator of whether
+  // OnPadLength has been called.
+  Http2Optional<size_t> opt_pad_length_;
+
+  // Temporary buffers for the AltSvc fields.
+  Http2String alt_svc_origin_;
+  Http2String alt_svc_value_;
+
+  // Listener used if we transition to an error state; the listener ignores all
+  // the callbacks.
+  Http2FrameDecoderNoOpListener no_op_listener_;
+
+  spdy::SpdyFramerVisitorInterface* visitor_ = nullptr;
+  spdy::SpdyFramerDebugVisitorInterface* debug_visitor_ = nullptr;
+
+  // If non-null, unknown frames and settings are passed to the extension.
+  spdy::ExtensionVisitorInterface* extension_ = nullptr;
+
+  // The HPACK decoder to be used for this adapter. User is responsible for
+  // clearing if the adapter is to be used for another connection.
+  std::unique_ptr<spdy::HpackDecoderAdapter> hpack_decoder_ = nullptr;
+
+  // The HTTP/2 frame decoder. Accessed via a unique_ptr to allow replacement
+  // (e.g. in tests) when Reset() is called.
+  std::unique_ptr<Http2FrameDecoder> frame_decoder_;
+
+  // Next frame type expected. Currently only used for CONTINUATION frames,
+  // but could be used for detecting whether the first frame is a SETTINGS
+  // frame.
+  // TODO(jamessyng): Provide means to indicate that decoder should require
+  // SETTINGS frame as the first frame.
+  Http2FrameType expected_frame_type_;
+
+  // Attempt to duplicate the SpdyState and SpdyFramerError values that
+  // SpdyFramer sets. Values determined by getting tests to pass.
+  SpdyState spdy_state_;
+  SpdyFramerError spdy_framer_error_;
+
+  // The limit on the size of received HTTP/2 payloads as specified in the
+  // SETTINGS_MAX_FRAME_SIZE advertised to peer.
+  size_t recv_frame_size_limit_ = spdy::kHttp2DefaultFramePayloadLimit;
+
+  // Has OnFrameHeader been called?
+  bool decoded_frame_header_ = false;
+
+  // Have we recorded an Http2FrameHeader for the current frame?
+  // We only do so if the decoder will make multiple callbacks for
+  // the frame; for example, for PING frames we don't make record
+  // the frame header, but for ALTSVC we do.
+  bool has_frame_header_ = false;
+
+  // Have we recorded an Http2FrameHeader for the current HPACK block?
+  // True only for multi-frame HPACK blocks.
+  bool has_hpack_first_frame_header_ = false;
+
+  // Has OnHeaders() already been called for current HEADERS block? Only
+  // meaningful between OnHeadersStart and OnHeadersPriority.
+  bool on_headers_called_;
+
+  // Has OnHpackFragment() already been called for current HPACK block?
+  // SpdyFramer will pass an empty buffer to the HPACK decoder if a HEADERS
+  // or PUSH_PROMISE has no HPACK data in it (e.g. a HEADERS frame with only
+  // padding). Detect that condition and replicate the behavior using this
+  // field.
+  bool on_hpack_fragment_called_;
+
+  // Have we seen a frame header that appears to be an HTTP/1 response?
+  bool latched_probable_http_response_ = false;
+
+  // Is expected_frame_type_ set?
+  bool has_expected_frame_type_ = false;
+
+  // Is the current frame payload destined for |extension_|?
+  bool handling_extension_payload_ = false;
+
+  bool process_single_input_frame_ = false;
+};
+
+}  // namespace http2
+
+namespace spdy {
+
+// Http2DecoderAdapter will use the given visitor implementing this
+// interface to deliver event callbacks as frames are decoded.
+//
+// Control frames that contain HTTP2 header blocks (HEADER, and PUSH_PROMISE)
+// are processed in fashion that allows the decompressed header block to be
+// delivered in chunks to the visitor.
+// The following steps are followed:
+//   1. OnHeaders, or OnPushPromise is called.
+//   2. OnHeaderFrameStart is called; visitor is expected to return an instance
+//      of SpdyHeadersHandlerInterface that will receive the header key-value
+//      pairs.
+//   3. OnHeaderFrameEnd is called, indicating that the full header block has
+//      been delivered for the control frame.
+// During step 2, if the visitor is not interested in accepting the header data,
+// it should return a no-op implementation of SpdyHeadersHandlerInterface.
+class SPDY_EXPORT_PRIVATE SpdyFramerVisitorInterface {
+ public:
+  virtual ~SpdyFramerVisitorInterface() {}
+
+  // Called if an error is detected in the SpdyFrame protocol.
+  virtual void OnError(http2::Http2DecoderAdapter::SpdyFramerError error) = 0;
+
+  // Called when the common header for a frame is received. Validating the
+  // common header occurs in later processing.
+  virtual void OnCommonHeader(SpdyStreamId /*stream_id*/,
+                              size_t /*length*/,
+                              uint8_t /*type*/,
+                              uint8_t /*flags*/) {}
+
+  // Called when a data frame header is received. The frame's data
+  // payload will be provided via subsequent calls to
+  // OnStreamFrameData().
+  virtual void OnDataFrameHeader(SpdyStreamId stream_id,
+                                 size_t length,
+                                 bool fin) = 0;
+
+  // Called when data is received.
+  // |stream_id| The stream receiving data.
+  // |data| A buffer containing the data received.
+  // |len| The length of the data buffer.
+  virtual void OnStreamFrameData(SpdyStreamId stream_id,
+                                 const char* data,
+                                 size_t len) = 0;
+
+  // Called when the other side has finished sending data on this stream.
+  // |stream_id| The stream that was receiving data.
+  virtual void OnStreamEnd(SpdyStreamId stream_id) = 0;
+
+  // Called when padding length field is received on a DATA frame.
+  // |stream_id| The stream receiving data.
+  // |value| The value of the padding length field.
+  virtual void OnStreamPadLength(SpdyStreamId stream_id, size_t value) {}
+
+  // Called when padding is received (the trailing octets, not pad_len field) on
+  // a DATA frame.
+  // |stream_id| The stream receiving data.
+  // |len| The number of padding octets.
+  virtual void OnStreamPadding(SpdyStreamId stream_id, size_t len) = 0;
+
+  // Called just before processing the payload of a frame containing header
+  // data. Should return an implementation of SpdyHeadersHandlerInterface that
+  // will receive headers for stream |stream_id|. The caller will not take
+  // ownership of the headers handler. The same instance should remain live
+  // and be returned for all header frames comprising a logical header block
+  // (i.e. until OnHeaderFrameEnd() is called).
+  virtual SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+      SpdyStreamId stream_id) = 0;
+
+  // Called after processing the payload of a frame containing header data.
+  virtual void OnHeaderFrameEnd(SpdyStreamId stream_id) = 0;
+
+  // Called when a RST_STREAM frame has been parsed.
+  virtual void OnRstStream(SpdyStreamId stream_id,
+                           SpdyErrorCode error_code) = 0;
+
+  // Called when a SETTINGS frame is received.
+  virtual void OnSettings() {}
+
+  // Called when a complete setting within a SETTINGS frame has been parsed.
+  // Note that |id| may or may not be a SETTINGS ID defined in the HTTP/2 spec.
+  virtual void OnSetting(SpdySettingsId id, uint32_t value) = 0;
+
+  // Called when a SETTINGS frame is received with the ACK flag set.
+  virtual void OnSettingsAck() {}
+
+  // Called before and after parsing SETTINGS id and value tuples.
+  virtual void OnSettingsEnd() = 0;
+
+  // Called when a PING frame has been parsed.
+  virtual void OnPing(SpdyPingId unique_id, bool is_ack) = 0;
+
+  // Called when a GOAWAY frame has been parsed.
+  virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                        SpdyErrorCode error_code) = 0;
+
+  // Called when a HEADERS frame is received.
+  // Note that header block data is not included. See OnHeaderFrameStart().
+  // |stream_id| The stream receiving the header.
+  // |has_priority| Whether or not the headers frame included a priority value,
+  //     and stream dependency info.
+  // |weight| If |has_priority| is true, then weight (in the range [1, 256])
+  //     for the receiving stream, otherwise 0.
+  // |parent_stream_id| If |has_priority| is true the parent stream of the
+  //     receiving stream, else 0.
+  // |exclusive| If |has_priority| is true the exclusivity of dependence on the
+  //     parent stream, else false.
+  // |fin| Whether FIN flag is set in frame headers.
+  // |end| False if HEADERs frame is to be followed by a CONTINUATION frame,
+  //     or true if not.
+  virtual void OnHeaders(SpdyStreamId stream_id,
+                         bool has_priority,
+                         int weight,
+                         SpdyStreamId parent_stream_id,
+                         bool exclusive,
+                         bool fin,
+                         bool end) = 0;
+
+  // Called when a WINDOW_UPDATE frame has been parsed.
+  virtual void OnWindowUpdate(SpdyStreamId stream_id,
+                              int delta_window_size) = 0;
+
+  // Called when a goaway frame opaque data is available.
+  // |goaway_data| A buffer containing the opaque GOAWAY data chunk received.
+  // |len| The length of the header data buffer. A length of zero indicates
+  //       that the header data block has been completely sent.
+  // When this function returns true the visitor indicates that it accepted
+  // all of the data. Returning false indicates that that an error has
+  // occurred while processing the data. Default implementation returns true.
+  virtual bool OnGoAwayFrameData(const char* goaway_data, size_t len);
+
+  // Called when a PUSH_PROMISE frame is received.
+  // Note that header block data is not included. See OnHeaderFrameStart().
+  virtual void OnPushPromise(SpdyStreamId stream_id,
+                             SpdyStreamId promised_stream_id,
+                             bool end) = 0;
+
+  // Called when a CONTINUATION frame is received.
+  // Note that header block data is not included. See OnHeaderFrameStart().
+  virtual void OnContinuation(SpdyStreamId stream_id, bool end) = 0;
+
+  // Called when an ALTSVC frame has been parsed.
+  virtual void OnAltSvc(
+      SpdyStreamId /*stream_id*/,
+      SpdyStringPiece /*origin*/,
+      const SpdyAltSvcWireFormat::AlternativeServiceVector& /*altsvc_vector*/) {
+  }
+
+  // Called when a PRIORITY frame is received.
+  // |stream_id| The stream to update the priority of.
+  // |parent_stream_id| The parent stream of |stream_id|.
+  // |weight| Stream weight, in the range [1, 256].
+  // |exclusive| Whether |stream_id| should be an only child of
+  //     |parent_stream_id|.
+  virtual void OnPriority(SpdyStreamId stream_id,
+                          SpdyStreamId parent_stream_id,
+                          int weight,
+                          bool exclusive) = 0;
+
+  // Called when a frame type we don't recognize is received.
+  // Return true if this appears to be a valid extension frame, false otherwise.
+  // We distinguish between extension frames and nonsense by checking
+  // whether the stream id is valid.
+  virtual bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) = 0;
+};
+
+class SPDY_EXPORT_PRIVATE ExtensionVisitorInterface {
+ public:
+  virtual ~ExtensionVisitorInterface() {}
+
+  // Called when SETTINGS are received, including non-standard SETTINGS.
+  virtual void OnSetting(SpdySettingsId id, uint32_t value) = 0;
+
+  // Called when non-standard frames are received.
+  virtual bool OnFrameHeader(SpdyStreamId stream_id,
+                             size_t length,
+                             uint8_t type,
+                             uint8_t flags) = 0;
+
+  // The payload for a single frame may be delivered as multiple calls to
+  // OnFramePayload. Since the length field is passed in OnFrameHeader, there is
+  // no explicit indication of the end of the frame payload.
+  virtual void OnFramePayload(const char* data, size_t len) = 0;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_HTTP2_FRAME_DECODER_ADAPTER_H_
diff --git a/spdy/core/mock_spdy_framer_visitor.cc b/spdy/core/mock_spdy_framer_visitor.cc
new file mode 100644
index 0000000..053c255
--- /dev/null
+++ b/spdy/core/mock_spdy_framer_visitor.cc
@@ -0,0 +1,19 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h"
+
+namespace spdy {
+
+namespace test {
+
+MockSpdyFramerVisitor::MockSpdyFramerVisitor() {
+  DelegateHeaderHandling();
+}
+
+MockSpdyFramerVisitor::~MockSpdyFramerVisitor() = default;
+
+}  // namespace test
+
+}  // namespace spdy
diff --git a/spdy/core/mock_spdy_framer_visitor.h b/spdy/core/mock_spdy_framer_visitor.h
new file mode 100644
index 0000000..124efe5
--- /dev/null
+++ b/spdy/core/mock_spdy_framer_visitor.h
@@ -0,0 +1,103 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_MOCK_SPDY_FRAMER_VISITOR_H_
+#define QUICHE_SPDY_CORE_MOCK_SPDY_FRAMER_VISITOR_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+
+class MockSpdyFramerVisitor : public SpdyFramerVisitorInterface {
+ public:
+  MockSpdyFramerVisitor();
+  ~MockSpdyFramerVisitor() override;
+
+  MOCK_METHOD1(OnError,
+               void(http2::Http2DecoderAdapter::SpdyFramerError error));
+  MOCK_METHOD3(OnDataFrameHeader,
+               void(SpdyStreamId stream_id, size_t length, bool fin));
+  MOCK_METHOD3(OnStreamFrameData,
+               void(SpdyStreamId stream_id, const char* data, size_t len));
+  MOCK_METHOD1(OnStreamEnd, void(SpdyStreamId stream_id));
+  MOCK_METHOD2(OnStreamPadLength, void(SpdyStreamId stream_id, size_t value));
+  MOCK_METHOD2(OnStreamPadding, void(SpdyStreamId stream_id, size_t len));
+  MOCK_METHOD1(OnHeaderFrameStart,
+               SpdyHeadersHandlerInterface*(SpdyStreamId stream_id));
+  MOCK_METHOD1(OnHeaderFrameEnd, void(SpdyStreamId stream_id));
+  MOCK_METHOD2(OnRstStream,
+               void(SpdyStreamId stream_id, SpdyErrorCode error_code));
+  MOCK_METHOD0(OnSettings, void());
+  MOCK_METHOD2(OnSetting, void(SpdySettingsId id, uint32_t value));
+  MOCK_METHOD2(OnPing, void(SpdyPingId unique_id, bool is_ack));
+  MOCK_METHOD0(OnSettingsEnd, void());
+  MOCK_METHOD2(OnGoAway,
+               void(SpdyStreamId last_accepted_stream_id,
+                    SpdyErrorCode error_code));
+  MOCK_METHOD7(OnHeaders,
+               void(SpdyStreamId stream_id,
+                    bool has_priority,
+                    int weight,
+                    SpdyStreamId parent_stream_id,
+                    bool exclusive,
+                    bool fin,
+                    bool end));
+  MOCK_METHOD2(OnWindowUpdate,
+               void(SpdyStreamId stream_id, int delta_window_size));
+  MOCK_METHOD3(OnPushPromise,
+               void(SpdyStreamId stream_id,
+                    SpdyStreamId promised_stream_id,
+                    bool end));
+  MOCK_METHOD2(OnContinuation, void(SpdyStreamId stream_id, bool end));
+  MOCK_METHOD3(OnAltSvc,
+               void(SpdyStreamId stream_id,
+                    SpdyStringPiece origin,
+                    const SpdyAltSvcWireFormat::AlternativeServiceVector&
+                        altsvc_vector));
+  MOCK_METHOD4(OnPriority,
+               void(SpdyStreamId stream_id,
+                    SpdyStreamId parent_stream_id,
+                    int weight,
+                    bool exclusive));
+  MOCK_METHOD2(OnUnknownFrame,
+               bool(SpdyStreamId stream_id, uint8_t frame_type));
+
+  void DelegateHeaderHandling() {
+    ON_CALL(*this, OnHeaderFrameStart(testing::_))
+        .WillByDefault(testing::Invoke(
+            this, &MockSpdyFramerVisitor::ReturnTestHeadersHandler));
+    ON_CALL(*this, OnHeaderFrameEnd(testing::_))
+        .WillByDefault(testing::Invoke(
+            this, &MockSpdyFramerVisitor::ResetTestHeadersHandler));
+  }
+
+  SpdyHeadersHandlerInterface* ReturnTestHeadersHandler(
+      SpdyStreamId /* stream_id */) {
+    if (headers_handler_ == nullptr) {
+      headers_handler_ = SpdyMakeUnique<TestHeadersHandler>();
+    }
+    return headers_handler_.get();
+  }
+
+  void ResetTestHeadersHandler(SpdyStreamId /* stream_id */) {
+    headers_handler_.reset();
+  }
+
+  std::unique_ptr<SpdyHeadersHandlerInterface> headers_handler_;
+};
+
+}  // namespace test
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_MOCK_SPDY_FRAMER_VISITOR_H_
diff --git a/spdy/core/priority_write_scheduler.h b/spdy/core/priority_write_scheduler.h
new file mode 100644
index 0000000..d5c3c44
--- /dev/null
+++ b/spdy/core/priority_write_scheduler.h
@@ -0,0 +1,322 @@
+// Copyright (c) 2015 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_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_
+#define QUICHE_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_containers.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/write_scheduler.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+namespace test {
+template <typename StreamIdType>
+class PriorityWriteSchedulerPeer;
+}
+
+// WriteScheduler implementation that manages the order in which streams are
+// written using the SPDY priority scheme described at:
+// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-2.3.3-Stream-priority
+//
+// Internally, PriorityWriteScheduler consists of 8 PriorityInfo objects, one
+// for each priority value.  Each PriorityInfo contains a list of streams of
+// that priority that are ready to write, as well as a timestamp of the last
+// I/O event that occurred for a stream of that priority.
+//
+// DO NOT USE. Deprecated.
+template <typename StreamIdType>
+class PriorityWriteScheduler : public WriteScheduler<StreamIdType> {
+ public:
+  using typename WriteScheduler<StreamIdType>::StreamPrecedenceType;
+
+  // Creates scheduler with no streams.
+  PriorityWriteScheduler() = default;
+
+  void RegisterStream(StreamIdType stream_id,
+                      const StreamPrecedenceType& precedence) override {
+    // TODO(mpw): verify |precedence.is_spdy3_priority() == true| once
+    //   Http2PriorityWriteScheduler enabled for HTTP/2.
+
+    // parent_id not used here, but may as well validate it.  However,
+    // parent_id may legitimately not be registered yet--see b/15676312.
+    StreamIdType parent_id = precedence.parent_id();
+    SPDY_DVLOG_IF(
+        1, parent_id != kHttp2RootStreamId && !StreamRegistered(parent_id))
+        << "Parent stream " << parent_id << " not registered";
+
+    if (stream_id == kHttp2RootStreamId) {
+      SPDY_BUG << "Stream " << kHttp2RootStreamId << " already registered";
+      return;
+    }
+    StreamInfo stream_info = {precedence.spdy3_priority(), stream_id, false};
+    bool inserted =
+        stream_infos_.insert(std::make_pair(stream_id, stream_info)).second;
+    SPDY_BUG_IF(!inserted) << "Stream " << stream_id << " already registered";
+  }
+
+  void UnregisterStream(StreamIdType stream_id) override {
+    auto it = stream_infos_.find(stream_id);
+    if (it == stream_infos_.end()) {
+      SPDY_BUG << "Stream " << stream_id << " not registered";
+      return;
+    }
+    StreamInfo& stream_info = it->second;
+    if (stream_info.ready) {
+      bool erased =
+          Erase(&priority_infos_[stream_info.priority].ready_list, stream_info);
+      DCHECK(erased);
+    }
+    stream_infos_.erase(it);
+  }
+
+  bool StreamRegistered(StreamIdType stream_id) const override {
+    return stream_infos_.find(stream_id) != stream_infos_.end();
+  }
+
+  StreamPrecedenceType GetStreamPrecedence(
+      StreamIdType stream_id) const override {
+    auto it = stream_infos_.find(stream_id);
+    if (it == stream_infos_.end()) {
+      DVLOG(1) << "Stream " << stream_id << " not registered";
+      return StreamPrecedenceType(kV3LowestPriority);
+    }
+    return StreamPrecedenceType(it->second.priority);
+  }
+
+  void UpdateStreamPrecedence(StreamIdType stream_id,
+                              const StreamPrecedenceType& precedence) override {
+    // TODO(mpw): verify |precedence.is_spdy3_priority() == true| once
+    //   Http2PriorityWriteScheduler enabled for HTTP/2.
+
+    // parent_id not used here, but may as well validate it.  However,
+    // parent_id may legitimately not be registered yet--see b/15676312.
+    StreamIdType parent_id = precedence.parent_id();
+    SPDY_DVLOG_IF(
+        1, parent_id != kHttp2RootStreamId && !StreamRegistered(parent_id))
+        << "Parent stream " << parent_id << " not registered";
+
+    auto it = stream_infos_.find(stream_id);
+    if (it == stream_infos_.end()) {
+      // TODO(mpw): add to stream_infos_ on demand--see b/15676312.
+      DVLOG(1) << "Stream " << stream_id << " not registered";
+      return;
+    }
+    StreamInfo& stream_info = it->second;
+    SpdyPriority new_priority = precedence.spdy3_priority();
+    if (stream_info.priority == new_priority) {
+      return;
+    }
+    if (stream_info.ready) {
+      bool erased =
+          Erase(&priority_infos_[stream_info.priority].ready_list, stream_info);
+      DCHECK(erased);
+      priority_infos_[new_priority].ready_list.push_back(&stream_info);
+      ++num_ready_streams_;
+    }
+    stream_info.priority = new_priority;
+  }
+
+  std::vector<StreamIdType> GetStreamChildren(
+      StreamIdType stream_id) const override {
+    return std::vector<StreamIdType>();
+  }
+
+  void RecordStreamEventTime(StreamIdType stream_id,
+                             int64_t now_in_usec) override {
+    auto it = stream_infos_.find(stream_id);
+    if (it == stream_infos_.end()) {
+      SPDY_BUG << "Stream " << stream_id << " not registered";
+      return;
+    }
+    PriorityInfo& priority_info = priority_infos_[it->second.priority];
+    priority_info.last_event_time_usec =
+        std::max(priority_info.last_event_time_usec, now_in_usec);
+  }
+
+  int64_t GetLatestEventWithPrecedence(StreamIdType stream_id) const override {
+    auto it = stream_infos_.find(stream_id);
+    if (it == stream_infos_.end()) {
+      SPDY_BUG << "Stream " << stream_id << " not registered";
+      return 0;
+    }
+    int64_t last_event_time_usec = 0;
+    const StreamInfo& stream_info = it->second;
+    for (SpdyPriority p = kV3HighestPriority; p < stream_info.priority; ++p) {
+      last_event_time_usec = std::max(last_event_time_usec,
+                                      priority_infos_[p].last_event_time_usec);
+    }
+    return last_event_time_usec;
+  }
+
+  StreamIdType PopNextReadyStream() override {
+    return std::get<0>(PopNextReadyStreamAndPrecedence());
+  }
+
+  // Returns the next ready stream and its precedence.
+  std::tuple<StreamIdType, StreamPrecedenceType>
+  PopNextReadyStreamAndPrecedence() override {
+    for (SpdyPriority p = kV3HighestPriority; p <= kV3LowestPriority; ++p) {
+      ReadyList& ready_list = priority_infos_[p].ready_list;
+      if (!ready_list.empty()) {
+        StreamInfo* info = ready_list.front();
+        ready_list.pop_front();
+        --num_ready_streams_;
+
+        DCHECK(stream_infos_.find(info->stream_id) != stream_infos_.end());
+        info->ready = false;
+        return std::make_tuple(info->stream_id,
+                               StreamPrecedenceType(info->priority));
+      }
+    }
+    SPDY_BUG << "No ready streams available";
+    return std::make_tuple(0, StreamPrecedenceType(kV3LowestPriority));
+  }
+
+  bool ShouldYield(StreamIdType stream_id) const override {
+    auto it = stream_infos_.find(stream_id);
+    if (it == stream_infos_.end()) {
+      SPDY_BUG << "Stream " << stream_id << " not registered";
+      return false;
+    }
+
+    // If there's a higher priority stream, this stream should yield.
+    const StreamInfo& stream_info = it->second;
+    for (SpdyPriority p = kV3HighestPriority; p < stream_info.priority; ++p) {
+      if (!priority_infos_[p].ready_list.empty()) {
+        return true;
+      }
+    }
+
+    // If this priority level is empty, or this stream is the next up, there's
+    // no need to yield.
+    const auto& ready_list = priority_infos_[it->second.priority].ready_list;
+    if (ready_list.empty() || ready_list.front()->stream_id == stream_id) {
+      return false;
+    }
+
+    // There are other streams in this priority level which take precedence.
+    // Yield.
+    return true;
+  }
+
+  void MarkStreamReady(StreamIdType stream_id, bool add_to_front) override {
+    auto it = stream_infos_.find(stream_id);
+    if (it == stream_infos_.end()) {
+      SPDY_BUG << "Stream " << stream_id << " not registered";
+      return;
+    }
+    StreamInfo& stream_info = it->second;
+    if (stream_info.ready) {
+      return;
+    }
+    ReadyList& ready_list = priority_infos_[stream_info.priority].ready_list;
+    if (add_to_front) {
+      ready_list.push_front(&stream_info);
+    } else {
+      ready_list.push_back(&stream_info);
+    }
+    ++num_ready_streams_;
+    stream_info.ready = true;
+  }
+
+  void MarkStreamNotReady(StreamIdType stream_id) override {
+    auto it = stream_infos_.find(stream_id);
+    if (it == stream_infos_.end()) {
+      SPDY_BUG << "Stream " << stream_id << " not registered";
+      return;
+    }
+    StreamInfo& stream_info = it->second;
+    if (!stream_info.ready) {
+      return;
+    }
+    bool erased =
+        Erase(&priority_infos_[stream_info.priority].ready_list, stream_info);
+    DCHECK(erased);
+    stream_info.ready = false;
+  }
+
+  // Returns true iff the number of ready streams is non-zero.
+  bool HasReadyStreams() const override { return num_ready_streams_ > 0; }
+
+  // Returns the number of ready streams.
+  size_t NumReadyStreams() const override { return num_ready_streams_; }
+
+  SpdyString DebugString() const override {
+    return SpdyStrCat(
+        "PriorityWriteScheduler {num_streams=", stream_infos_.size(),
+        " num_ready_streams=", NumReadyStreams(), "}");
+  }
+
+  // Returns true if a stream is ready.
+  bool IsStreamReady(StreamIdType stream_id) const {
+    auto it = stream_infos_.find(stream_id);
+    if (it == stream_infos_.end()) {
+      DLOG(INFO) << "Stream " << stream_id << " not registered";
+      return false;
+    }
+    return it->second.ready;
+  }
+
+ private:
+  friend class test::PriorityWriteSchedulerPeer<StreamIdType>;
+
+  // State kept for all registered streams. All ready streams have ready = true
+  // and should be present in priority_infos_[priority].ready_list.
+  struct StreamInfo {
+    SpdyPriority priority;
+    StreamIdType stream_id;
+    bool ready;
+  };
+
+  // O(1) size lookup, O(1) insert at front or back (amortized).
+  using ReadyList = http2::Http2Deque<StreamInfo*>;
+
+  // State kept for each priority level.
+  struct PriorityInfo {
+    // IDs of streams that are ready to write.
+    ReadyList ready_list;
+    // Time of latest write event for stream of this priority, in microseconds.
+    int64_t last_event_time_usec = 0;
+  };
+
+  typedef std::unordered_map<StreamIdType, StreamInfo> StreamInfoMap;
+
+  // Erases first occurrence (which should be the only one) of |info| in
+  // |ready_list|, returning true if found (and erased), or false otherwise.
+  // Decrements |num_ready_streams_| if an entry is erased.
+  bool Erase(ReadyList* ready_list, const StreamInfo& info) {
+    auto it = std::find(ready_list->begin(), ready_list->end(), &info);
+    if (it == ready_list->end()) {
+      return false;
+    }
+    ready_list->erase(it);
+    --num_ready_streams_;
+    return true;
+  }
+
+  // Number of ready streams.
+  size_t num_ready_streams_ = 0;
+  // Per-priority state, including ready lists.
+  PriorityInfo priority_infos_[kV3LowestPriority + 1];
+  // StreamInfos for all registered streams.
+  StreamInfoMap stream_infos_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_
diff --git a/spdy/core/priority_write_scheduler_test.cc b/spdy/core/priority_write_scheduler_test.cc
new file mode 100644
index 0000000..a285cb2
--- /dev/null
+++ b/spdy/core/priority_write_scheduler_test.cc
@@ -0,0 +1,371 @@
+// Copyright (c) 2015 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/spdy/core/priority_write_scheduler.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+
+namespace spdy {
+namespace test {
+
+template <typename StreamIdType>
+class PriorityWriteSchedulerPeer {
+ public:
+  explicit PriorityWriteSchedulerPeer(
+      PriorityWriteScheduler<StreamIdType>* scheduler)
+      : scheduler_(scheduler) {}
+
+  size_t NumReadyStreams(SpdyPriority priority) const {
+    return scheduler_->priority_infos_[priority].ready_list.size();
+  }
+
+ private:
+  PriorityWriteScheduler<StreamIdType>* scheduler_;
+};
+
+namespace {
+
+class PriorityWriteSchedulerTest : public ::testing::Test {
+ public:
+  PriorityWriteSchedulerTest() : peer_(&scheduler_) {}
+
+  PriorityWriteScheduler<SpdyStreamId> scheduler_;
+  PriorityWriteSchedulerPeer<SpdyStreamId> peer_;
+};
+
+TEST_F(PriorityWriteSchedulerTest, RegisterUnregisterStreams) {
+  EXPECT_FALSE(scheduler_.HasReadyStreams());
+  EXPECT_FALSE(scheduler_.StreamRegistered(1));
+  scheduler_.RegisterStream(1, SpdyStreamPrecedence(1));
+  EXPECT_TRUE(scheduler_.StreamRegistered(1));
+
+  // Root stream counts as already registered.
+  EXPECT_SPDY_BUG(
+      scheduler_.RegisterStream(kHttp2RootStreamId, SpdyStreamPrecedence(1)),
+      "Stream 0 already registered");
+
+  // Try redundant registrations.
+  EXPECT_SPDY_BUG(scheduler_.RegisterStream(1, SpdyStreamPrecedence(1)),
+                  "Stream 1 already registered");
+  EXPECT_SPDY_BUG(scheduler_.RegisterStream(1, SpdyStreamPrecedence(2)),
+                  "Stream 1 already registered");
+
+  scheduler_.RegisterStream(2, SpdyStreamPrecedence(3));
+
+  // Verify registration != ready.
+  EXPECT_FALSE(scheduler_.HasReadyStreams());
+
+  scheduler_.UnregisterStream(1);
+  scheduler_.UnregisterStream(2);
+
+  // Try redundant unregistration.
+  EXPECT_SPDY_BUG(scheduler_.UnregisterStream(1), "Stream 1 not registered");
+  EXPECT_SPDY_BUG(scheduler_.UnregisterStream(2), "Stream 2 not registered");
+}
+
+TEST_F(PriorityWriteSchedulerTest, RegisterStreamWithHttp2StreamDependency) {
+  EXPECT_FALSE(scheduler_.HasReadyStreams());
+  EXPECT_FALSE(scheduler_.StreamRegistered(1));
+  scheduler_.RegisterStream(
+      1, SpdyStreamPrecedence(kHttp2RootStreamId, 123, false));
+  EXPECT_TRUE(scheduler_.StreamRegistered(1));
+  EXPECT_TRUE(scheduler_.GetStreamPrecedence(1).is_spdy3_priority());
+  EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+  EXPECT_FALSE(scheduler_.HasReadyStreams());
+
+  EXPECT_SPDY_BUG(scheduler_.RegisterStream(
+                      1, SpdyStreamPrecedence(kHttp2RootStreamId, 256, false)),
+                  "Stream 1 already registered");
+  EXPECT_TRUE(scheduler_.GetStreamPrecedence(1).is_spdy3_priority());
+  EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+  // Registering stream with a non-existent parent stream is permissible, per
+  // b/15676312, but parent stream will always be reset to 0.
+  scheduler_.RegisterStream(2, SpdyStreamPrecedence(3, 123, false));
+  EXPECT_TRUE(scheduler_.StreamRegistered(2));
+  EXPECT_FALSE(scheduler_.StreamRegistered(3));
+  EXPECT_EQ(kHttp2RootStreamId, scheduler_.GetStreamPrecedence(2).parent_id());
+}
+
+TEST_F(PriorityWriteSchedulerTest, GetStreamPrecedence) {
+  // Unknown streams tolerated due to b/15676312. However, return lowest
+  // priority.
+  EXPECT_EQ(kV3LowestPriority,
+            scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+  scheduler_.RegisterStream(1, SpdyStreamPrecedence(3));
+  EXPECT_TRUE(scheduler_.GetStreamPrecedence(1).is_spdy3_priority());
+  EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+  // Redundant registration shouldn't change stream priority.
+  EXPECT_SPDY_BUG(scheduler_.RegisterStream(1, SpdyStreamPrecedence(4)),
+                  "Stream 1 already registered");
+  EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+  scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(5));
+  EXPECT_EQ(5, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+  // Toggling ready state shouldn't change stream priority.
+  scheduler_.MarkStreamReady(1, true);
+  EXPECT_EQ(5, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+  // Test changing priority of ready stream.
+  EXPECT_EQ(1u, peer_.NumReadyStreams(5));
+  scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(6));
+  EXPECT_EQ(6, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+  EXPECT_EQ(0u, peer_.NumReadyStreams(5));
+  EXPECT_EQ(1u, peer_.NumReadyStreams(6));
+
+  EXPECT_EQ(1u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(6, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+  scheduler_.UnregisterStream(1);
+  EXPECT_EQ(kV3LowestPriority,
+            scheduler_.GetStreamPrecedence(1).spdy3_priority());
+}
+
+TEST_F(PriorityWriteSchedulerTest, PopNextReadyStreamAndPrecedence) {
+  scheduler_.RegisterStream(1, SpdyStreamPrecedence(3));
+  scheduler_.MarkStreamReady(1, true);
+  EXPECT_EQ(std::make_tuple(1u, SpdyStreamPrecedence(3)),
+            scheduler_.PopNextReadyStreamAndPrecedence());
+  scheduler_.UnregisterStream(1);
+}
+
+TEST_F(PriorityWriteSchedulerTest, UpdateStreamPrecedence) {
+  // For the moment, updating stream precedence on a non-registered stream
+  // should have no effect. In the future, it will lazily cause the stream to
+  // be registered (b/15676312).
+  EXPECT_EQ(kV3LowestPriority,
+            scheduler_.GetStreamPrecedence(3).spdy3_priority());
+  EXPECT_FALSE(scheduler_.StreamRegistered(3));
+  scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(1));
+  EXPECT_FALSE(scheduler_.StreamRegistered(3));
+  EXPECT_EQ(kV3LowestPriority,
+            scheduler_.GetStreamPrecedence(3).spdy3_priority());
+
+  scheduler_.RegisterStream(3, SpdyStreamPrecedence(1));
+  EXPECT_EQ(1, scheduler_.GetStreamPrecedence(3).spdy3_priority());
+  scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(2));
+  EXPECT_EQ(2, scheduler_.GetStreamPrecedence(3).spdy3_priority());
+
+  // Updating priority of stream to current priority value is valid, but has no
+  // effect.
+  scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(2));
+  EXPECT_EQ(2, scheduler_.GetStreamPrecedence(3).spdy3_priority());
+
+  // Even though stream 4 is marked ready after stream 5, it should be returned
+  // first by PopNextReadyStream() since it has higher priority.
+  scheduler_.RegisterStream(4, SpdyStreamPrecedence(1));
+  scheduler_.MarkStreamReady(3, false);  // priority 2
+  EXPECT_TRUE(scheduler_.IsStreamReady(3));
+  scheduler_.MarkStreamReady(4, false);  // priority 1
+  EXPECT_TRUE(scheduler_.IsStreamReady(4));
+  EXPECT_EQ(4u, scheduler_.PopNextReadyStream());
+  EXPECT_FALSE(scheduler_.IsStreamReady(4));
+  EXPECT_EQ(3u, scheduler_.PopNextReadyStream());
+  EXPECT_FALSE(scheduler_.IsStreamReady(3));
+
+  // Verify that lowering priority of stream 4 causes it to be returned later
+  // by PopNextReadyStream().
+  scheduler_.MarkStreamReady(3, false);  // priority 2
+  scheduler_.MarkStreamReady(4, false);  // priority 1
+  scheduler_.UpdateStreamPrecedence(4, SpdyStreamPrecedence(3));
+  EXPECT_EQ(3u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(4u, scheduler_.PopNextReadyStream());
+
+  scheduler_.UnregisterStream(3);
+}
+
+TEST_F(PriorityWriteSchedulerTest,
+       UpdateStreamPrecedenceWithHttp2StreamDependency) {
+  // Unknown streams tolerated due to b/15676312, but should have no effect.
+  scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 100, false));
+  EXPECT_FALSE(scheduler_.StreamRegistered(3));
+
+  scheduler_.RegisterStream(3, SpdyStreamPrecedence(3));
+  scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 100, false));
+  EXPECT_TRUE(scheduler_.GetStreamPrecedence(3).is_spdy3_priority());
+  EXPECT_EQ(4, scheduler_.GetStreamPrecedence(3).spdy3_priority());
+
+  scheduler_.UnregisterStream(3);
+  scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 100, false));
+  EXPECT_FALSE(scheduler_.StreamRegistered(3));
+}
+
+TEST_F(PriorityWriteSchedulerTest, MarkStreamReadyBack) {
+  EXPECT_FALSE(scheduler_.HasReadyStreams());
+  EXPECT_SPDY_BUG(scheduler_.MarkStreamReady(1, false),
+                  "Stream 1 not registered");
+  EXPECT_FALSE(scheduler_.HasReadyStreams());
+  EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+                  "No ready streams available");
+
+  // Add a bunch of ready streams to tail of per-priority lists.
+  // Expected order: (P2) 4, (P3) 1, 2, 3, (P5) 5.
+  scheduler_.RegisterStream(1, SpdyStreamPrecedence(3));
+  scheduler_.MarkStreamReady(1, false);
+  EXPECT_TRUE(scheduler_.HasReadyStreams());
+  scheduler_.RegisterStream(2, SpdyStreamPrecedence(3));
+  scheduler_.MarkStreamReady(2, false);
+  scheduler_.RegisterStream(3, SpdyStreamPrecedence(3));
+  scheduler_.MarkStreamReady(3, false);
+  scheduler_.RegisterStream(4, SpdyStreamPrecedence(2));
+  scheduler_.MarkStreamReady(4, false);
+  scheduler_.RegisterStream(5, SpdyStreamPrecedence(5));
+  scheduler_.MarkStreamReady(5, false);
+
+  EXPECT_EQ(4u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(1u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(2u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(3u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(5u, scheduler_.PopNextReadyStream());
+  EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+                  "No ready streams available");
+}
+
+TEST_F(PriorityWriteSchedulerTest, MarkStreamReadyFront) {
+  EXPECT_FALSE(scheduler_.HasReadyStreams());
+  EXPECT_SPDY_BUG(scheduler_.MarkStreamReady(1, true),
+                  "Stream 1 not registered");
+  EXPECT_FALSE(scheduler_.HasReadyStreams());
+  EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+                  "No ready streams available");
+
+  // Add a bunch of ready streams to head of per-priority lists.
+  // Expected order: (P2) 4, (P3) 3, 2, 1, (P5) 5
+  scheduler_.RegisterStream(1, SpdyStreamPrecedence(3));
+  scheduler_.MarkStreamReady(1, true);
+  EXPECT_TRUE(scheduler_.HasReadyStreams());
+  scheduler_.RegisterStream(2, SpdyStreamPrecedence(3));
+  scheduler_.MarkStreamReady(2, true);
+  scheduler_.RegisterStream(3, SpdyStreamPrecedence(3));
+  scheduler_.MarkStreamReady(3, true);
+  scheduler_.RegisterStream(4, SpdyStreamPrecedence(2));
+  scheduler_.MarkStreamReady(4, true);
+  scheduler_.RegisterStream(5, SpdyStreamPrecedence(5));
+  scheduler_.MarkStreamReady(5, true);
+
+  EXPECT_EQ(4u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(3u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(2u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(1u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(5u, scheduler_.PopNextReadyStream());
+  EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+                  "No ready streams available");
+}
+
+TEST_F(PriorityWriteSchedulerTest, MarkStreamReadyBackAndFront) {
+  scheduler_.RegisterStream(1, SpdyStreamPrecedence(4));
+  scheduler_.RegisterStream(2, SpdyStreamPrecedence(3));
+  scheduler_.RegisterStream(3, SpdyStreamPrecedence(3));
+  scheduler_.RegisterStream(4, SpdyStreamPrecedence(3));
+  scheduler_.RegisterStream(5, SpdyStreamPrecedence(4));
+  scheduler_.RegisterStream(6, SpdyStreamPrecedence(1));
+
+  // Add a bunch of ready streams to per-priority lists, with variety of adding
+  // at head and tail.
+  // Expected order: (P1) 6, (P3) 4, 2, 3, (P4) 1, 5
+  scheduler_.MarkStreamReady(1, true);
+  scheduler_.MarkStreamReady(2, true);
+  scheduler_.MarkStreamReady(3, false);
+  scheduler_.MarkStreamReady(4, true);
+  scheduler_.MarkStreamReady(5, false);
+  scheduler_.MarkStreamReady(6, true);
+
+  EXPECT_EQ(6u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(4u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(2u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(3u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(1u, scheduler_.PopNextReadyStream());
+  EXPECT_EQ(5u, scheduler_.PopNextReadyStream());
+  EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+                  "No ready streams available");
+}
+
+TEST_F(PriorityWriteSchedulerTest, MarkStreamNotReady) {
+  // Verify ready state reflected in NumReadyStreams().
+  scheduler_.RegisterStream(1, SpdyStreamPrecedence(1));
+  EXPECT_EQ(0u, scheduler_.NumReadyStreams());
+  scheduler_.MarkStreamReady(1, false);
+  EXPECT_EQ(1u, scheduler_.NumReadyStreams());
+  scheduler_.MarkStreamNotReady(1);
+  EXPECT_EQ(0u, scheduler_.NumReadyStreams());
+
+  // Empty pop should fail.
+  EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+                  "No ready streams available");
+
+  // Tolerate redundant marking of a stream as not ready.
+  scheduler_.MarkStreamNotReady(1);
+  EXPECT_EQ(0u, scheduler_.NumReadyStreams());
+
+  // Should only be able to mark registered streams.
+  EXPECT_SPDY_BUG(scheduler_.MarkStreamNotReady(3), "Stream 3 not registered");
+}
+
+TEST_F(PriorityWriteSchedulerTest, UnregisterRemovesStream) {
+  scheduler_.RegisterStream(3, SpdyStreamPrecedence(4));
+  scheduler_.MarkStreamReady(3, false);
+  EXPECT_EQ(1u, scheduler_.NumReadyStreams());
+
+  // Unregistering a stream should remove it from set of ready streams.
+  scheduler_.UnregisterStream(3);
+  EXPECT_EQ(0u, scheduler_.NumReadyStreams());
+  EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+                  "No ready streams available");
+}
+
+TEST_F(PriorityWriteSchedulerTest, ShouldYield) {
+  scheduler_.RegisterStream(1, SpdyStreamPrecedence(1));
+  scheduler_.RegisterStream(4, SpdyStreamPrecedence(4));
+  scheduler_.RegisterStream(5, SpdyStreamPrecedence(4));
+  scheduler_.RegisterStream(7, SpdyStreamPrecedence(7));
+
+  // Make sure we don't yield when the list is empty.
+  EXPECT_FALSE(scheduler_.ShouldYield(1));
+
+  // Add a low priority stream.
+  scheduler_.MarkStreamReady(4, false);
+  // 4 should not yield to itself.
+  EXPECT_FALSE(scheduler_.ShouldYield(4));
+  // 7 should yield as 4 is blocked and a higher priority.
+  EXPECT_TRUE(scheduler_.ShouldYield(7));
+  // 5 should yield to 4 as they are the same priority.
+  EXPECT_TRUE(scheduler_.ShouldYield(5));
+  // 1 should not yield as 1 is higher priority.
+  EXPECT_FALSE(scheduler_.ShouldYield(1));
+
+  // Add a second stream in that priority class.
+  scheduler_.MarkStreamReady(5, false);
+  // 4 and 5 are both blocked, but 4 is at the front so should not yield.
+  EXPECT_FALSE(scheduler_.ShouldYield(4));
+  EXPECT_TRUE(scheduler_.ShouldYield(5));
+}
+
+TEST_F(PriorityWriteSchedulerTest, GetLatestEventWithPrecedence) {
+  EXPECT_SPDY_BUG(scheduler_.RecordStreamEventTime(3, 5),
+                  "Stream 3 not registered");
+  EXPECT_SPDY_BUG(EXPECT_EQ(0, scheduler_.GetLatestEventWithPrecedence(4)),
+                  "Stream 4 not registered");
+
+  for (int i = 1; i < 5; ++i) {
+    scheduler_.RegisterStream(i, SpdyStreamPrecedence(i));
+  }
+  for (int i = 1; i < 5; ++i) {
+    EXPECT_EQ(0, scheduler_.GetLatestEventWithPrecedence(i));
+  }
+  for (int i = 1; i < 5; ++i) {
+    scheduler_.RecordStreamEventTime(i, i * 100);
+  }
+  for (int i = 1; i < 5; ++i) {
+    EXPECT_EQ((i - 1) * 100, scheduler_.GetLatestEventWithPrecedence(i));
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace spdy
diff --git a/spdy/core/spdy_alt_svc_wire_format.cc b/spdy/core/spdy_alt_svc_wire_format.cc
new file mode 100644
index 0000000..15234a6
--- /dev/null
+++ b/spdy/core/spdy_alt_svc_wire_format.cc
@@ -0,0 +1,390 @@
+// Copyright (c) 2015 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/spdy/core/spdy_alt_svc_wire_format.h"
+
+#include <algorithm>
+#include <cctype>
+#include <limits>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+namespace {
+
+template <class T>
+bool ParsePositiveIntegerImpl(SpdyStringPiece::const_iterator c,
+                              SpdyStringPiece::const_iterator end,
+                              T* value) {
+  *value = 0;
+  for (; c != end && std::isdigit(*c); ++c) {
+    if (*value > std::numeric_limits<T>::max() / 10) {
+      return false;
+    }
+    *value *= 10;
+    if (*value > std::numeric_limits<T>::max() - (*c - '0')) {
+      return false;
+    }
+    *value += *c - '0';
+  }
+  return (c == end && *value > 0);
+}
+
+}  // namespace
+
+SpdyAltSvcWireFormat::AlternativeService::AlternativeService() = default;
+
+SpdyAltSvcWireFormat::AlternativeService::AlternativeService(
+    const SpdyString& protocol_id,
+    const SpdyString& host,
+    uint16_t port,
+    uint32_t max_age,
+    VersionVector version)
+    : protocol_id(protocol_id),
+      host(host),
+      port(port),
+      max_age(max_age),
+      version(std::move(version)) {}
+
+SpdyAltSvcWireFormat::AlternativeService::~AlternativeService() = default;
+
+SpdyAltSvcWireFormat::AlternativeService::AlternativeService(
+    const AlternativeService& other) = default;
+
+// static
+bool SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+    SpdyStringPiece value,
+    AlternativeServiceVector* altsvc_vector) {
+  // Empty value is invalid according to the specification.
+  if (value.empty()) {
+    return false;
+  }
+  altsvc_vector->clear();
+  if (value == SpdyStringPiece("clear")) {
+    return true;
+  }
+  SpdyStringPiece::const_iterator c = value.begin();
+  while (c != value.end()) {
+    // Parse protocol-id.
+    SpdyStringPiece::const_iterator percent_encoded_protocol_id_end =
+        std::find(c, value.end(), '=');
+    SpdyString protocol_id;
+    if (percent_encoded_protocol_id_end == c ||
+        !PercentDecode(c, percent_encoded_protocol_id_end, &protocol_id)) {
+      return false;
+    }
+    // Check for IETF format for advertising QUIC:
+    // hq=":443";quic=51303338;quic=51303334
+    const bool is_ietf_format_quic = (protocol_id == "hq");
+    c = percent_encoded_protocol_id_end;
+    if (c == value.end()) {
+      return false;
+    }
+    // Parse alt-authority.
+    DCHECK_EQ('=', *c);
+    ++c;
+    if (c == value.end() || *c != '"') {
+      return false;
+    }
+    ++c;
+    SpdyStringPiece::const_iterator alt_authority_begin = c;
+    for (; c != value.end() && *c != '"'; ++c) {
+      // Decode backslash encoding.
+      if (*c != '\\') {
+        continue;
+      }
+      ++c;
+      if (c == value.end()) {
+        return false;
+      }
+    }
+    if (c == alt_authority_begin || c == value.end()) {
+      return false;
+    }
+    DCHECK_EQ('"', *c);
+    SpdyString host;
+    uint16_t port;
+    if (!ParseAltAuthority(alt_authority_begin, c, &host, &port)) {
+      return false;
+    }
+    ++c;
+    // Parse parameters.
+    uint32_t max_age = 86400;
+    VersionVector version;
+    SpdyStringPiece::const_iterator parameters_end =
+        std::find(c, value.end(), ',');
+    while (c != parameters_end) {
+      SkipWhiteSpace(&c, parameters_end);
+      if (c == parameters_end) {
+        break;
+      }
+      if (*c != ';') {
+        return false;
+      }
+      ++c;
+      SkipWhiteSpace(&c, parameters_end);
+      if (c == parameters_end) {
+        break;
+      }
+      SpdyString parameter_name;
+      for (; c != parameters_end && *c != '=' && *c != ' ' && *c != '\t'; ++c) {
+        parameter_name.push_back(tolower(*c));
+      }
+      SkipWhiteSpace(&c, parameters_end);
+      if (c == parameters_end || *c != '=') {
+        return false;
+      }
+      ++c;
+      SkipWhiteSpace(&c, parameters_end);
+      SpdyStringPiece::const_iterator parameter_value_begin = c;
+      for (; c != parameters_end && *c != ';' && *c != ' ' && *c != '\t'; ++c) {
+      }
+      if (c == parameter_value_begin) {
+        return false;
+      }
+      if (parameter_name == "ma") {
+        if (!ParsePositiveInteger32(parameter_value_begin, c, &max_age)) {
+          return false;
+        }
+      } else if (!is_ietf_format_quic && parameter_name == "v") {
+        // Version is a comma separated list of positive integers enclosed in
+        // quotation marks.  Since it can contain commas, which are not
+        // delineating alternative service entries, |parameters_end| and |c| can
+        // be invalid.
+        if (*parameter_value_begin != '"') {
+          return false;
+        }
+        c = std::find(parameter_value_begin + 1, value.end(), '"');
+        if (c == value.end()) {
+          return false;
+        }
+        ++c;
+        parameters_end = std::find(c, value.end(), ',');
+        SpdyStringPiece::const_iterator v_begin = parameter_value_begin + 1;
+        while (v_begin < c) {
+          SpdyStringPiece::const_iterator v_end = v_begin;
+          while (v_end < c - 1 && *v_end != ',') {
+            ++v_end;
+          }
+          uint16_t v;
+          if (!ParsePositiveInteger16(v_begin, v_end, &v)) {
+            return false;
+          }
+          version.push_back(v);
+          v_begin = v_end + 1;
+          if (v_begin == c - 1) {
+            // List ends in comma.
+            return false;
+          }
+        }
+      } else if (is_ietf_format_quic && parameter_name == "quic") {
+        // IETF format for advertising QUIC. Version is hex encoding of QUIC
+        // version tag. Hex-encoded string should not include leading "0x" or
+        // leading zeros.
+        // Example for advertising QUIC versions "Q038" and "Q034":
+        // hq=":443";quic=51303338;quic=51303334
+        if (*parameter_value_begin == '0') {
+          return false;
+        }
+        // Versions will be stored as the uint32_t hex decoding of the param
+        // value string. Example: QUIC version "Q038", which is advertised as:
+        // hq=":443";quic=51303338
+        // ... will be stored in |versions| as 0x51303338.
+        uint32_t quic_version;
+        if (!SpdyHexDecodeToUInt32(SpdyStringPiece(parameter_value_begin,
+                                                   c - parameter_value_begin),
+                                   &quic_version) ||
+            quic_version == 0) {
+          return false;
+        }
+        version.push_back(quic_version);
+      }
+    }
+    altsvc_vector->emplace_back(protocol_id, host, port, max_age, version);
+    for (; c != value.end() && (*c == ' ' || *c == '\t' || *c == ','); ++c) {
+    }
+  }
+  return true;
+}
+
+// static
+SpdyString SpdyAltSvcWireFormat::SerializeHeaderFieldValue(
+    const AlternativeServiceVector& altsvc_vector) {
+  if (altsvc_vector.empty()) {
+    return SpdyString("clear");
+  }
+  const char kNibbleToHex[] = "0123456789ABCDEF";
+  SpdyString value;
+  for (const AlternativeService& altsvc : altsvc_vector) {
+    if (!value.empty()) {
+      value.push_back(',');
+    }
+    // Check for IETF format for advertising QUIC.
+    const bool is_ietf_format_quic = (altsvc.protocol_id == "hq");
+    // Percent escape protocol id according to
+    // http://tools.ietf.org/html/rfc7230#section-3.2.6.
+    for (char c : altsvc.protocol_id) {
+      if (isalnum(c)) {
+        value.push_back(c);
+        continue;
+      }
+      switch (c) {
+        case '!':
+        case '#':
+        case '$':
+        case '&':
+        case '\'':
+        case '*':
+        case '+':
+        case '-':
+        case '.':
+        case '^':
+        case '_':
+        case '`':
+        case '|':
+        case '~':
+          value.push_back(c);
+          break;
+        default:
+          value.push_back('%');
+          // Network byte order is big-endian.
+          value.push_back(kNibbleToHex[c >> 4]);
+          value.push_back(kNibbleToHex[c & 0x0f]);
+          break;
+      }
+    }
+    value.push_back('=');
+    value.push_back('"');
+    for (char c : altsvc.host) {
+      if (c == '"' || c == '\\') {
+        value.push_back('\\');
+      }
+      value.push_back(c);
+    }
+    value.append(SpdyStrCat(":", altsvc.port, "\""));
+    if (altsvc.max_age != 86400) {
+      value.append(SpdyStrCat("; ma=", altsvc.max_age));
+    }
+    if (!altsvc.version.empty()) {
+      if (is_ietf_format_quic) {
+        for (uint32_t quic_version : altsvc.version) {
+          value.append("; quic=");
+          value.append(SpdyHexEncodeUInt32AndTrim(quic_version));
+        }
+      } else {
+        value.append("; v=\"");
+        for (auto it = altsvc.version.begin(); it != altsvc.version.end();
+             ++it) {
+          if (it != altsvc.version.begin()) {
+            value.append(",");
+          }
+          value.append(SpdyStrCat(*it));
+        }
+        value.append("\"");
+      }
+    }
+  }
+  return value;
+}
+
+// static
+void SpdyAltSvcWireFormat::SkipWhiteSpace(SpdyStringPiece::const_iterator* c,
+                                          SpdyStringPiece::const_iterator end) {
+  for (; *c != end && (**c == ' ' || **c == '\t'); ++*c) {
+  }
+}
+
+// static
+bool SpdyAltSvcWireFormat::PercentDecode(SpdyStringPiece::const_iterator c,
+                                         SpdyStringPiece::const_iterator end,
+                                         SpdyString* output) {
+  output->clear();
+  for (; c != end; ++c) {
+    if (*c != '%') {
+      output->push_back(*c);
+      continue;
+    }
+    DCHECK_EQ('%', *c);
+    ++c;
+    if (c == end || !std::isxdigit(*c)) {
+      return false;
+    }
+    // Network byte order is big-endian.
+    char decoded = SpdyHexDigitToInt(*c) << 4;
+    ++c;
+    if (c == end || !std::isxdigit(*c)) {
+      return false;
+    }
+    decoded += SpdyHexDigitToInt(*c);
+    output->push_back(decoded);
+  }
+  return true;
+}
+
+// static
+bool SpdyAltSvcWireFormat::ParseAltAuthority(
+    SpdyStringPiece::const_iterator c,
+    SpdyStringPiece::const_iterator end,
+    SpdyString* host,
+    uint16_t* port) {
+  host->clear();
+  if (c == end) {
+    return false;
+  }
+  if (*c == '[') {
+    for (; c != end && *c != ']'; ++c) {
+      if (*c == '"') {
+        // Port is mandatory.
+        return false;
+      }
+      host->push_back(*c);
+    }
+    if (c == end) {
+      return false;
+    }
+    DCHECK_EQ(']', *c);
+    host->push_back(*c);
+    ++c;
+  } else {
+    for (; c != end && *c != ':'; ++c) {
+      if (*c == '"') {
+        // Port is mandatory.
+        return false;
+      }
+      if (*c == '\\') {
+        ++c;
+        if (c == end) {
+          return false;
+        }
+      }
+      host->push_back(*c);
+    }
+  }
+  if (c == end || *c != ':') {
+    return false;
+  }
+  DCHECK_EQ(':', *c);
+  ++c;
+  return ParsePositiveInteger16(c, end, port);
+}
+
+// static
+bool SpdyAltSvcWireFormat::ParsePositiveInteger16(
+    SpdyStringPiece::const_iterator c,
+    SpdyStringPiece::const_iterator end,
+    uint16_t* value) {
+  return ParsePositiveIntegerImpl<uint16_t>(c, end, value);
+}
+
+// static
+bool SpdyAltSvcWireFormat::ParsePositiveInteger32(
+    SpdyStringPiece::const_iterator c,
+    SpdyStringPiece::const_iterator end,
+    uint32_t* value) {
+  return ParsePositiveIntegerImpl<uint32_t>(c, end, value);
+}
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_alt_svc_wire_format.h b/spdy/core/spdy_alt_svc_wire_format.h
new file mode 100644
index 0000000..ac834bc
--- /dev/null
+++ b/spdy/core/spdy_alt_svc_wire_format.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2015 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.
+
+// This file contains data structures and utility functions used for serializing
+// and parsing alternative service header values, common to HTTP/1.1 header
+// fields and HTTP/2 and QUIC ALTSVC frames.  See specification at
+// https://httpwg.github.io/http-extensions/alt-svc.html.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_ALT_SVC_WIRE_FORMAT_H_
+#define QUICHE_SPDY_CORE_SPDY_ALT_SVC_WIRE_FORMAT_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+class SpdyAltSvcWireFormatPeer;
+}  // namespace test
+
+class SPDY_EXPORT_PRIVATE SpdyAltSvcWireFormat {
+ public:
+  using VersionVector = SpdyInlinedVector<uint32_t, 8>;
+
+  struct SPDY_EXPORT_PRIVATE AlternativeService {
+    SpdyString protocol_id;
+    SpdyString host;
+
+    // Default is 0: invalid port.
+    uint16_t port = 0;
+    // Default is one day.
+    uint32_t max_age = 86400;
+    // Default is empty: unspecified version.
+    VersionVector version;
+
+    AlternativeService();
+    AlternativeService(const SpdyString& protocol_id,
+                       const SpdyString& host,
+                       uint16_t port,
+                       uint32_t max_age,
+                       VersionVector version);
+    AlternativeService(const AlternativeService& other);
+    ~AlternativeService();
+
+    bool operator==(const AlternativeService& other) const {
+      return protocol_id == other.protocol_id && host == other.host &&
+             port == other.port && version == other.version &&
+             max_age == other.max_age;
+    }
+  };
+  // An empty vector means alternative services should be cleared for given
+  // origin.  Note that the wire format for this is the string "clear", not an
+  // empty value (which is invalid).
+  typedef std::vector<AlternativeService> AlternativeServiceVector;
+
+  friend class test::SpdyAltSvcWireFormatPeer;
+  static bool ParseHeaderFieldValue(SpdyStringPiece value,
+                                    AlternativeServiceVector* altsvc_vector);
+  static SpdyString SerializeHeaderFieldValue(
+      const AlternativeServiceVector& altsvc_vector);
+
+ private:
+  static void SkipWhiteSpace(SpdyStringPiece::const_iterator* c,
+                             SpdyStringPiece::const_iterator end);
+  static bool PercentDecode(SpdyStringPiece::const_iterator c,
+                            SpdyStringPiece::const_iterator end,
+                            SpdyString* output);
+  static bool ParseAltAuthority(SpdyStringPiece::const_iterator c,
+                                SpdyStringPiece::const_iterator end,
+                                SpdyString* host,
+                                uint16_t* port);
+  static bool ParsePositiveInteger16(SpdyStringPiece::const_iterator c,
+                                     SpdyStringPiece::const_iterator end,
+                                     uint16_t* value);
+  static bool ParsePositiveInteger32(SpdyStringPiece::const_iterator c,
+                                     SpdyStringPiece::const_iterator end,
+                                     uint32_t* value);
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_ALT_SVC_WIRE_FORMAT_H_
diff --git a/spdy/core/spdy_alt_svc_wire_format_test.cc b/spdy/core/spdy_alt_svc_wire_format_test.cc
new file mode 100644
index 0000000..c2595f0
--- /dev/null
+++ b/spdy/core/spdy_alt_svc_wire_format_test.cc
@@ -0,0 +1,573 @@
+// Copyright (c) 2015 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/spdy/core/spdy_alt_svc_wire_format.h"
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace spdy {
+
+namespace test {
+
+// Expose all private methods of class SpdyAltSvcWireFormat.
+class SpdyAltSvcWireFormatPeer {
+ public:
+  static void SkipWhiteSpace(SpdyStringPiece::const_iterator* c,
+                             SpdyStringPiece::const_iterator end) {
+    SpdyAltSvcWireFormat::SkipWhiteSpace(c, end);
+  }
+  static bool PercentDecode(SpdyStringPiece::const_iterator c,
+                            SpdyStringPiece::const_iterator end,
+                            SpdyString* output) {
+    return SpdyAltSvcWireFormat::PercentDecode(c, end, output);
+  }
+  static bool ParseAltAuthority(SpdyStringPiece::const_iterator c,
+                                SpdyStringPiece::const_iterator end,
+                                SpdyString* host,
+                                uint16_t* port) {
+    return SpdyAltSvcWireFormat::ParseAltAuthority(c, end, host, port);
+  }
+  static bool ParsePositiveInteger16(SpdyStringPiece::const_iterator c,
+                                     SpdyStringPiece::const_iterator end,
+                                     uint16_t* max_age) {
+    return SpdyAltSvcWireFormat::ParsePositiveInteger16(c, end, max_age);
+  }
+  static bool ParsePositiveInteger32(SpdyStringPiece::const_iterator c,
+                                     SpdyStringPiece::const_iterator end,
+                                     uint32_t* max_age) {
+    return SpdyAltSvcWireFormat::ParsePositiveInteger32(c, end, max_age);
+  }
+};
+
+}  // namespace test
+
+namespace {
+
+// Generate header field values, possibly with multiply defined parameters and
+// random case, and corresponding AlternativeService entries.
+void FuzzHeaderFieldValue(
+    int i,
+    SpdyString* header_field_value,
+    SpdyAltSvcWireFormat::AlternativeService* expected_altsvc) {
+  if (!header_field_value->empty()) {
+    header_field_value->push_back(',');
+  }
+  // TODO(b/77515496): use struct of bools instead of int |i| to generate the
+  // header field value.
+  bool is_ietf_format_quic = (i & 1 << 0) != 0;
+  if (i & 1 << 0) {
+    expected_altsvc->protocol_id = "hq";
+    header_field_value->append("hq=\"");
+  } else {
+    expected_altsvc->protocol_id = "a=b%c";
+    header_field_value->append("a%3Db%25c=\"");
+  }
+  if (i & 1 << 1) {
+    expected_altsvc->host = "foo\"bar\\baz";
+    header_field_value->append("foo\\\"bar\\\\baz");
+  } else {
+    expected_altsvc->host = "";
+  }
+  expected_altsvc->port = 42;
+  header_field_value->append(":42\"");
+  if (i & 1 << 2) {
+    header_field_value->append(" ");
+  }
+  if (i & 3 << 3) {
+    expected_altsvc->max_age = 1111;
+    header_field_value->append(";");
+    if (i & 1 << 3) {
+      header_field_value->append(" ");
+    }
+    header_field_value->append("mA=1111");
+    if (i & 2 << 3) {
+      header_field_value->append(" ");
+    }
+  }
+  if (i & 1 << 5) {
+    header_field_value->append("; J=s");
+  }
+  if (i & 1 << 6) {
+    if (is_ietf_format_quic) {
+      if (i & 1 << 7) {
+        expected_altsvc->version.push_back(0x923457e);
+        header_field_value->append("; quic=923457E");
+      } else {
+        expected_altsvc->version.push_back(1);
+        expected_altsvc->version.push_back(0xFFFFFFFF);
+        header_field_value->append("; quic=1; quic=fFfFffFf");
+      }
+    } else {
+      if (i & i << 7) {
+        expected_altsvc->version.push_back(24);
+        header_field_value->append("; v=\"24\"");
+      } else {
+        expected_altsvc->version.push_back(1);
+        expected_altsvc->version.push_back(65535);
+        header_field_value->append("; v=\"1,65535\"");
+      }
+    }
+  }
+  if (i & 1 << 8) {
+    expected_altsvc->max_age = 999999999;
+    header_field_value->append("; Ma=999999999");
+  }
+  if (i & 1 << 9) {
+    header_field_value->append(";");
+  }
+  if (i & 1 << 10) {
+    header_field_value->append(" ");
+  }
+  if (i & 1 << 11) {
+    header_field_value->append(",");
+  }
+  if (i & 1 << 12) {
+    header_field_value->append(" ");
+  }
+}
+
+// Generate AlternativeService entries and corresponding header field values in
+// canonical form, that is, what SerializeHeaderFieldValue() should output.
+void FuzzAlternativeService(int i,
+                            SpdyAltSvcWireFormat::AlternativeService* altsvc,
+                            SpdyString* expected_header_field_value) {
+  if (!expected_header_field_value->empty()) {
+    expected_header_field_value->push_back(',');
+  }
+  altsvc->protocol_id = "a=b%c";
+  altsvc->port = 42;
+  expected_header_field_value->append("a%3Db%25c=\"");
+  if (i & 1 << 0) {
+    altsvc->host = "foo\"bar\\baz";
+    expected_header_field_value->append("foo\\\"bar\\\\baz");
+  }
+  expected_header_field_value->append(":42\"");
+  if (i & 1 << 1) {
+    altsvc->max_age = 1111;
+    expected_header_field_value->append("; ma=1111");
+  }
+  if (i & 1 << 2) {
+    altsvc->version.push_back(24);
+    altsvc->version.push_back(25);
+    expected_header_field_value->append("; v=\"24,25\"");
+  }
+}
+
+// Tests of public API.
+
+TEST(SpdyAltSvcWireFormatTest, DefaultValues) {
+  SpdyAltSvcWireFormat::AlternativeService altsvc;
+  EXPECT_EQ("", altsvc.protocol_id);
+  EXPECT_EQ("", altsvc.host);
+  EXPECT_EQ(0u, altsvc.port);
+  EXPECT_EQ(86400u, altsvc.max_age);
+  EXPECT_TRUE(altsvc.version.empty());
+}
+
+TEST(SpdyAltSvcWireFormatTest, ParseInvalidEmptyHeaderFieldValue) {
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+  ASSERT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue("", &altsvc_vector));
+}
+
+TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueClear) {
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+  ASSERT_TRUE(
+      SpdyAltSvcWireFormat::ParseHeaderFieldValue("clear", &altsvc_vector));
+  EXPECT_EQ(0u, altsvc_vector.size());
+}
+
+// Fuzz test of ParseHeaderFieldValue() with optional whitespaces, ignored
+// parameters, duplicate parameters, trailing space, trailing alternate service
+// separator, etc.  Single alternative service at a time.
+TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValue) {
+  for (int i = 0; i < 1 << 13; ++i) {
+    SpdyString header_field_value;
+    SpdyAltSvcWireFormat::AlternativeService expected_altsvc;
+    FuzzHeaderFieldValue(i, &header_field_value, &expected_altsvc);
+    SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+    ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(header_field_value,
+                                                            &altsvc_vector));
+    ASSERT_EQ(1u, altsvc_vector.size());
+    EXPECT_EQ(expected_altsvc.protocol_id, altsvc_vector[0].protocol_id);
+    EXPECT_EQ(expected_altsvc.host, altsvc_vector[0].host);
+    EXPECT_EQ(expected_altsvc.port, altsvc_vector[0].port);
+    EXPECT_EQ(expected_altsvc.max_age, altsvc_vector[0].max_age);
+    EXPECT_EQ(expected_altsvc.version, altsvc_vector[0].version);
+
+    // Roundtrip test starting with |altsvc_vector|.
+    SpdyString reserialized_header_field_value =
+        SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector);
+    SpdyAltSvcWireFormat::AlternativeServiceVector roundtrip_altsvc_vector;
+    ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+        reserialized_header_field_value, &roundtrip_altsvc_vector));
+    ASSERT_EQ(1u, roundtrip_altsvc_vector.size());
+    EXPECT_EQ(expected_altsvc.protocol_id,
+              roundtrip_altsvc_vector[0].protocol_id);
+    EXPECT_EQ(expected_altsvc.host, roundtrip_altsvc_vector[0].host);
+    EXPECT_EQ(expected_altsvc.port, roundtrip_altsvc_vector[0].port);
+    EXPECT_EQ(expected_altsvc.max_age, roundtrip_altsvc_vector[0].max_age);
+    EXPECT_EQ(expected_altsvc.version, roundtrip_altsvc_vector[0].version);
+  }
+}
+
+// Fuzz test of ParseHeaderFieldValue() with optional whitespaces, ignored
+// parameters, duplicate parameters, trailing space, trailing alternate service
+// separator, etc.  Possibly multiple alternative service at a time.
+TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueMultiple) {
+  for (int i = 0; i < 1 << 13;) {
+    SpdyString header_field_value;
+    SpdyAltSvcWireFormat::AlternativeServiceVector expected_altsvc_vector;
+    // This will generate almost two hundred header field values with two,
+    // three, four, five, six, and seven alternative services each, and
+    // thousands with a single one.
+    do {
+      SpdyAltSvcWireFormat::AlternativeService expected_altsvc;
+      FuzzHeaderFieldValue(i, &header_field_value, &expected_altsvc);
+      expected_altsvc_vector.push_back(expected_altsvc);
+      ++i;
+    } while (i % 6 < i % 7);
+    SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+    ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(header_field_value,
+                                                            &altsvc_vector));
+    ASSERT_EQ(expected_altsvc_vector.size(), altsvc_vector.size());
+    for (unsigned int j = 0; j < altsvc_vector.size(); ++j) {
+      EXPECT_EQ(expected_altsvc_vector[j].protocol_id,
+                altsvc_vector[j].protocol_id);
+      EXPECT_EQ(expected_altsvc_vector[j].host, altsvc_vector[j].host);
+      EXPECT_EQ(expected_altsvc_vector[j].port, altsvc_vector[j].port);
+      EXPECT_EQ(expected_altsvc_vector[j].max_age, altsvc_vector[j].max_age);
+      EXPECT_EQ(expected_altsvc_vector[j].version, altsvc_vector[j].version);
+    }
+
+    // Roundtrip test starting with |altsvc_vector|.
+    SpdyString reserialized_header_field_value =
+        SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector);
+    SpdyAltSvcWireFormat::AlternativeServiceVector roundtrip_altsvc_vector;
+    ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+        reserialized_header_field_value, &roundtrip_altsvc_vector));
+    ASSERT_EQ(expected_altsvc_vector.size(), roundtrip_altsvc_vector.size());
+    for (unsigned int j = 0; j < roundtrip_altsvc_vector.size(); ++j) {
+      EXPECT_EQ(expected_altsvc_vector[j].protocol_id,
+                roundtrip_altsvc_vector[j].protocol_id);
+      EXPECT_EQ(expected_altsvc_vector[j].host,
+                roundtrip_altsvc_vector[j].host);
+      EXPECT_EQ(expected_altsvc_vector[j].port,
+                roundtrip_altsvc_vector[j].port);
+      EXPECT_EQ(expected_altsvc_vector[j].max_age,
+                roundtrip_altsvc_vector[j].max_age);
+      EXPECT_EQ(expected_altsvc_vector[j].version,
+                roundtrip_altsvc_vector[j].version);
+    }
+  }
+}
+
+TEST(SpdyAltSvcWireFormatTest, SerializeEmptyHeaderFieldValue) {
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+  EXPECT_EQ("clear",
+            SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector));
+}
+
+// Test ParseHeaderFieldValue() and SerializeHeaderFieldValue() on the same pair
+// of |expected_header_field_value| and |altsvc|, with and without hostname and
+// each
+// parameter.  Single alternative service at a time.
+TEST(SpdyAltSvcWireFormatTest, RoundTrip) {
+  for (int i = 0; i < 1 << 3; ++i) {
+    SpdyAltSvcWireFormat::AlternativeService altsvc;
+    SpdyString expected_header_field_value;
+    FuzzAlternativeService(i, &altsvc, &expected_header_field_value);
+
+    // Test ParseHeaderFieldValue().
+    SpdyAltSvcWireFormat::AlternativeServiceVector parsed_altsvc_vector;
+    ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+        expected_header_field_value, &parsed_altsvc_vector));
+    ASSERT_EQ(1u, parsed_altsvc_vector.size());
+    EXPECT_EQ(altsvc.protocol_id, parsed_altsvc_vector[0].protocol_id);
+    EXPECT_EQ(altsvc.host, parsed_altsvc_vector[0].host);
+    EXPECT_EQ(altsvc.port, parsed_altsvc_vector[0].port);
+    EXPECT_EQ(altsvc.max_age, parsed_altsvc_vector[0].max_age);
+    EXPECT_EQ(altsvc.version, parsed_altsvc_vector[0].version);
+
+    // Test SerializeHeaderFieldValue().
+    SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+    altsvc_vector.push_back(altsvc);
+    EXPECT_EQ(expected_header_field_value,
+              SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector));
+  }
+}
+
+// Test ParseHeaderFieldValue() and SerializeHeaderFieldValue() on the same pair
+// of |expected_header_field_value| and |altsvc|, with and without hostname and
+// each
+// parameter.  Multiple alternative services at a time.
+TEST(SpdyAltSvcWireFormatTest, RoundTripMultiple) {
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+  SpdyString expected_header_field_value;
+  for (int i = 0; i < 1 << 3; ++i) {
+    SpdyAltSvcWireFormat::AlternativeService altsvc;
+    FuzzAlternativeService(i, &altsvc, &expected_header_field_value);
+    altsvc_vector.push_back(altsvc);
+  }
+
+  // Test ParseHeaderFieldValue().
+  SpdyAltSvcWireFormat::AlternativeServiceVector parsed_altsvc_vector;
+  ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+      expected_header_field_value, &parsed_altsvc_vector));
+  ASSERT_EQ(altsvc_vector.size(), parsed_altsvc_vector.size());
+  auto expected_it = altsvc_vector.begin();
+  auto parsed_it = parsed_altsvc_vector.begin();
+  for (; expected_it != altsvc_vector.end(); ++expected_it, ++parsed_it) {
+    EXPECT_EQ(expected_it->protocol_id, parsed_it->protocol_id);
+    EXPECT_EQ(expected_it->host, parsed_it->host);
+    EXPECT_EQ(expected_it->port, parsed_it->port);
+    EXPECT_EQ(expected_it->max_age, parsed_it->max_age);
+    EXPECT_EQ(expected_it->version, parsed_it->version);
+  }
+
+  // Test SerializeHeaderFieldValue().
+  EXPECT_EQ(expected_header_field_value,
+            SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector));
+}
+
+// ParseHeaderFieldValue() should return false on malformed field values:
+// invalid percent encoding, unmatched quotation mark, empty port, non-numeric
+// characters in numeric fields.
+TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueInvalid) {
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+  const char* invalid_field_value_array[] = {"a%",
+                                             "a%x",
+                                             "a%b",
+                                             "a%9z",
+                                             "a=",
+                                             "a=\"",
+                                             "a=\"b\"",
+                                             "a=\":\"",
+                                             "a=\"c:\"",
+                                             "a=\"c:foo\"",
+                                             "a=\"c:42foo\"",
+                                             "a=\"b:42\"bar",
+                                             "a=\"b:42\" ; m",
+                                             "a=\"b:42\" ; min-age",
+                                             "a=\"b:42\" ; ma",
+                                             "a=\"b:42\" ; ma=",
+                                             "a=\"b:42\" ; v=\"..\"",
+                                             "a=\"b:42\" ; ma=ma",
+                                             "a=\"b:42\" ; ma=123bar",
+                                             "a=\"b:42\" ; v=24",
+                                             "a=\"b:42\" ; v=24,25",
+                                             "a=\"b:42\" ; v=\"-3\"",
+                                             "a=\"b:42\" ; v=\"1.2\"",
+                                             "a=\"b:42\" ; v=\"24,\""};
+  for (const char* invalid_field_value : invalid_field_value_array) {
+    EXPECT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+        invalid_field_value, &altsvc_vector))
+        << invalid_field_value;
+  }
+}
+
+// ParseHeaderFieldValue() should return false on a field values truncated
+// before closing quotation mark, without trying to access memory beyond the end
+// of the input.
+TEST(SpdyAltSvcWireFormatTest, ParseTruncatedHeaderFieldValue) {
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+  const char* field_value_array[] = {"a=\":137\"", "a=\"foo:137\"",
+                                     "a%25=\"foo\\\"bar\\\\baz:137\""};
+  for (const SpdyString& field_value : field_value_array) {
+    for (size_t len = 1; len < field_value.size(); ++len) {
+      EXPECT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+          field_value.substr(0, len), &altsvc_vector))
+          << len;
+    }
+  }
+}
+
+// Tests of private methods.
+
+// Test SkipWhiteSpace().
+TEST(SpdyAltSvcWireFormatTest, SkipWhiteSpace) {
+  SpdyStringPiece input("a \tb  ");
+  SpdyStringPiece::const_iterator c = input.begin();
+  test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end());
+  ASSERT_EQ(input.begin(), c);
+  ++c;
+  test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end());
+  ASSERT_EQ(input.begin() + 3, c);
+  ++c;
+  test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end());
+  ASSERT_EQ(input.end(), c);
+}
+
+// Test PercentDecode() on valid input.
+TEST(SpdyAltSvcWireFormatTest, PercentDecodeValid) {
+  SpdyStringPiece input("");
+  SpdyString output;
+  ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
+      input.begin(), input.end(), &output));
+  EXPECT_EQ("", output);
+
+  input = SpdyStringPiece("foo");
+  output.clear();
+  ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
+      input.begin(), input.end(), &output));
+  EXPECT_EQ("foo", output);
+
+  input = SpdyStringPiece("%2ca%5Cb");
+  output.clear();
+  ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
+      input.begin(), input.end(), &output));
+  EXPECT_EQ(",a\\b", output);
+}
+
+// Test PercentDecode() on invalid input.
+TEST(SpdyAltSvcWireFormatTest, PercentDecodeInvalid) {
+  const char* invalid_input_array[] = {"a%", "a%x", "a%b", "%J22", "%9z"};
+  for (const char* invalid_input : invalid_input_array) {
+    SpdyStringPiece input(invalid_input);
+    SpdyString output;
+    EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
+        input.begin(), input.end(), &output))
+        << input;
+  }
+}
+
+// Test ParseAltAuthority() on valid input.
+TEST(SpdyAltSvcWireFormatTest, ParseAltAuthorityValid) {
+  SpdyStringPiece input(":42");
+  SpdyString host;
+  uint16_t port;
+  ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority(
+      input.begin(), input.end(), &host, &port));
+  EXPECT_TRUE(host.empty());
+  EXPECT_EQ(42, port);
+
+  input = SpdyStringPiece("foo:137");
+  ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority(
+      input.begin(), input.end(), &host, &port));
+  EXPECT_EQ("foo", host);
+  EXPECT_EQ(137, port);
+
+  input = SpdyStringPiece("[2003:8:0:16::509d:9615]:443");
+  ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority(
+      input.begin(), input.end(), &host, &port));
+  EXPECT_EQ("[2003:8:0:16::509d:9615]", host);
+  EXPECT_EQ(443, port);
+}
+
+// Test ParseAltAuthority() on invalid input: empty string, no port, zero port,
+// non-digit characters following port.
+TEST(SpdyAltSvcWireFormatTest, ParseAltAuthorityInvalid) {
+  const char* invalid_input_array[] = {"",
+                                       ":",
+                                       "foo:",
+                                       ":bar",
+                                       ":0",
+                                       "foo:0",
+                                       ":12bar",
+                                       "foo:23bar",
+                                       " ",
+                                       ":12 ",
+                                       "foo:12 ",
+                                       "[2003:8:0:16::509d:9615]",
+                                       "[2003:8:0:16::509d:9615]:",
+                                       "[2003:8:0:16::509d:9615]foo:443",
+                                       "[2003:8:0:16::509d:9615:443",
+                                       "2003:8:0:16::509d:9615]:443"};
+  for (const char* invalid_input : invalid_input_array) {
+    SpdyStringPiece input(invalid_input);
+    SpdyString host;
+    uint16_t port;
+    EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority(
+        input.begin(), input.end(), &host, &port))
+        << input;
+  }
+}
+
+// Test ParseInteger() on valid input.
+TEST(SpdyAltSvcWireFormatTest, ParseIntegerValid) {
+  SpdyStringPiece input("3");
+  uint16_t value;
+  ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+      input.begin(), input.end(), &value));
+  EXPECT_EQ(3, value);
+
+  input = SpdyStringPiece("1337");
+  ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+      input.begin(), input.end(), &value));
+  EXPECT_EQ(1337, value);
+}
+
+// Test ParseIntegerValid() on invalid input: empty, zero, non-numeric, trailing
+// non-numeric characters.
+TEST(SpdyAltSvcWireFormatTest, ParseIntegerInvalid) {
+  const char* invalid_input_array[] = {"", " ", "a", "0", "00", "1 ", "12b"};
+  for (const char* invalid_input : invalid_input_array) {
+    SpdyStringPiece input(invalid_input);
+    uint16_t value;
+    EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+        input.begin(), input.end(), &value))
+        << input;
+  }
+}
+
+// Test ParseIntegerValid() around overflow limit.
+TEST(SpdyAltSvcWireFormatTest, ParseIntegerOverflow) {
+  // Largest possible uint16_t value.
+  SpdyStringPiece input("65535");
+  uint16_t value16;
+  ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+      input.begin(), input.end(), &value16));
+  EXPECT_EQ(65535, value16);
+
+  // Overflow uint16_t, ParsePositiveInteger16() should return false.
+  input = SpdyStringPiece("65536");
+  ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+      input.begin(), input.end(), &value16));
+
+  // However, even if overflow is not checked for, 65536 overflows to 0, which
+  // returns false anyway.  Check for a larger number which overflows to 1.
+  input = SpdyStringPiece("65537");
+  ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+      input.begin(), input.end(), &value16));
+
+  // Largest possible uint32_t value.
+  input = SpdyStringPiece("4294967295");
+  uint32_t value32;
+  ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32(
+      input.begin(), input.end(), &value32));
+  EXPECT_EQ(4294967295, value32);
+
+  // Overflow uint32_t, ParsePositiveInteger32() should return false.
+  input = SpdyStringPiece("4294967296");
+  ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32(
+      input.begin(), input.end(), &value32));
+
+  // However, even if overflow is not checked for, 4294967296 overflows to 0,
+  // which returns false anyway.  Check for a larger number which overflows to
+  // 1.
+  input = SpdyStringPiece("4294967297");
+  ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32(
+      input.begin(), input.end(), &value32));
+}
+
+// Test parsing an Alt-Svc entry with IP literal hostname.
+// Regression test for https://crbug.com/664173.
+TEST(SpdyAltSvcWireFormatTest, ParseIPLiteral) {
+  const char* input =
+      "quic=\"[2003:8:0:16::509d:9615]:443\"; v=\"36,35\"; ma=60";
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+  ASSERT_TRUE(
+      SpdyAltSvcWireFormat::ParseHeaderFieldValue(input, &altsvc_vector));
+  EXPECT_EQ(1u, altsvc_vector.size());
+  EXPECT_EQ("quic", altsvc_vector[0].protocol_id);
+  EXPECT_EQ("[2003:8:0:16::509d:9615]", altsvc_vector[0].host);
+  EXPECT_EQ(443u, altsvc_vector[0].port);
+  EXPECT_EQ(60u, altsvc_vector[0].max_age);
+  EXPECT_THAT(altsvc_vector[0].version, ::testing::ElementsAre(36, 35));
+}
+
+}  // namespace
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_bitmasks.h b/spdy/core/spdy_bitmasks.h
new file mode 100644
index 0000000..657bd17
--- /dev/null
+++ b/spdy/core/spdy_bitmasks.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 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_SPDY_CORE_SPDY_BITMASKS_H_
+#define QUICHE_SPDY_CORE_SPDY_BITMASKS_H_
+
+namespace spdy {
+
+// StreamId mask from the SpdyHeader
+const unsigned int kStreamIdMask = 0x7fffffff;
+
+// Mask the lower 24 bits.
+const unsigned int kLengthMask = 0xffffff;
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_BITMASKS_H_
diff --git a/spdy/core/spdy_deframer_visitor.cc b/spdy/core/spdy_deframer_visitor.cc
new file mode 100644
index 0000000..76188e9
--- /dev/null
+++ b/spdy/core/spdy_deframer_visitor.cc
@@ -0,0 +1,1028 @@
+// 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/spdy/core/spdy_deframer_visitor.h"
+
+#include <stdlib.h>
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <memory>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_flags.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace spdy {
+namespace test {
+
+// Specify whether to process headers as request or response in visitor-related
+// params.
+enum class HeaderDirection { REQUEST, RESPONSE };
+
+// Types of HTTP/2 frames, per RFC 7540.
+// TODO(jamessynge): Switch to using http2/http2_constants.h when ready.
+enum Http2FrameType {
+  DATA = 0,
+  HEADERS = 1,
+  PRIORITY = 2,
+  RST_STREAM = 3,
+  SETTINGS = 4,
+  PUSH_PROMISE = 5,
+  PING = 6,
+  GOAWAY = 7,
+  WINDOW_UPDATE = 8,
+  CONTINUATION = 9,
+  ALTSVC = 10,
+
+  // Not a frame type.
+  UNSET = -1,
+  UNKNOWN = -2,
+};
+
+// TODO(jamessynge): Switch to using http2/http2_constants.h when ready.
+const char* Http2FrameTypeToString(Http2FrameType v) {
+  switch (v) {
+    case DATA:
+      return "DATA";
+    case HEADERS:
+      return "HEADERS";
+    case PRIORITY:
+      return "PRIORITY";
+    case RST_STREAM:
+      return "RST_STREAM";
+    case SETTINGS:
+      return "SETTINGS";
+    case PUSH_PROMISE:
+      return "PUSH_PROMISE";
+    case PING:
+      return "PING";
+    case GOAWAY:
+      return "GOAWAY";
+    case WINDOW_UPDATE:
+      return "WINDOW_UPDATE";
+    case CONTINUATION:
+      return "CONTINUATION";
+    case ALTSVC:
+      return "ALTSVC";
+    case UNSET:
+      return "UNSET";
+    case UNKNOWN:
+      return "UNKNOWN";
+    default:
+      return "Invalid Http2FrameType";
+  }
+}
+
+// TODO(jamessynge): Switch to using http2/http2_constants.h when ready.
+inline std::ostream& operator<<(std::ostream& out, Http2FrameType v) {
+  return out << Http2FrameTypeToString(v);
+}
+
+// Flag bits in the flag field of the common header of HTTP/2 frames
+// (see https://httpwg.github.io/specs/rfc7540.html#FrameHeader for details on
+// the fixed 9-octet header structure shared by all frames).
+// Flag bits are only valid for specified frame types.
+// TODO(jamessynge): Switch to using http2/http2_constants.h when ready.
+enum Http2HeaderFlag {
+  NO_FLAGS = 0,
+
+  END_STREAM_FLAG = 0x1,
+  ACK_FLAG = 0x1,
+  END_HEADERS_FLAG = 0x4,
+  PADDED_FLAG = 0x8,
+  PRIORITY_FLAG = 0x20,
+};
+
+// Returns name of frame type.
+// TODO(jamessynge): Switch to using http2/http2_constants.h when ready.
+const char* Http2FrameTypeToString(Http2FrameType v);
+
+void SpdyDeframerVisitorInterface::OnPingAck(
+    std::unique_ptr<SpdyPingIR> frame) {
+  OnPing(std::move(frame));
+}
+
+void SpdyDeframerVisitorInterface::OnSettingsAck(
+    std::unique_ptr<SpdySettingsIR> frame) {
+  OnSettings(std::move(frame), nullptr);
+}
+
+class SpdyTestDeframerImpl : public SpdyTestDeframer,
+                             public SpdyHeadersHandlerInterface {
+ public:
+  explicit SpdyTestDeframerImpl(
+      std::unique_ptr<SpdyDeframerVisitorInterface> listener)
+      : listener_(std::move(listener)) {
+    CHECK(listener_ != nullptr);
+  }
+  SpdyTestDeframerImpl(const SpdyTestDeframerImpl&) = delete;
+  SpdyTestDeframerImpl& operator=(const SpdyTestDeframerImpl&) = delete;
+  ~SpdyTestDeframerImpl() override = default;
+
+  bool AtFrameEnd() override;
+
+  // Callbacks defined in SpdyFramerVisitorInterface.  These are in the
+  // alphabetical order for ease of navigation, and are not in same order
+  // as in SpdyFramerVisitorInterface.
+  void OnAltSvc(SpdyStreamId stream_id,
+                SpdyStringPiece origin,
+                const SpdyAltSvcWireFormat::AlternativeServiceVector&
+                    altsvc_vector) override;
+  void OnContinuation(SpdyStreamId stream_id, bool end) override;
+  SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+      SpdyStreamId stream_id) override;
+  void OnHeaderFrameEnd(SpdyStreamId stream_id) override;
+  void OnDataFrameHeader(SpdyStreamId stream_id,
+                         size_t length,
+                         bool fin) override;
+  void OnError(http2::Http2DecoderAdapter::SpdyFramerError error) override;
+  void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                SpdyErrorCode error_code) override;
+  bool OnGoAwayFrameData(const char* goaway_data, size_t len) override;
+  void OnHeaders(SpdyStreamId stream_id,
+                 bool has_priority,
+                 int weight,
+                 SpdyStreamId parent_stream_id,
+                 bool exclusive,
+                 bool fin,
+                 bool end) override;
+  void OnPing(SpdyPingId unique_id, bool is_ack) override;
+  void OnPriority(SpdyStreamId stream_id,
+                  SpdyStreamId parent_stream_id,
+                  int weight,
+                  bool exclusive) override;
+  void OnPushPromise(SpdyStreamId stream_id,
+                     SpdyStreamId promised_stream_id,
+                     bool end) override;
+  void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override;
+  void OnSetting(SpdySettingsId id, uint32_t value) override;
+  void OnSettings() override;
+  void OnSettingsAck() override;
+  void OnSettingsEnd() override;
+  void OnStreamFrameData(SpdyStreamId stream_id,
+                         const char* data,
+                         size_t len) override;
+  void OnStreamEnd(SpdyStreamId stream_id) override;
+  void OnStreamPadLength(SpdyStreamId stream_id, size_t value) override;
+  void OnStreamPadding(SpdyStreamId stream_id, size_t len) override;
+  bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override;
+  void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override;
+
+  // Callbacks defined in SpdyHeadersHandlerInterface.
+
+  void OnHeaderBlockStart() override;
+  void OnHeader(SpdyStringPiece key, SpdyStringPiece value) override;
+  void OnHeaderBlockEnd(size_t header_bytes_parsed,
+                        size_t compressed_header_bytes_parsed) override;
+
+ protected:
+  void AtDataEnd();
+  void AtGoAwayEnd();
+  void AtHeadersEnd();
+  void AtPushPromiseEnd();
+
+  // Per-physical frame state.
+  // Frame type of the frame currently being processed.
+  Http2FrameType frame_type_ = UNSET;
+  // Stream id of the frame currently being processed.
+  SpdyStreamId stream_id_;
+  // Did the most recent frame header include the END_HEADERS flag?
+  bool end_ = false;
+  // Did the most recent frame header include the ack flag?
+  bool ack_ = false;
+
+  // Per-HPACK block state. Only valid while processing a HEADERS or
+  // PUSH_PROMISE frame, and its CONTINUATION frames.
+  // Did the most recent HEADERS or PUSH_PROMISE include the END_STREAM flag?
+  // Note that this does not necessarily indicate that the current frame is
+  // the last frame for the stream (may be followed by CONTINUATION frames,
+  // may only half close).
+  bool fin_ = false;
+  bool got_hpack_end_ = false;
+
+  std::unique_ptr<SpdyString> data_;
+
+  // Total length of the data frame.
+  size_t data_len_ = 0;
+
+  // Amount of skipped padding (i.e. total length of padding, including Pad
+  // Length field).
+  size_t padding_len_ = 0;
+
+  std::unique_ptr<SpdyString> goaway_description_;
+  std::unique_ptr<StringPairVector> headers_;
+  std::unique_ptr<SettingVector> settings_;
+  std::unique_ptr<TestHeadersHandler> headers_handler_;
+
+  std::unique_ptr<SpdyGoAwayIR> goaway_ir_;
+  std::unique_ptr<SpdyHeadersIR> headers_ir_;
+  std::unique_ptr<SpdyPushPromiseIR> push_promise_ir_;
+  std::unique_ptr<SpdySettingsIR> settings_ir_;
+
+ private:
+  std::unique_ptr<SpdyDeframerVisitorInterface> listener_;
+};
+
+// static
+std::unique_ptr<SpdyTestDeframer> SpdyTestDeframer::CreateConverter(
+    std::unique_ptr<SpdyDeframerVisitorInterface> listener) {
+  return SpdyMakeUnique<SpdyTestDeframerImpl>(std::move(listener));
+}
+
+void SpdyTestDeframerImpl::AtDataEnd() {
+  DVLOG(1) << "AtDataEnd";
+  CHECK_EQ(data_len_, padding_len_ + data_->size());
+  auto ptr = SpdyMakeUnique<SpdyDataIR>(stream_id_, std::move(*data_));
+  CHECK_EQ(0u, data_->size());
+  data_.reset();
+
+  CHECK_LE(0u, padding_len_);
+  CHECK_LE(padding_len_, 256u);
+  if (padding_len_ > 0) {
+    ptr->set_padding_len(padding_len_);
+  }
+  padding_len_ = 0;
+
+  ptr->set_fin(fin_);
+  listener_->OnData(std::move(ptr));
+  frame_type_ = UNSET;
+  fin_ = false;
+  data_len_ = 0;
+}
+
+void SpdyTestDeframerImpl::AtGoAwayEnd() {
+  DVLOG(1) << "AtDataEnd";
+  CHECK_EQ(frame_type_, GOAWAY);
+  if (HTTP2_DIE_IF_NULL(goaway_description_)->empty()) {
+    listener_->OnGoAway(std::move(goaway_ir_));
+  } else {
+    listener_->OnGoAway(SpdyMakeUnique<SpdyGoAwayIR>(
+        goaway_ir_->last_good_stream_id(), goaway_ir_->error_code(),
+        std::move(*goaway_description_)));
+    CHECK_EQ(0u, goaway_description_->size());
+  }
+  goaway_description_.reset();
+  goaway_ir_.reset();
+  frame_type_ = UNSET;
+}
+
+void SpdyTestDeframerImpl::AtHeadersEnd() {
+  DVLOG(1) << "AtDataEnd";
+  CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK(end_) << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK(got_hpack_end_);
+
+  CHECK(headers_ir_ != nullptr);
+  CHECK(headers_ != nullptr);
+  CHECK(headers_handler_ != nullptr);
+
+  CHECK_LE(0u, padding_len_);
+  CHECK_LE(padding_len_, 256u);
+  if (padding_len_ > 0) {
+    headers_ir_->set_padding_len(padding_len_);
+  }
+  padding_len_ = 0;
+
+  headers_ir_->set_header_block(headers_handler_->decoded_block().Clone());
+  headers_handler_.reset();
+  listener_->OnHeaders(std::move(headers_ir_), std::move(headers_));
+
+  frame_type_ = UNSET;
+  fin_ = false;
+  end_ = false;
+  got_hpack_end_ = false;
+}
+
+void SpdyTestDeframerImpl::AtPushPromiseEnd() {
+  DVLOG(1) << "AtDataEnd";
+  CHECK(frame_type_ == PUSH_PROMISE || frame_type_ == CONTINUATION)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK(end_) << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+
+  CHECK(push_promise_ir_ != nullptr);
+  CHECK(headers_ != nullptr);
+  CHECK(headers_handler_ != nullptr);
+
+  CHECK_EQ(headers_ir_.get(), nullptr);
+
+  CHECK_LE(0u, padding_len_);
+  CHECK_LE(padding_len_, 256u);
+  if (padding_len_ > 0) {
+    push_promise_ir_->set_padding_len(padding_len_);
+  }
+  padding_len_ = 0;
+
+  push_promise_ir_->set_header_block(headers_handler_->decoded_block().Clone());
+  headers_handler_.reset();
+  listener_->OnPushPromise(std::move(push_promise_ir_), std::move(headers_));
+
+  frame_type_ = UNSET;
+  end_ = false;
+}
+
+bool SpdyTestDeframerImpl::AtFrameEnd() {
+  bool incomplete_logical_header = false;
+  // The caller says that the SpdyFrame has reached the end of the frame,
+  // so if we have any accumulated data, flush it.
+  switch (frame_type_) {
+    case DATA:
+      AtDataEnd();
+      break;
+
+    case GOAWAY:
+      AtGoAwayEnd();
+      break;
+
+    case HEADERS:
+      if (end_) {
+        AtHeadersEnd();
+      } else {
+        incomplete_logical_header = true;
+      }
+      break;
+
+    case PUSH_PROMISE:
+      if (end_) {
+        AtPushPromiseEnd();
+      } else {
+        incomplete_logical_header = true;
+      }
+      break;
+
+    case CONTINUATION:
+      if (end_) {
+        if (headers_ir_) {
+          AtHeadersEnd();
+        } else if (push_promise_ir_) {
+          AtPushPromiseEnd();
+        } else {
+          LOG(FATAL) << "Where is the SpdyFrameIR for the headers!";
+        }
+      } else {
+        incomplete_logical_header = true;
+      }
+      break;
+
+    case UNSET:
+      // Except for the frame types above, the others don't leave any record
+      // in the state of this object. Make sure nothing got left by accident.
+      CHECK_EQ(data_.get(), nullptr);
+      CHECK_EQ(goaway_description_.get(), nullptr);
+      CHECK_EQ(goaway_ir_.get(), nullptr);
+      CHECK_EQ(headers_.get(), nullptr);
+      CHECK_EQ(headers_handler_.get(), nullptr);
+      CHECK_EQ(headers_ir_.get(), nullptr);
+      CHECK_EQ(push_promise_ir_.get(), nullptr);
+      CHECK_EQ(settings_.get(), nullptr);
+      CHECK_EQ(settings_ir_.get(), nullptr);
+      break;
+
+    default:
+      SPDY_BUG << "Expected UNSET, instead frame_type_==" << frame_type_;
+      return false;
+  }
+  frame_type_ = UNSET;
+  stream_id_ = 0;
+  end_ = false;
+  ack_ = false;
+  if (!incomplete_logical_header) {
+    fin_ = false;
+  }
+  return true;
+}
+
+// Overridden methods from SpdyFramerVisitorInterface in alpha order...
+
+void SpdyTestDeframerImpl::OnAltSvc(
+    SpdyStreamId stream_id,
+    SpdyStringPiece origin,
+    const SpdyAltSvcWireFormat::AlternativeServiceVector& altsvc_vector) {
+  DVLOG(1) << "OnAltSvc stream_id: " << stream_id;
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK_GT(stream_id, 0u);
+  auto ptr = SpdyMakeUnique<SpdyAltSvcIR>(stream_id);
+  ptr->set_origin(SpdyString(origin));
+  for (auto& altsvc : altsvc_vector) {
+    ptr->add_altsvc(altsvc);
+  }
+  listener_->OnAltSvc(std::move(ptr));
+}
+
+// A CONTINUATION frame contains a Header Block Fragment, and immediately
+// follows another frame that contains a Header Block Fragment (HEADERS,
+// PUSH_PROMISE or CONTINUATION). The last such frame has the END flag set.
+// SpdyFramer ensures that the behavior is correct before calling the visitor.
+void SpdyTestDeframerImpl::OnContinuation(SpdyStreamId stream_id, bool end) {
+  DVLOG(1) << "OnContinuation stream_id: " << stream_id;
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK_GT(stream_id, 0u);
+  CHECK_NE(nullptr, headers_.get());
+  frame_type_ = CONTINUATION;
+
+  stream_id_ = stream_id;
+  end_ = end;
+}
+
+// Note that length includes the padding length (0 to 256, when the optional
+// padding length field is counted). Padding comes after the payload, both
+// for DATA frames and for control frames.
+void SpdyTestDeframerImpl::OnDataFrameHeader(SpdyStreamId stream_id,
+                                             size_t length,
+                                             bool fin) {
+  DVLOG(1) << "OnDataFrameHeader stream_id: " << stream_id;
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK_GT(stream_id, 0u);
+  CHECK_EQ(data_.get(), nullptr);
+  frame_type_ = DATA;
+
+  stream_id_ = stream_id;
+  fin_ = fin;
+  data_len_ = length;
+  data_ = SpdyMakeUnique<SpdyString>();
+}
+
+// The SpdyFramer will not process any more data at this point.
+void SpdyTestDeframerImpl::OnError(
+    http2::Http2DecoderAdapter::SpdyFramerError error) {
+  DVLOG(1) << "SpdyFramer detected an error in the stream: "
+           << http2::Http2DecoderAdapter::SpdyFramerErrorToString(error)
+           << "     frame_type_: " << Http2FrameTypeToString(frame_type_);
+  listener_->OnError(error, this);
+}
+
+// Received a GOAWAY frame from the peer. The last stream id it accepted from us
+// is |last_accepted_stream_id|. |status| is a protocol defined error code.
+// The frame may also contain data. After this OnGoAwayFrameData will be called
+// for any non-zero amount of data, and after that it will be called with len==0
+// to indicate the end of the GOAWAY frame.
+void SpdyTestDeframerImpl::OnGoAway(SpdyStreamId last_good_stream_id,
+                                    SpdyErrorCode error_code) {
+  DVLOG(1) << "OnGoAway last_good_stream_id: " << last_good_stream_id
+           << "     error code: " << error_code;
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  frame_type_ = GOAWAY;
+  goaway_ir_ =
+      SpdyMakeUnique<SpdyGoAwayIR>(last_good_stream_id, error_code, "");
+  goaway_description_ = SpdyMakeUnique<SpdyString>();
+}
+
+// If len==0 then we've reached the end of the GOAWAY frame.
+bool SpdyTestDeframerImpl::OnGoAwayFrameData(const char* goaway_data,
+                                             size_t len) {
+  DVLOG(1) << "OnGoAwayFrameData";
+  CHECK_EQ(frame_type_, GOAWAY)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK(goaway_description_ != nullptr);
+  goaway_description_->append(goaway_data, len);
+  return true;
+}
+
+SpdyHeadersHandlerInterface* SpdyTestDeframerImpl::OnHeaderFrameStart(
+    SpdyStreamId stream_id) {
+  return this;
+}
+
+void SpdyTestDeframerImpl::OnHeaderFrameEnd(SpdyStreamId stream_id) {
+  DVLOG(1) << "OnHeaderFrameEnd stream_id: " << stream_id;
+}
+
+// Received the fixed portion of a HEADERS frame. Called before the variable
+// length (including zero length) Header Block Fragment is processed. If fin
+// is true then there will be no DATA or trailing HEADERS after this HEADERS
+// frame.
+// If end is true, then there will be no CONTINUATION frame(s) following this
+// frame; else if true then there will be CONTINATION frames(s) immediately
+// following this frame, terminated by a CONTINUATION frame with end==true.
+void SpdyTestDeframerImpl::OnHeaders(SpdyStreamId stream_id,
+                                     bool has_priority,
+                                     int weight,
+                                     SpdyStreamId parent_stream_id,
+                                     bool exclusive,
+                                     bool fin,
+                                     bool end) {
+  DVLOG(1) << "OnHeaders stream_id: " << stream_id;
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK_GT(stream_id, 0u);
+  frame_type_ = HEADERS;
+
+  stream_id_ = stream_id;
+  fin_ = fin;
+  end_ = end;
+
+  headers_ = SpdyMakeUnique<StringPairVector>();
+  headers_handler_ = SpdyMakeUnique<TestHeadersHandler>();
+  headers_ir_ = SpdyMakeUnique<SpdyHeadersIR>(stream_id);
+  headers_ir_->set_fin(fin);
+  if (has_priority) {
+    headers_ir_->set_has_priority(true);
+    headers_ir_->set_weight(weight);
+    headers_ir_->set_parent_stream_id(parent_stream_id);
+    headers_ir_->set_exclusive(exclusive);
+  }
+}
+
+// The HTTP/2 protocol refers to the payload, |unique_id| here, as 8 octets of
+// opaque data that is to be echoed back to the sender, with the ACK bit added.
+// It isn't defined as a counter,
+// or frame id, as the SpdyPingId naming might imply.
+// Responding to a PING is supposed to be at the highest priority.
+void SpdyTestDeframerImpl::OnPing(uint64_t unique_id, bool is_ack) {
+  DVLOG(1) << "OnPing unique_id: " << unique_id
+           << "      is_ack: " << (is_ack ? "true" : "false");
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  auto ptr = SpdyMakeUnique<SpdyPingIR>(unique_id);
+  if (is_ack) {
+    ptr->set_is_ack(is_ack);
+    listener_->OnPingAck(std::move(ptr));
+  } else {
+    listener_->OnPing(std::move(ptr));
+  }
+}
+
+void SpdyTestDeframerImpl::OnPriority(SpdyStreamId stream_id,
+                                      SpdyStreamId parent_stream_id,
+                                      int weight,
+                                      bool exclusive) {
+  DVLOG(1) << "OnPriority stream_id: " << stream_id;
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK_GT(stream_id, 0u);
+
+  listener_->OnPriority(SpdyMakeUnique<SpdyPriorityIR>(
+      stream_id, parent_stream_id, weight, exclusive));
+}
+
+void SpdyTestDeframerImpl::OnPushPromise(SpdyStreamId stream_id,
+                                         SpdyStreamId promised_stream_id,
+                                         bool end) {
+  DVLOG(1) << "OnPushPromise stream_id: " << stream_id;
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK_GT(stream_id, 0u);
+
+  frame_type_ = PUSH_PROMISE;
+  stream_id_ = stream_id;
+  end_ = end;
+
+  headers_ = SpdyMakeUnique<StringPairVector>();
+  headers_handler_ = SpdyMakeUnique<TestHeadersHandler>();
+  push_promise_ir_ =
+      SpdyMakeUnique<SpdyPushPromiseIR>(stream_id, promised_stream_id);
+}
+
+// Closes the specified stream. After this the sender may still send PRIORITY
+// frames for this stream, which we can ignore.
+void SpdyTestDeframerImpl::OnRstStream(SpdyStreamId stream_id,
+                                       SpdyErrorCode error_code) {
+  DVLOG(1) << "OnRstStream stream_id: " << stream_id
+           << "     error code: " << error_code;
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK_GT(stream_id, 0u);
+
+  listener_->OnRstStream(
+      SpdyMakeUnique<SpdyRstStreamIR>(stream_id, error_code));
+}
+
+// Called for an individual setting. There is no negotiation; the sender is
+// stating the value that the sender is using.
+void SpdyTestDeframerImpl::OnSetting(SpdySettingsId id, uint32_t value) {
+  DVLOG(1) << "OnSetting id: " << id << std::hex << "    value: " << value;
+  CHECK_EQ(frame_type_, SETTINGS)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK(settings_ != nullptr);
+  SpdyKnownSettingsId known_id;
+  if (ParseSettingsId(id, &known_id)) {
+    settings_->push_back(std::make_pair(known_id, value));
+    settings_ir_->AddSetting(known_id, value);
+  }
+}
+
+// Called at the start of a SETTINGS frame with setting entries, but not the
+// (required) ACK of a SETTINGS frame. There is no stream_id because
+// the settings apply to the entire connection, not to an individual stream.
+void SpdyTestDeframerImpl::OnSettings() {
+  DVLOG(1) << "OnSettings";
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK_EQ(nullptr, settings_ir_.get());
+  CHECK_EQ(nullptr, settings_.get());
+  frame_type_ = SETTINGS;
+  ack_ = false;
+
+  settings_ = SpdyMakeUnique<SettingVector>();
+  settings_ir_ = SpdyMakeUnique<SpdySettingsIR>();
+}
+
+void SpdyTestDeframerImpl::OnSettingsAck() {
+  DVLOG(1) << "OnSettingsAck";
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  auto ptr = SpdyMakeUnique<SpdySettingsIR>();
+  ptr->set_is_ack(true);
+  listener_->OnSettingsAck(std::move(ptr));
+}
+
+void SpdyTestDeframerImpl::OnSettingsEnd() {
+  DVLOG(1) << "OnSettingsEnd";
+  CHECK_EQ(frame_type_, SETTINGS)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK(!ack_);
+  CHECK_NE(nullptr, settings_ir_.get());
+  CHECK_NE(nullptr, settings_.get());
+  listener_->OnSettings(std::move(settings_ir_), std::move(settings_));
+  frame_type_ = UNSET;
+}
+
+// Called for a zero length DATA frame with the END_STREAM flag set, or at the
+// end a complete HPACK block (and its padding) that started with a HEADERS
+// frame with the END_STREAM flag set. Doesn't apply to PUSH_PROMISE frames
+// because they don't have END_STREAM flags.
+void SpdyTestDeframerImpl::OnStreamEnd(SpdyStreamId stream_id) {
+  DVLOG(1) << "OnStreamEnd stream_id: " << stream_id;
+  CHECK_EQ(stream_id_, stream_id);
+  CHECK(frame_type_ == DATA || frame_type_ == HEADERS ||
+        frame_type_ == CONTINUATION)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK(fin_);
+}
+
+// The data arg points into the non-padding payload of a DATA frame.
+// This must be a DATA frame (i.e. this method will not be
+// called for HEADERS or CONTINUATION frames).
+// This method may be called multiple times for a single DATA frame, depending
+// upon buffer boundaries.
+void SpdyTestDeframerImpl::OnStreamFrameData(SpdyStreamId stream_id,
+                                             const char* data,
+                                             size_t len) {
+  DVLOG(1) << "OnStreamFrameData stream_id: " << stream_id
+           << "    len: " << len;
+  CHECK_EQ(stream_id_, stream_id);
+  CHECK_EQ(frame_type_, DATA);
+  data_->append(data, len);
+}
+
+// Called when receiving the padding length field at the start of the DATA frame
+// payload. value will be in the range 0 to 255.
+void SpdyTestDeframerImpl::OnStreamPadLength(SpdyStreamId stream_id,
+                                             size_t value) {
+  DVLOG(1) << "OnStreamPadding stream_id: " << stream_id
+           << "    value: " << value;
+  CHECK(frame_type_ == DATA || frame_type_ == HEADERS ||
+        frame_type_ == PUSH_PROMISE)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK_EQ(stream_id_, stream_id);
+  CHECK_GE(255u, value);
+  // Count the padding length byte against total padding.
+  padding_len_ += 1;
+  CHECK_EQ(1u, padding_len_);
+}
+
+// Called when padding is skipped over at the end of the DATA frame. len will
+// be in the range 1 to 255.
+void SpdyTestDeframerImpl::OnStreamPadding(SpdyStreamId stream_id, size_t len) {
+  DVLOG(1) << "OnStreamPadding stream_id: " << stream_id << "    len: " << len;
+  CHECK(frame_type_ == DATA || frame_type_ == HEADERS ||
+        frame_type_ == PUSH_PROMISE)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK_EQ(stream_id_, stream_id);
+  CHECK_LE(1u, len);
+  CHECK_GE(255u, len);
+  padding_len_ += len;
+  CHECK_LE(padding_len_, 256u) << "len=" << len;
+}
+
+// WINDOW_UPDATE is supposed to be hop-by-hop, according to the spec.
+// stream_id is 0 if the update applies to the connection, else stream_id
+// will be the id of a stream previously seen, which maybe half or fully
+// closed.
+void SpdyTestDeframerImpl::OnWindowUpdate(SpdyStreamId stream_id,
+                                          int delta_window_size) {
+  DVLOG(1) << "OnWindowUpdate stream_id: " << stream_id
+           << "    delta_window_size: " << delta_window_size;
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK_NE(0, delta_window_size);
+
+  listener_->OnWindowUpdate(
+      SpdyMakeUnique<SpdyWindowUpdateIR>(stream_id, delta_window_size));
+}
+
+// Return true to indicate that the stream_id is valid; if not valid then
+// SpdyFramer considers the connection corrupted. Requires keeping track
+// of the set of currently open streams. For now we'll assume that unknown
+// frame types are unsupported.
+bool SpdyTestDeframerImpl::OnUnknownFrame(SpdyStreamId stream_id,
+                                          uint8_t frame_type) {
+  DVLOG(1) << "OnAltSvc stream_id: " << stream_id;
+  CHECK_EQ(frame_type_, UNSET)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  frame_type_ = UNKNOWN;
+
+  stream_id_ = stream_id;
+  return false;
+}
+
+// Callbacks defined in SpdyHeadersHandlerInterface.
+
+void SpdyTestDeframerImpl::OnHeaderBlockStart() {
+  CHECK(frame_type_ == HEADERS || frame_type_ == PUSH_PROMISE)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK(headers_ != nullptr);
+  CHECK_EQ(0u, headers_->size());
+  got_hpack_end_ = false;
+}
+
+void SpdyTestDeframerImpl::OnHeader(SpdyStringPiece key,
+                                    SpdyStringPiece value) {
+  CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION ||
+        frame_type_ == PUSH_PROMISE)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK(!got_hpack_end_);
+  HTTP2_DIE_IF_NULL(headers_)->emplace_back(SpdyString(key), SpdyString(value));
+  HTTP2_DIE_IF_NULL(headers_handler_)->OnHeader(key, value);
+}
+
+void SpdyTestDeframerImpl::OnHeaderBlockEnd(
+    size_t /* header_bytes_parsed */,
+    size_t /* compressed_header_bytes_parsed */) {
+  CHECK(headers_ != nullptr);
+  CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION ||
+        frame_type_ == PUSH_PROMISE)
+      << "   frame_type_=" << Http2FrameTypeToString(frame_type_);
+  CHECK(end_);
+  CHECK(!got_hpack_end_);
+  got_hpack_end_ = true;
+}
+
+class LoggingSpdyDeframerDelegate : public SpdyDeframerVisitorInterface {
+ public:
+  explicit LoggingSpdyDeframerDelegate(
+      std::unique_ptr<SpdyDeframerVisitorInterface> wrapped)
+      : wrapped_(std::move(wrapped)) {
+    if (!wrapped_) {
+      wrapped_ = SpdyMakeUnique<SpdyDeframerVisitorInterface>();
+    }
+  }
+  ~LoggingSpdyDeframerDelegate() override = default;
+
+  void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnAltSvc";
+    wrapped_->OnAltSvc(std::move(frame));
+  }
+  void OnData(std::unique_ptr<SpdyDataIR> frame) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnData";
+    wrapped_->OnData(std::move(frame));
+  }
+  void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnGoAway";
+    wrapped_->OnGoAway(std::move(frame));
+  }
+
+  // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+  // significantly modifies the headers, so the actual header entries (name
+  // and value strings) are provided in a vector.
+  void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame,
+                 std::unique_ptr<StringPairVector> headers) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnHeaders";
+    wrapped_->OnHeaders(std::move(frame), std::move(headers));
+  }
+
+  void OnPing(std::unique_ptr<SpdyPingIR> frame) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPing";
+    wrapped_->OnPing(std::move(frame));
+  }
+  void OnPingAck(std::unique_ptr<SpdyPingIR> frame) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPingAck";
+    wrapped_->OnPingAck(std::move(frame));
+  }
+
+  void OnPriority(std::unique_ptr<SpdyPriorityIR> frame) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPriority";
+    wrapped_->OnPriority(std::move(frame));
+  }
+
+  // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+  // significantly modifies the headers, so the actual header entries (name
+  // and value strings) are provided in a vector.
+  void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame,
+                     std::unique_ptr<StringPairVector> headers) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPushPromise";
+    wrapped_->OnPushPromise(std::move(frame), std::move(headers));
+  }
+
+  void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnRstStream";
+    wrapped_->OnRstStream(std::move(frame));
+  }
+
+  // SpdySettingsIR has a map for settings, so loses info about the order of
+  // settings, and whether the same setting appeared more than once, so the
+  // the actual settings (parameter and value) are provided in a vector.
+  void OnSettings(std::unique_ptr<SpdySettingsIR> frame,
+                  std::unique_ptr<SettingVector> settings) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnSettings";
+    wrapped_->OnSettings(std::move(frame), std::move(settings));
+  }
+
+  // A settings frame with an ACK has no content, but for uniformity passing
+  // a frame with the ACK flag set.
+  void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnSettingsAck";
+    wrapped_->OnSettingsAck(std::move(frame));
+  }
+
+  void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnWindowUpdate";
+    wrapped_->OnWindowUpdate(std::move(frame));
+  }
+
+  // The SpdyFramer will not process any more data at this point.
+  void OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
+               SpdyTestDeframer* deframer) override {
+    DVLOG(1) << "LoggingSpdyDeframerDelegate::OnError";
+    wrapped_->OnError(error, deframer);
+  }
+
+ private:
+  std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_;
+};
+
+// static
+std::unique_ptr<SpdyDeframerVisitorInterface>
+SpdyDeframerVisitorInterface::LogBeforeVisiting(
+    std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_listener) {
+  return SpdyMakeUnique<LoggingSpdyDeframerDelegate>(
+      std::move(wrapped_listener));
+}
+
+CollectedFrame::CollectedFrame() = default;
+
+CollectedFrame::CollectedFrame(CollectedFrame&& other)
+    : frame_ir(std::move(other.frame_ir)),
+      headers(std::move(other.headers)),
+      settings(std::move(other.settings)),
+      error_reported(other.error_reported) {}
+
+CollectedFrame::~CollectedFrame() = default;
+
+CollectedFrame& CollectedFrame::operator=(CollectedFrame&& other) {
+  frame_ir = std::move(other.frame_ir);
+  headers = std::move(other.headers);
+  settings = std::move(other.settings);
+  error_reported = other.error_reported;
+  return *this;
+}
+
+AssertionResult CollectedFrame::VerifyHasHeaders(
+    const StringPairVector& expected_headers) const {
+  VERIFY_NE(headers.get(), nullptr);
+  VERIFY_THAT(*headers, ::testing::ContainerEq(expected_headers));
+  return AssertionSuccess();
+}
+
+AssertionResult CollectedFrame::VerifyHasSettings(
+    const SettingVector& expected_settings) const {
+  VERIFY_NE(settings.get(), nullptr);
+  VERIFY_THAT(*settings, testing::ContainerEq(expected_settings));
+  return AssertionSuccess();
+}
+
+DeframerCallbackCollector::DeframerCallbackCollector(
+    std::vector<CollectedFrame>* collected_frames)
+    : collected_frames_(HTTP2_DIE_IF_NULL(collected_frames)) {}
+
+void DeframerCallbackCollector::OnAltSvc(
+    std::unique_ptr<SpdyAltSvcIR> frame_ir) {
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  collected_frames_->push_back(std::move(cf));
+}
+void DeframerCallbackCollector::OnData(std::unique_ptr<SpdyDataIR> frame_ir) {
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  collected_frames_->push_back(std::move(cf));
+}
+void DeframerCallbackCollector::OnGoAway(
+    std::unique_ptr<SpdyGoAwayIR> frame_ir) {
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  collected_frames_->push_back(std::move(cf));
+}
+
+// SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+// significantly modifies the headers, so the actual header entries (name
+// and value strings) are provided in a vector.
+void DeframerCallbackCollector::OnHeaders(
+    std::unique_ptr<SpdyHeadersIR> frame_ir,
+    std::unique_ptr<StringPairVector> headers) {
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  cf.headers = std::move(headers);
+  collected_frames_->push_back(std::move(cf));
+}
+
+void DeframerCallbackCollector::OnPing(std::unique_ptr<SpdyPingIR> frame_ir) {
+  EXPECT_TRUE(frame_ir && !frame_ir->is_ack());
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  collected_frames_->push_back(std::move(cf));
+}
+
+void DeframerCallbackCollector::OnPingAck(
+    std::unique_ptr<SpdyPingIR> frame_ir) {
+  EXPECT_TRUE(frame_ir && frame_ir->is_ack());
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  collected_frames_->push_back(std::move(cf));
+}
+
+void DeframerCallbackCollector::OnPriority(
+    std::unique_ptr<SpdyPriorityIR> frame_ir) {
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  collected_frames_->push_back(std::move(cf));
+}
+
+// SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+// significantly modifies the headers, so the actual header entries (name
+// and value strings) are provided in a vector.
+void DeframerCallbackCollector::OnPushPromise(
+    std::unique_ptr<SpdyPushPromiseIR> frame_ir,
+    std::unique_ptr<StringPairVector> headers) {
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  cf.headers = std::move(headers);
+  collected_frames_->push_back(std::move(cf));
+}
+
+void DeframerCallbackCollector::OnRstStream(
+    std::unique_ptr<SpdyRstStreamIR> frame_ir) {
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  collected_frames_->push_back(std::move(cf));
+}
+
+// SpdySettingsIR has a map for settings, so loses info about the order of
+// settings, and whether the same setting appeared more than once, so the
+// the actual settings (parameter and value) are provided in a vector.
+void DeframerCallbackCollector::OnSettings(
+    std::unique_ptr<SpdySettingsIR> frame_ir,
+    std::unique_ptr<SettingVector> settings) {
+  EXPECT_TRUE(frame_ir && !frame_ir->is_ack());
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  cf.settings = std::move(settings);
+  collected_frames_->push_back(std::move(cf));
+}
+
+// A settings frame_ir with an ACK has no content, but for uniformity passing
+// a frame_ir with the ACK flag set.
+void DeframerCallbackCollector::OnSettingsAck(
+    std::unique_ptr<SpdySettingsIR> frame_ir) {
+  EXPECT_TRUE(frame_ir && frame_ir->is_ack());
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  collected_frames_->push_back(std::move(cf));
+}
+
+void DeframerCallbackCollector::OnWindowUpdate(
+    std::unique_ptr<SpdyWindowUpdateIR> frame_ir) {
+  CollectedFrame cf;
+  cf.frame_ir = std::move(frame_ir);
+  collected_frames_->push_back(std::move(cf));
+}
+
+// The SpdyFramer will not process any more data at this point.
+void DeframerCallbackCollector::OnError(
+    http2::Http2DecoderAdapter::SpdyFramerError error,
+    SpdyTestDeframer* deframer) {
+  CollectedFrame cf;
+  cf.error_reported = true;
+  collected_frames_->push_back(std::move(cf));
+}
+
+}  // namespace test
+}  // namespace spdy
diff --git a/spdy/core/spdy_deframer_visitor.h b/spdy/core/spdy_deframer_visitor.h
new file mode 100644
index 0000000..50d9987
--- /dev/null
+++ b/spdy/core/spdy_deframer_visitor.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_SPDY_CORE_SPDY_DEFRAMER_VISITOR_H_
+#define QUICHE_SPDY_CORE_SPDY_DEFRAMER_VISITOR_H_
+
+// Supports testing by converting callbacks to SpdyFramerVisitorInterface into
+// callbacks to SpdyDeframerVisitorInterface, whose arguments are generally
+// SpdyFrameIR instances. This enables a test client or test backend to operate
+// at a level between the low-level callbacks of SpdyFramerVisitorInterface and
+// the much higher level of entire messages (i.e. headers, body, trailers).
+// Where possible the converter (SpdyTestDeframer) tries to preserve information
+// that might be useful to tests (e.g. the order of headers or the amount of
+// padding); the design also aims to allow tests to be concise, ideally
+// supporting gMock style EXPECT_CALL(visitor, OnHeaders(...matchers...))
+// without too much boilerplate.
+//
+// Only supports HTTP/2 for the moment.
+//
+// Example of usage:
+//
+//    SpdyFramer framer(HTTP2);
+//
+//    // Need to call SpdyTestDeframer::AtFrameEnd() after processing each
+//    // frame, so tell SpdyFramer to stop after each.
+//    framer.set_process_single_input_frame(true);
+//
+//    // Need the new OnHeader callbacks.
+//    framer.set_use_new_methods_for_test(true);
+//
+//    // Create your visitor, a subclass of SpdyDeframerVisitorInterface.
+//    // For example, using DeframerCallbackCollector to collect frames:
+//    std::vector<CollectedFrame> collected_frames;
+//    auto your_visitor = SpdyMakeUnique<DeframerCallbackCollector>(
+//        &collected_frames);
+//
+//    // Transfer ownership of your visitor to the converter, which ensures that
+//    // your visitor stays alive while the converter needs to call it.
+//    auto the_deframer = SpdyTestDeframer::CreateConverter(
+//       std::move(your_visitor));
+//
+//    // Tell the framer to notify SpdyTestDeframer of the decoded frame
+//    // details.
+//    framer.set_visitor(the_deframer.get());
+//
+//    // Process frames.
+//    SpdyStringPiece input = ...
+//    while (!input.empty() && !framer.HasError()) {
+//      size_t consumed = framer.ProcessInput(input.data(), input.size());
+//      input.remove_prefix(consumed);
+//      if (framer.state() == SpdyFramer::SPDY_READY_FOR_FRAME) {
+//        the_deframer->AtFrameEnd();
+//      }
+//    }
+//
+//    // Make sure that the correct frames were received. For example:
+//    ASSERT_EQ(collected_frames.size(), 3);
+//
+//    SpdyDataIR expected1(7 /*stream_id*/, "Data Payload");
+//    expected1.set_padding_len(17);
+//    EXPECT_TRUE(collected_frames[0].VerifyEquals(expected1));
+//
+//    // Repeat for the other frames.
+//
+// Note that you could also seed the subclass of SpdyDeframerVisitorInterface
+// with the expected frames, which it would pop-off the list as its expectations
+// are met.
+
+#include <cstdint>
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+
+namespace spdy {
+namespace test {
+
+// Non-lossy representation of a SETTINGS frame payload.
+typedef std::vector<std::pair<SpdyKnownSettingsId, uint32_t>> SettingVector;
+
+// StringPairVector is used to record information lost by SpdyHeaderBlock, in
+// particular the order of each header entry, though it doesn't expose the
+// inner details of the HPACK block, such as the type of encoding selected
+// for each header entry, nor dynamic table size changes.
+typedef std::pair<SpdyString, SpdyString> StringPair;
+typedef std::vector<StringPair> StringPairVector;
+
+// Forward decl.
+class SpdyTestDeframer;
+
+// Note that this only roughly captures the frames, as padding bytes are lost,
+// continuation frames are combined with their leading HEADERS or PUSH_PROMISE,
+// the details of the HPACK encoding are lost, leaving
+// only the list of header entries (name and value strings). If really helpful,
+// we could add a SpdyRawDeframerVisitorInterface that gets the HPACK bytes,
+// and receives continuation frames. For more info we'd need to improve
+// SpdyFramerVisitorInterface.
+class SpdyDeframerVisitorInterface {
+ public:
+  virtual ~SpdyDeframerVisitorInterface() {}
+
+  // Wrap a visitor in another SpdyDeframerVisitorInterface that will
+  // DVLOG each call, and will then forward the calls to the wrapped visitor
+  // (if provided; nullptr is OK). Takes ownership of the wrapped visitor.
+  static std::unique_ptr<SpdyDeframerVisitorInterface> LogBeforeVisiting(
+      std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_visitor);
+
+  virtual void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame) {}
+  virtual void OnData(std::unique_ptr<SpdyDataIR> frame) {}
+  virtual void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame) {}
+
+  // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+  // significantly modifies the headers, so the actual header entries (name
+  // and value strings) are provided in a vector.
+  virtual void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame,
+                         std::unique_ptr<StringPairVector> headers) {}
+
+  virtual void OnPing(std::unique_ptr<SpdyPingIR> frame) {}
+  virtual void OnPingAck(std::unique_ptr<SpdyPingIR> frame);
+  virtual void OnPriority(std::unique_ptr<SpdyPriorityIR> frame) {}
+
+  // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+  // significantly modifies the headers, so the actual header entries (name
+  // and value strings) are provided in a vector.
+  virtual void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame,
+                             std::unique_ptr<StringPairVector> headers) {}
+
+  virtual void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame) {}
+
+  // SpdySettingsIR has a map for settings, so loses info about the order of
+  // settings, and whether the same setting appeared more than once, so the
+  // the actual settings (parameter and value) are provided in a vector.
+  virtual void OnSettings(std::unique_ptr<SpdySettingsIR> frame,
+                          std::unique_ptr<SettingVector> settings) {}
+
+  // A settings frame with an ACK has no content, but for uniformity passing
+  // a frame with the ACK flag set.
+  virtual void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame);
+
+  virtual void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame) {}
+
+  // The SpdyFramer will not process any more data at this point.
+  virtual void OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
+                       SpdyTestDeframer* deframer) {}
+};
+
+class SpdyTestDeframer : public SpdyFramerVisitorInterface {
+ public:
+  ~SpdyTestDeframer() override {}
+
+  // Creates a SpdyFramerVisitorInterface that builds SpdyFrameIR concrete
+  // instances based on the callbacks it receives; when an entire frame is
+  // decoded/reconstructed it calls the passed in SpdyDeframerVisitorInterface.
+  // Transfers ownership of visitor to the new SpdyTestDeframer, which ensures
+  // that it continues to exist while the SpdyTestDeframer exists.
+  static std::unique_ptr<SpdyTestDeframer> CreateConverter(
+      std::unique_ptr<SpdyDeframerVisitorInterface> visitor);
+
+  // Call to notify the deframer that the SpdyFramer has returned after reaching
+  // the end of decoding a frame. This is used to flush info about some frame
+  // types where we don't get a clear end signal; others are flushed (i.e. the
+  // appropriate call to the SpdyDeframerVisitorInterface method is invoked)
+  // as they're decoded by SpdyFramer and it calls the deframer. See the
+  // example in the comments at the top of this file.
+  virtual bool AtFrameEnd() = 0;
+
+ protected:
+  SpdyTestDeframer() {}
+  SpdyTestDeframer(const SpdyTestDeframer&) = delete;
+  SpdyTestDeframer& operator=(const SpdyTestDeframer&) = delete;
+};
+
+// CollectedFrame holds the result of one call to SpdyDeframerVisitorInterface,
+// as recorded by DeframerCallbackCollector.
+struct CollectedFrame {
+  CollectedFrame();
+  CollectedFrame(CollectedFrame&& other);
+  ~CollectedFrame();
+  CollectedFrame& operator=(CollectedFrame&& other);
+
+  // Compare a SpdyFrameIR sub-class instance, expected_ir, against the
+  // collected SpdyFrameIR.
+  template <class T,
+            typename X =
+                typename std::enable_if<std::is_base_of<SpdyFrameIR, T>::value>>
+  ::testing::AssertionResult VerifyHasFrame(const T& expected_ir) const {
+    return VerifySpdyFrameIREquals(expected_ir, frame_ir.get());
+  }
+
+  // Compare the collected headers against a StringPairVector. Ignores
+  // this->frame_ir.
+  ::testing::AssertionResult VerifyHasHeaders(
+      const StringPairVector& expected_headers) const;
+
+  // Compare the collected settings (parameter and value pairs) against
+  // expected_settings. Ignores this->frame_ir.
+  ::testing::AssertionResult VerifyHasSettings(
+      const SettingVector& expected_settings) const;
+
+  std::unique_ptr<SpdyFrameIR> frame_ir;
+  std::unique_ptr<StringPairVector> headers;
+  std::unique_ptr<SettingVector> settings;
+  bool error_reported = false;
+};
+
+// Creates a CollectedFrame instance for each callback, storing it in the
+// vector provided to the constructor.
+class DeframerCallbackCollector : public SpdyDeframerVisitorInterface {
+ public:
+  explicit DeframerCallbackCollector(
+      std::vector<CollectedFrame>* collected_frames);
+  ~DeframerCallbackCollector() override {}
+
+  void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame_ir) override;
+  void OnData(std::unique_ptr<SpdyDataIR> frame_ir) override;
+  void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame_ir) override;
+  void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame_ir,
+                 std::unique_ptr<StringPairVector> headers) override;
+  void OnPing(std::unique_ptr<SpdyPingIR> frame_ir) override;
+  void OnPingAck(std::unique_ptr<SpdyPingIR> frame_ir) override;
+  void OnPriority(std::unique_ptr<SpdyPriorityIR> frame_ir) override;
+  void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame_ir,
+                     std::unique_ptr<StringPairVector> headers) override;
+  void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame_ir) override;
+  void OnSettings(std::unique_ptr<SpdySettingsIR> frame_ir,
+                  std::unique_ptr<SettingVector> settings) override;
+  void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame_ir) override;
+  void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame_ir) override;
+  void OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
+               SpdyTestDeframer* deframer) override;
+
+ private:
+  std::vector<CollectedFrame>* collected_frames_;
+};
+
+}  // namespace test
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_DEFRAMER_VISITOR_H_
diff --git a/spdy/core/spdy_deframer_visitor_test.cc b/spdy/core/spdy_deframer_visitor_test.cc
new file mode 100644
index 0000000..ede1a20
--- /dev/null
+++ b/spdy/core/spdy_deframer_visitor_test.cc
@@ -0,0 +1,247 @@
+// 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/spdy/core/spdy_deframer_visitor.h"
+
+#include <stdlib.h>
+
+#include <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+
+namespace spdy {
+namespace test {
+namespace {
+
+class SpdyDeframerVisitorTest : public ::testing::Test {
+ protected:
+  SpdyDeframerVisitorTest() : encoder_(SpdyFramer::ENABLE_COMPRESSION) {
+    decoder_.set_process_single_input_frame(true);
+    auto collector =
+        SpdyMakeUnique<DeframerCallbackCollector>(&collected_frames_);
+    auto log_and_collect =
+        SpdyDeframerVisitorInterface::LogBeforeVisiting(std::move(collector));
+    deframer_ = SpdyTestDeframer::CreateConverter(std::move(log_and_collect));
+    decoder_.set_visitor(deframer_.get());
+  }
+
+  bool DeframeInput(const char* input, size_t size) {
+    size_t input_remaining = size;
+    while (input_remaining > 0 &&
+           decoder_.spdy_framer_error() ==
+               http2::Http2DecoderAdapter::SPDY_NO_ERROR) {
+      // To make the tests more interesting, we feed random (and small) chunks
+      // into the framer.  This simulates getting strange-sized reads from
+      // the socket.
+      const size_t kMaxReadSize = 32;
+      size_t bytes_read =
+          (random_.Uniform(std::min(input_remaining, kMaxReadSize))) + 1;
+      size_t bytes_processed = decoder_.ProcessInput(input, bytes_read);
+      input_remaining -= bytes_processed;
+      input += bytes_processed;
+      if (decoder_.state() ==
+          http2::Http2DecoderAdapter::SPDY_READY_FOR_FRAME) {
+        deframer_->AtFrameEnd();
+      }
+    }
+    return (input_remaining == 0 &&
+            decoder_.spdy_framer_error() ==
+                http2::Http2DecoderAdapter::SPDY_NO_ERROR);
+  }
+
+  SpdyFramer encoder_;
+  http2::Http2DecoderAdapter decoder_;
+  std::vector<CollectedFrame> collected_frames_;
+  std::unique_ptr<SpdyTestDeframer> deframer_;
+
+ private:
+  http2::test::Http2Random random_;
+};
+
+TEST_F(SpdyDeframerVisitorTest, DataFrame) {
+  const char kFrameData[] = {
+      0x00, 0x00, 0x0d,        // Length = 13.
+      0x00,                    // DATA
+      0x08,                    // PADDED
+      0x00, 0x00, 0x00, 0x01,  // Stream 1
+      0x07,                    // Pad length field.
+      'h',  'e',  'l',  'l',   // Data
+      'o',                     // More Data
+      0x00, 0x00, 0x00, 0x00,  // Padding
+      0x00, 0x00, 0x00         // More Padding
+  };
+
+  EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData));
+  ASSERT_EQ(1u, collected_frames_.size());
+  const CollectedFrame& cf0 = collected_frames_[0];
+  ASSERT_NE(cf0.frame_ir, nullptr);
+
+  SpdyDataIR expected_ir(/* stream_id = */ 1, "hello");
+  expected_ir.set_padding_len(8);
+  EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+}
+
+TEST_F(SpdyDeframerVisitorTest, HeaderFrameWithContinuation) {
+  const char kFrameData[] = {
+      0x00, 0x00, 0x05,        // Payload Length: 5
+      0x01,                    // Type: HEADERS
+      0x09,                    // Flags: PADDED | END_STREAM
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x04,                    // Padding Length: 4
+      0x00, 0x00, 0x00, 0x00,  // Padding
+      /* Second Frame */
+      0x00, 0x00, 0x12,        // Payload Length: 18
+      0x09,                    // Type: CONTINUATION
+      0x04,                    // Flags: END_HEADERS
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00,                    // Unindexed, literal name & value
+      0x03, 0x62, 0x61, 0x72,  // Name len and name (3, "bar")
+      0x03, 0x66, 0x6f, 0x6f,  // Value len and value (3, "foo")
+      0x00,                    // Unindexed, literal name & value
+      0x03, 0x66, 0x6f, 0x6f,  // Name len and name (3, "foo")
+      0x03, 0x62, 0x61, 0x72,  // Value len and value (3, "bar")
+  };
+
+  EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData));
+  ASSERT_EQ(1u, collected_frames_.size());
+  const CollectedFrame& cf0 = collected_frames_[0];
+
+  StringPairVector headers;
+  headers.push_back({"bar", "foo"});
+  headers.push_back({"foo", "bar"});
+
+  EXPECT_TRUE(cf0.VerifyHasHeaders(headers));
+
+  SpdyHeadersIR expected_ir(/* stream_id = */ 1);
+  // Yet again SpdyFramerVisitorInterface is lossy: it doesn't call OnPadding
+  // for HEADERS, just for DATA. Sigh.
+  //    expected_ir.set_padding_len(5);
+  expected_ir.set_fin(true);
+  for (const auto& nv : headers) {
+    expected_ir.SetHeader(nv.first, nv.second);
+  }
+
+  EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+
+  // Confirm that mismatches are also detected.
+  headers.push_back({"baz", "bing"});
+  EXPECT_FALSE(cf0.VerifyHasHeaders(headers));
+  EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+
+  headers.pop_back();
+  EXPECT_TRUE(cf0.VerifyHasHeaders(headers));
+  EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+
+  expected_ir.SetHeader("baz", "bing");
+  EXPECT_FALSE(cf0.VerifyHasFrame(expected_ir));
+  EXPECT_TRUE(cf0.VerifyHasHeaders(headers));
+}
+
+TEST_F(SpdyDeframerVisitorTest, PriorityFrame) {
+  const char kFrameData[] = {
+      0x00,   0x00, 0x05,        // Length: 5
+      0x02,                      //   Type: PRIORITY
+      0x00,                      //  Flags: none
+      0x00,   0x00, 0x00, 0x65,  // Stream: 101
+      '\x80', 0x00, 0x00, 0x01,  // Parent: 1 (Exclusive)
+      0x10,                      // Weight: 17
+  };
+
+  EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData));
+  ASSERT_EQ(1u, collected_frames_.size());
+  const CollectedFrame& cf0 = collected_frames_[0];
+
+  SpdyPriorityIR expected_ir(/* stream_id = */ 101,
+                             /* parent_stream_id = */ 1, /* weight = */ 17,
+                             /* exclusive = */ true);
+  EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+
+  // Confirm that mismatches are also detected.
+  EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 1, 16, true)));
+  EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 50, 17, true)));
+  EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(201, 1, 17, true)));
+  EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 1, 17, false)));
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_RstStreamFrame) {
+  // TODO(jamessynge): Please implement.
+}
+
+TEST_F(SpdyDeframerVisitorTest, SettingsFrame) {
+  // Settings frame with two entries for the same parameter but with different
+  // values. The last one will be in the decoded SpdySettingsIR, but the vector
+  // of settings will have both, in the same order.
+  const char kFrameData[] = {
+      0x00, 0x00, 0x0c,          // Length
+      0x04,                      // Type (SETTINGS)
+      0x00,                      // Flags
+      0x00, 0x00, 0x00, 0x00,    // Stream id (must be zero)
+      0x00, 0x04,                // Setting id (SETTINGS_INITIAL_WINDOW_SIZE)
+      0x0a, 0x0b, 0x0c, 0x0d,    // Setting value
+      0x00, 0x04,                // Setting id (SETTINGS_INITIAL_WINDOW_SIZE)
+      0x00, 0x00, 0x00, '\xff',  // Setting value
+  };
+
+  EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData));
+  ASSERT_EQ(1u, collected_frames_.size());
+  const CollectedFrame& cf0 = collected_frames_[0];
+  ASSERT_NE(cf0.frame_ir, nullptr);
+
+  SpdySettingsIR expected_ir;
+  expected_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 255);
+  EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+
+  SettingVector expected_settings;
+  expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 0x0a0b0c0d});
+  expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 255});
+
+  EXPECT_TRUE(cf0.VerifyHasSettings(expected_settings));
+
+  // Confirm that mismatches are also detected.
+  expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 65536});
+  EXPECT_FALSE(cf0.VerifyHasSettings(expected_settings));
+
+  expected_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 65536);
+  EXPECT_FALSE(cf0.VerifyHasFrame(expected_ir));
+
+  SpdySettingsIR unexpected_ir;
+  unexpected_ir.set_is_ack(true);
+  EXPECT_FALSE(cf0.VerifyHasFrame(unexpected_ir));
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_PushPromiseFrame) {
+  // TODO(jamessynge): Please implement.
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_PingFrame) {
+  // TODO(jamessynge): Please implement.
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_GoAwayFrame) {
+  // TODO(jamessynge): Please implement.
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_WindowUpdateFrame) {
+  // TODO(jamessynge): Please implement.
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_AltSvcFrame) {
+  // TODO(jamessynge): Please implement.
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace spdy
diff --git a/spdy/core/spdy_frame_builder.cc b/spdy/core/spdy_frame_builder.cc
new file mode 100644
index 0000000..a056b70
--- /dev/null
+++ b/spdy/core/spdy_frame_builder.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2012 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/spdy/core/spdy_frame_builder.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <new>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+
+namespace spdy {
+
+SpdyFrameBuilder::SpdyFrameBuilder(size_t size)
+    : buffer_(new char[size]), capacity_(size), length_(0), offset_(0) {}
+
+SpdyFrameBuilder::SpdyFrameBuilder(size_t size, ZeroCopyOutputBuffer* output)
+    : buffer_(output == nullptr ? new char[size] : nullptr),
+      output_(output),
+      capacity_(size),
+      length_(0),
+      offset_(0) {}
+
+SpdyFrameBuilder::~SpdyFrameBuilder() = default;
+
+char* SpdyFrameBuilder::GetWritableBuffer(size_t length) {
+  if (!CanWrite(length)) {
+    return nullptr;
+  }
+  return buffer_.get() + offset_ + length_;
+}
+
+char* SpdyFrameBuilder::GetWritableOutput(size_t length,
+                                          size_t* actual_length) {
+  char* dest = nullptr;
+  int size = 0;
+
+  if (!CanWrite(length)) {
+    return nullptr;
+  }
+  output_->Next(&dest, &size);
+  *actual_length = std::min<size_t>(length, size);
+  return dest;
+}
+
+bool SpdyFrameBuilder::Seek(size_t length) {
+  if (!CanWrite(length)) {
+    return false;
+  }
+  if (output_ == nullptr) {
+    length_ += length;
+  } else {
+    output_->AdvanceWritePtr(length);
+    length_ += length;
+  }
+  return true;
+}
+
+bool SpdyFrameBuilder::BeginNewFrame(SpdyFrameType type,
+                                     uint8_t flags,
+                                     SpdyStreamId stream_id) {
+  uint8_t raw_frame_type = SerializeFrameType(type);
+  DCHECK(IsDefinedFrameType(raw_frame_type));
+  DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+  bool success = true;
+  if (length_ > 0) {
+    SPDY_BUG << "SpdyFrameBuilder doesn't have a clean state when BeginNewFrame"
+             << "is called. Leftover length_ is " << length_;
+    offset_ += length_;
+    length_ = 0;
+  }
+
+  success &= WriteUInt24(capacity_ - offset_ - kFrameHeaderSize);
+  success &= WriteUInt8(raw_frame_type);
+  success &= WriteUInt8(flags);
+  success &= WriteUInt32(stream_id);
+  DCHECK_EQ(kDataFrameMinimumSize, length_);
+  return success;
+}
+
+bool SpdyFrameBuilder::BeginNewFrame(SpdyFrameType type,
+                                     uint8_t flags,
+                                     SpdyStreamId stream_id,
+                                     size_t length) {
+  uint8_t raw_frame_type = SerializeFrameType(type);
+  DCHECK(IsDefinedFrameType(raw_frame_type));
+  DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+  SPDY_BUG_IF(length > kHttp2DefaultFramePayloadLimit)
+      << "Frame length  " << length_ << " is longer than frame size limit.";
+  return BeginNewFrameInternal(raw_frame_type, flags, stream_id, length);
+}
+
+bool SpdyFrameBuilder::BeginNewUncheckedFrame(uint8_t raw_frame_type,
+                                              uint8_t flags,
+                                              SpdyStreamId stream_id,
+                                              size_t length) {
+  return BeginNewFrameInternal(raw_frame_type, flags, stream_id, length);
+}
+
+bool SpdyFrameBuilder::BeginNewFrameInternal(uint8_t raw_frame_type,
+                                             uint8_t flags,
+                                             SpdyStreamId stream_id,
+                                             size_t length) {
+  DCHECK_EQ(length, length & kLengthMask);
+  bool success = true;
+
+  offset_ += length_;
+  length_ = 0;
+
+  success &= WriteUInt24(length);
+  success &= WriteUInt8(raw_frame_type);
+  success &= WriteUInt8(flags);
+  success &= WriteUInt32(stream_id);
+  DCHECK_EQ(kDataFrameMinimumSize, length_);
+  return success;
+}
+
+bool SpdyFrameBuilder::WriteStringPiece32(const SpdyStringPiece value) {
+  if (!WriteUInt32(value.size())) {
+    return false;
+  }
+
+  return WriteBytes(value.data(), value.size());
+}
+
+bool SpdyFrameBuilder::WriteBytes(const void* data, uint32_t data_len) {
+  if (!CanWrite(data_len)) {
+    return false;
+  }
+
+  if (output_ == nullptr) {
+    char* dest = GetWritableBuffer(data_len);
+    memcpy(dest, data, data_len);
+    Seek(data_len);
+  } else {
+    char* dest = nullptr;
+    size_t size = 0;
+    size_t total_written = 0;
+    const char* data_ptr = reinterpret_cast<const char*>(data);
+    while (data_len > 0) {
+      dest = GetWritableOutput(data_len, &size);
+      if (dest == nullptr || size == 0) {
+        // Unable to make progress.
+        return false;
+      }
+      uint32_t to_copy = std::min<uint32_t>(data_len, size);
+      const char* src = data_ptr + total_written;
+      memcpy(dest, src, to_copy);
+      Seek(to_copy);
+      data_len -= to_copy;
+      total_written += to_copy;
+    }
+  }
+  return true;
+}
+
+bool SpdyFrameBuilder::CanWrite(size_t length) const {
+  if (length > kLengthMask) {
+    DCHECK(false);
+    return false;
+  }
+
+  if (output_ == nullptr) {
+    if (offset_ + length_ + length > capacity_) {
+      DLOG(FATAL) << "Requested: " << length << " capacity: " << capacity_
+                  << " used: " << offset_ + length_;
+      return false;
+    }
+  } else {
+    if (length > output_->BytesFree()) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_frame_builder.h b/spdy/core/spdy_frame_builder.h
new file mode 100644
index 0000000..c569c8c
--- /dev/null
+++ b/spdy/core/spdy_frame_builder.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2012 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_SPDY_CORE_SPDY_FRAME_BUILDER_H_
+#define QUICHE_SPDY_CORE_SPDY_FRAME_BUILDER_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_test_utils_prod.h"
+
+namespace spdy {
+
+// This class provides facilities for basic binary value packing
+// into Spdy frames.
+//
+// The SpdyFrameBuilder supports appending primitive values (int, string, etc)
+// to a frame instance.  The SpdyFrameBuilder grows its internal memory buffer
+// dynamically to hold the sequence of primitive values.   The internal memory
+// buffer is exposed as the "data" of the SpdyFrameBuilder.
+class SPDY_EXPORT_PRIVATE SpdyFrameBuilder {
+ public:
+  // Initializes a SpdyFrameBuilder with a buffer of given size
+  explicit SpdyFrameBuilder(size_t size);
+  // Doesn't take ownership of output.
+  SpdyFrameBuilder(size_t size, ZeroCopyOutputBuffer* output);
+
+  ~SpdyFrameBuilder();
+
+  // Returns the total size of the SpdyFrameBuilder's data, which may include
+  // multiple frames.
+  size_t length() const { return offset_ + length_; }
+
+  // Seeks forward by the given number of bytes. Useful in conjunction with
+  // GetWriteableBuffer() above.
+  bool Seek(size_t length);
+
+  // Populates this frame with a HTTP2 frame prefix using length information
+  // from |capacity_|. The given type must be a control frame type.
+  bool BeginNewFrame(SpdyFrameType type, uint8_t flags, SpdyStreamId stream_id);
+
+  // Populates this frame with a HTTP2 frame prefix with type and length
+  // information.  |type| must be a defined frame type.
+  bool BeginNewFrame(SpdyFrameType type,
+                     uint8_t flags,
+                     SpdyStreamId stream_id,
+                     size_t length);
+
+  // Populates this frame with a HTTP2 frame prefix with type and length
+  // information.  |raw_frame_type| may be a defined or undefined frame type.
+  bool BeginNewUncheckedFrame(uint8_t raw_frame_type,
+                              uint8_t flags,
+                              SpdyStreamId stream_id,
+                              size_t length);
+
+  // Takes the buffer from the SpdyFrameBuilder.
+  SpdySerializedFrame take() {
+    SPDY_BUG_IF(output_ != nullptr) << "ZeroCopyOutputBuffer is used to build "
+                                    << "frames. take() shouldn't be called";
+    SPDY_BUG_IF(kMaxFrameSizeLimit < length_)
+        << "Frame length " << length_
+        << " is longer than the maximum possible allowed length.";
+    SpdySerializedFrame rv(buffer_.release(), length(), true);
+    capacity_ = 0;
+    length_ = 0;
+    offset_ = 0;
+    return rv;
+  }
+
+  // Methods for adding to the payload.  These values are appended to the end
+  // of the SpdyFrameBuilder payload. Note - binary integers are converted from
+  // host to network form.
+  bool WriteUInt8(uint8_t value) { return WriteBytes(&value, sizeof(value)); }
+  bool WriteUInt16(uint16_t value) {
+    value = SpdyHostToNet16(value);
+    return WriteBytes(&value, sizeof(value));
+  }
+  bool WriteUInt24(uint32_t value) {
+    value = SpdyHostToNet32(value);
+    return WriteBytes(reinterpret_cast<char*>(&value) + 1, sizeof(value) - 1);
+  }
+  bool WriteUInt32(uint32_t value) {
+    value = SpdyHostToNet32(value);
+    return WriteBytes(&value, sizeof(value));
+  }
+  bool WriteUInt64(uint64_t value) {
+    uint32_t upper = SpdyHostToNet32(static_cast<uint32_t>(value >> 32));
+    uint32_t lower = SpdyHostToNet32(static_cast<uint32_t>(value));
+    return (WriteBytes(&upper, sizeof(upper)) &&
+            WriteBytes(&lower, sizeof(lower)));
+  }
+  bool WriteStringPiece32(const SpdyStringPiece value);
+  bool WriteBytes(const void* data, uint32_t data_len);
+
+ private:
+  SPDY_FRIEND_TEST(SpdyFrameBuilderTest, GetWritableBuffer);
+  SPDY_FRIEND_TEST(SpdyFrameBuilderTest, GetWritableOutput);
+  SPDY_FRIEND_TEST(SpdyFrameBuilderTest, GetWritableOutputNegative);
+
+  // Populates this frame with a HTTP2 frame prefix with type and length
+  // information.
+  bool BeginNewFrameInternal(uint8_t raw_frame_type,
+                             uint8_t flags,
+                             SpdyStreamId stream_id,
+                             size_t length);
+
+  // Returns a writeable buffer of given size in bytes, to be appended to the
+  // currently written frame. Does bounds checking on length but does not
+  // increment the underlying iterator. To do so, consumers should subsequently
+  // call Seek().
+  // In general, consumers should use Write*() calls instead of this.
+  // Returns NULL on failure.
+  char* GetWritableBuffer(size_t length);
+  char* GetWritableOutput(size_t desired_length, size_t* actual_length);
+
+  // Checks to make sure that there is an appropriate amount of space for a
+  // write of given size, in bytes.
+  bool CanWrite(size_t length) const;
+
+  // A buffer to be created whenever a new frame needs to be written. Used only
+  // if |output_| is nullptr.
+  std::unique_ptr<char[]> buffer_;
+  // A pre-allocated buffer. If not-null, serialized frame data is written to
+  // this buffer.
+  ZeroCopyOutputBuffer* output_ = nullptr;  // Does not own.
+
+  size_t capacity_;  // Allocation size of payload, set by constructor.
+  size_t length_;    // Length of the latest frame in the buffer.
+  size_t offset_;    // Position at which the latest frame begins.
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_FRAME_BUILDER_H_
diff --git a/spdy/core/spdy_frame_builder_test.cc b/spdy/core/spdy_frame_builder_test.cc
new file mode 100644
index 0000000..97d10f1
--- /dev/null
+++ b/spdy/core/spdy_frame_builder_test.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2013 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/spdy/core/spdy_frame_builder.h"
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/array_output_buffer.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+namespace spdy {
+
+namespace {
+
+const int64_t kSize = 64 * 1024;
+char output_buffer[kSize] = "";
+
+}  // namespace
+
+// Verifies that SpdyFrameBuilder::GetWritableBuffer() can be used to build a
+// SpdySerializedFrame.
+TEST(SpdyFrameBuilderTest, GetWritableBuffer) {
+  const size_t kBuilderSize = 10;
+  SpdyFrameBuilder builder(kBuilderSize);
+  char* writable_buffer = builder.GetWritableBuffer(kBuilderSize);
+  memset(writable_buffer, ~1, kBuilderSize);
+  EXPECT_TRUE(builder.Seek(kBuilderSize));
+  SpdySerializedFrame frame(builder.take());
+  char expected[kBuilderSize];
+  memset(expected, ~1, kBuilderSize);
+  EXPECT_EQ(SpdyStringPiece(expected, kBuilderSize),
+            SpdyStringPiece(frame.data(), kBuilderSize));
+}
+
+// Verifies that SpdyFrameBuilder::GetWritableBuffer() can be used to build a
+// SpdySerializedFrame to the output buffer.
+TEST(SpdyFrameBuilderTest, GetWritableOutput) {
+  ArrayOutputBuffer output(output_buffer, kSize);
+  const size_t kBuilderSize = 10;
+  SpdyFrameBuilder builder(kBuilderSize, &output);
+  size_t actual_size = 0;
+  char* writable_buffer = builder.GetWritableOutput(kBuilderSize, &actual_size);
+  memset(writable_buffer, ~1, kBuilderSize);
+  EXPECT_TRUE(builder.Seek(kBuilderSize));
+  SpdySerializedFrame frame(output.Begin(), kBuilderSize, false);
+  char expected[kBuilderSize];
+  memset(expected, ~1, kBuilderSize);
+  EXPECT_EQ(SpdyStringPiece(expected, kBuilderSize),
+            SpdyStringPiece(frame.data(), kBuilderSize));
+}
+
+// Verifies the case that the buffer's capacity is too small.
+TEST(SpdyFrameBuilderTest, GetWritableOutputNegative) {
+  size_t small_cap = 1;
+  ArrayOutputBuffer output(output_buffer, small_cap);
+  const size_t kBuilderSize = 10;
+  SpdyFrameBuilder builder(kBuilderSize, &output);
+  size_t actual_size = 0;
+  char* writable_buffer = builder.GetWritableOutput(kBuilderSize, &actual_size);
+  builder.GetWritableOutput(kBuilderSize, &actual_size);
+  EXPECT_EQ(0u, actual_size);
+  EXPECT_EQ(nullptr, writable_buffer);
+}
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_frame_reader.cc b/spdy/core/spdy_frame_reader.cc
new file mode 100644
index 0000000..b9bf4c1
--- /dev/null
+++ b/spdy/core/spdy_frame_reader.cc
@@ -0,0 +1,200 @@
+// Copyright (c) 2012 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/spdy/core/spdy_frame_reader.h"
+
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h"
+
+namespace spdy {
+
+SpdyFrameReader::SpdyFrameReader(const char* data, const size_t len)
+    : data_(data), len_(len), ofs_(0) {}
+
+bool SpdyFrameReader::ReadUInt8(uint8_t* result) {
+  // Make sure that we have the whole uint8_t.
+  if (!CanRead(1)) {
+    OnFailure();
+    return false;
+  }
+
+  // Read into result.
+  *result = *reinterpret_cast<const uint8_t*>(data_ + ofs_);
+
+  // Iterate.
+  ofs_ += 1;
+
+  return true;
+}
+
+bool SpdyFrameReader::ReadUInt16(uint16_t* result) {
+  // Make sure that we have the whole uint16_t.
+  if (!CanRead(2)) {
+    OnFailure();
+    return false;
+  }
+
+  // Read into result.
+  *result = SpdyNetToHost16(*(reinterpret_cast<const uint16_t*>(data_ + ofs_)));
+
+  // Iterate.
+  ofs_ += 2;
+
+  return true;
+}
+
+bool SpdyFrameReader::ReadUInt32(uint32_t* result) {
+  // Make sure that we have the whole uint32_t.
+  if (!CanRead(4)) {
+    OnFailure();
+    return false;
+  }
+
+  // Read into result.
+  *result = SpdyNetToHost32(*(reinterpret_cast<const uint32_t*>(data_ + ofs_)));
+
+  // Iterate.
+  ofs_ += 4;
+
+  return true;
+}
+
+bool SpdyFrameReader::ReadUInt64(uint64_t* result) {
+  // Make sure that we have the whole uint64_t.
+  if (!CanRead(8)) {
+    OnFailure();
+    return false;
+  }
+
+  // Read into result. Network byte order is big-endian.
+  uint64_t upper =
+      SpdyNetToHost32(*(reinterpret_cast<const uint32_t*>(data_ + ofs_)));
+  uint64_t lower =
+      SpdyNetToHost32(*(reinterpret_cast<const uint32_t*>(data_ + ofs_ + 4)));
+  *result = (upper << 32) + lower;
+
+  // Iterate.
+  ofs_ += 8;
+
+  return true;
+}
+
+bool SpdyFrameReader::ReadUInt31(uint32_t* result) {
+  bool success = ReadUInt32(result);
+
+  // Zero out highest-order bit.
+  if (success) {
+    *result &= 0x7fffffff;
+  }
+
+  return success;
+}
+
+bool SpdyFrameReader::ReadUInt24(uint32_t* result) {
+  // Make sure that we have the whole uint24_t.
+  if (!CanRead(3)) {
+    OnFailure();
+    return false;
+  }
+
+  // Read into result.
+  *result = 0;
+  memcpy(reinterpret_cast<char*>(result) + 1, data_ + ofs_, 3);
+  *result = SpdyNetToHost32(*result);
+
+  // Iterate.
+  ofs_ += 3;
+
+  return true;
+}
+
+bool SpdyFrameReader::ReadStringPiece16(SpdyStringPiece* result) {
+  // Read resultant length.
+  uint16_t result_len;
+  if (!ReadUInt16(&result_len)) {
+    // OnFailure() already called.
+    return false;
+  }
+
+  // Make sure that we have the whole string.
+  if (!CanRead(result_len)) {
+    OnFailure();
+    return false;
+  }
+
+  // Set result.
+  *result = SpdyStringPiece(data_ + ofs_, result_len);
+
+  // Iterate.
+  ofs_ += result_len;
+
+  return true;
+}
+
+bool SpdyFrameReader::ReadStringPiece32(SpdyStringPiece* result) {
+  // Read resultant length.
+  uint32_t result_len;
+  if (!ReadUInt32(&result_len)) {
+    // OnFailure() already called.
+    return false;
+  }
+
+  // Make sure that we have the whole string.
+  if (!CanRead(result_len)) {
+    OnFailure();
+    return false;
+  }
+
+  // Set result.
+  *result = SpdyStringPiece(data_ + ofs_, result_len);
+
+  // Iterate.
+  ofs_ += result_len;
+
+  return true;
+}
+
+bool SpdyFrameReader::ReadBytes(void* result, size_t size) {
+  // Make sure that we have enough data to read.
+  if (!CanRead(size)) {
+    OnFailure();
+    return false;
+  }
+
+  // Read into result.
+  memcpy(result, data_ + ofs_, size);
+
+  // Iterate.
+  ofs_ += size;
+
+  return true;
+}
+
+bool SpdyFrameReader::Seek(size_t size) {
+  if (!CanRead(size)) {
+    OnFailure();
+    return false;
+  }
+
+  // Iterate.
+  ofs_ += size;
+
+  return true;
+}
+
+bool SpdyFrameReader::IsDoneReading() const {
+  return len_ == ofs_;
+}
+
+bool SpdyFrameReader::CanRead(size_t bytes) const {
+  return bytes <= (len_ - ofs_);
+}
+
+void SpdyFrameReader::OnFailure() {
+  // Set our iterator to the end of the buffer so that further reads fail
+  // immediately.
+  ofs_ = len_;
+}
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_frame_reader.h b/spdy/core/spdy_frame_reader.h
new file mode 100644
index 0000000..dc6c064
--- /dev/null
+++ b/spdy/core/spdy_frame_reader.h
@@ -0,0 +1,129 @@
+// Copyright (c) 2012 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_SPDY_CORE_SPDY_FRAME_READER_H_
+#define QUICHE_SPDY_CORE_SPDY_FRAME_READER_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+// Used for reading SPDY frames. Though there isn't really anything terribly
+// SPDY-specific here, it's a helper class that's useful when doing SPDY
+// framing.
+//
+// To use, simply construct a SpdyFramerReader using the underlying buffer that
+// you'd like to read fields from, then call one of the Read*() methods to
+// actually do some reading.
+//
+// This class keeps an internal iterator to keep track of what's already been
+// read and each successive Read*() call automatically increments said iterator
+// on success. On failure, internal state of the SpdyFrameReader should not be
+// trusted and it is up to the caller to throw away the failed instance and
+// handle the error as appropriate. None of the Read*() methods should ever be
+// called after failure, as they will also fail immediately.
+class SPDY_EXPORT_PRIVATE SpdyFrameReader {
+ public:
+  // Caller must provide an underlying buffer to work on.
+  SpdyFrameReader(const char* data, const size_t len);
+
+  // Empty destructor.
+  ~SpdyFrameReader() {}
+
+  // Reads an 8-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt8(uint8_t* result);
+
+  // Reads a 16-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt16(uint16_t* result);
+
+  // Reads a 32-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt32(uint32_t* result);
+
+  // Reads a 64-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt64(uint64_t* result);
+
+  // Reads a 31-bit unsigned integer into the given output parameter. This is
+  // equivalent to ReadUInt32() above except that the highest-order bit is
+  // discarded.
+  // Forwards the internal iterator (by 4B) on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt31(uint32_t* result);
+
+  // Reads a 24-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterator (by 3B) on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt24(uint32_t* result);
+
+  // Reads a string prefixed with 16-bit length into the given output parameter.
+  //
+  // NOTE: Does not copy but rather references strings in the underlying buffer.
+  // This should be kept in mind when handling memory management!
+  //
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadStringPiece16(SpdyStringPiece* result);
+
+  // Reads a string prefixed with 32-bit length into the given output parameter.
+  //
+  // NOTE: Does not copy but rather references strings in the underlying buffer.
+  // This should be kept in mind when handling memory management!
+  //
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadStringPiece32(SpdyStringPiece* result);
+
+  // Reads a given number of bytes into the given buffer. The buffer
+  // must be of adequate size.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadBytes(void* result, size_t size);
+
+  // Seeks a given number of bytes into the buffer from the current offset.
+  // Equivelant to an empty read.
+  // Forwards the internal iterator.
+  // Returns true on success, false otherwise.
+  bool Seek(size_t size);
+
+  // Rewinds this reader to the beginning of the frame.
+  void Rewind() { ofs_ = 0; }
+
+  // Returns true if the entirety of the underlying buffer has been read via
+  // Read*() calls.
+  bool IsDoneReading() const;
+
+  // Returns the number of bytes that have been consumed by the reader so far.
+  size_t GetBytesConsumed() const { return ofs_; }
+
+ private:
+  // Returns true if the underlying buffer has enough room to read the given
+  // amount of bytes.
+  bool CanRead(size_t bytes) const;
+
+  // To be called when a read fails for any reason.
+  void OnFailure();
+
+  // The data buffer that we're reading from.
+  const char* data_;
+
+  // The length of the data buffer that we're reading from.
+  const size_t len_;
+
+  // The location of the next read from our data buffer.
+  size_t ofs_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_FRAME_READER_H_
diff --git a/spdy/core/spdy_frame_reader_test.cc b/spdy/core/spdy_frame_reader_test.cc
new file mode 100644
index 0000000..8caf60f
--- /dev/null
+++ b/spdy/core/spdy_frame_reader_test.cc
@@ -0,0 +1,247 @@
+// Copyright (c) 2012 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/spdy/core/spdy_frame_reader.h"
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h"
+
+namespace spdy {
+
+TEST(SpdyFrameReaderTest, ReadUInt16) {
+  // Frame data in network byte order.
+  const uint16_t kFrameData[] = {
+      SpdyHostToNet16(1),
+      SpdyHostToNet16(1 << 15),
+  };
+
+  SpdyFrameReader frame_reader(reinterpret_cast<const char*>(kFrameData),
+                               sizeof(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  uint16_t uint16_val;
+  EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+  EXPECT_EQ(1, uint16_val);
+
+  EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val));
+  EXPECT_TRUE(frame_reader.IsDoneReading());
+  EXPECT_EQ(1 << 15, uint16_val);
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt32) {
+  // Frame data in network byte order.
+  const uint32_t kFrameData[] = {
+      SpdyHostToNet32(1),
+      SpdyHostToNet32(0x80000000),
+  };
+
+  SpdyFrameReader frame_reader(reinterpret_cast<const char*>(kFrameData),
+                               SPDY_ARRAYSIZE(kFrameData) * sizeof(uint32_t));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  uint32_t uint32_val;
+  EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+  EXPECT_EQ(1u, uint32_val);
+
+  EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val));
+  EXPECT_TRUE(frame_reader.IsDoneReading());
+  EXPECT_EQ(1u << 31, uint32_val);
+}
+
+TEST(SpdyFrameReaderTest, ReadStringPiece16) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+      0x00, 0x02,  // uint16_t(2)
+      0x48, 0x69,  // "Hi"
+      0x00, 0x10,  // uint16_t(16)
+      0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2c,
+      0x20, 0x31, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x33,  // "Testing, 1, 2, 3"
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  SpdyStringPiece stringpiece_val;
+  EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+  EXPECT_EQ(0, stringpiece_val.compare("Hi"));
+
+  EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val));
+  EXPECT_TRUE(frame_reader.IsDoneReading());
+  EXPECT_EQ(0, stringpiece_val.compare("Testing, 1, 2, 3"));
+}
+
+TEST(SpdyFrameReaderTest, ReadStringPiece32) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+      0x00, 0x00, 0x00, 0x03,  // uint32_t(3)
+      0x66, 0x6f, 0x6f,        // "foo"
+      0x00, 0x00, 0x00, 0x10,  // uint32_t(16)
+      0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2c,
+      0x20, 0x34, 0x2c, 0x20, 0x35, 0x2c, 0x20, 0x36,  // "Testing, 4, 5, 6"
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  SpdyStringPiece stringpiece_val;
+  EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+  EXPECT_EQ(0, stringpiece_val.compare("foo"));
+
+  EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val));
+  EXPECT_TRUE(frame_reader.IsDoneReading());
+  EXPECT_EQ(0, stringpiece_val.compare("Testing, 4, 5, 6"));
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt16WithBufferTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+      0x00,  // part of a uint16_t
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  uint16_t uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt32WithBufferTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+      0x00, 0x00, 0x00,  // part of a uint32_t
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  uint32_t uint32_val;
+  EXPECT_FALSE(frame_reader.ReadUInt32(&uint32_val));
+
+  // Also make sure that trying to read a uint16_t, which technically could
+  // work, fails immediately due to previously encountered failed read.
+  uint16_t uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece16() with a buffer too small to fit the entire string.
+TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+      0x00, 0x03,  // uint16_t(3)
+      0x48, 0x69,  // "Hi"
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  SpdyStringPiece stringpiece_val;
+  EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val));
+
+  // Also make sure that trying to read a uint16_t, which technically could
+  // work, fails immediately due to previously encountered failed read.
+  uint16_t uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece16() with a buffer too small even to fit the length.
+TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferWayTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+      0x00,  // part of a uint16_t
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  SpdyStringPiece stringpiece_val;
+  EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val));
+
+  // Also make sure that trying to read a uint16_t, which technically could
+  // work, fails immediately due to previously encountered failed read.
+  uint16_t uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece32() with a buffer too small to fit the entire string.
+TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+      0x00, 0x00, 0x00, 0x03,  // uint32_t(3)
+      0x48, 0x69,              // "Hi"
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  SpdyStringPiece stringpiece_val;
+  EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val));
+
+  // Also make sure that trying to read a uint16_t, which technically could
+  // work, fails immediately due to previously encountered failed read.
+  uint16_t uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece32() with a buffer too small even to fit the length.
+TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferWayTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+      0x00, 0x00, 0x00,  // part of a uint32_t
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  SpdyStringPiece stringpiece_val;
+  EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val));
+
+  // Also make sure that trying to read a uint16_t, which technically could
+  // work, fails immediately due to previously encountered failed read.
+  uint16_t uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+TEST(SpdyFrameReaderTest, ReadBytes) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+      0x66, 0x6f, 0x6f,  // "foo"
+      0x48, 0x69,        // "Hi"
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  char dest1[3] = {};
+  EXPECT_TRUE(frame_reader.ReadBytes(&dest1, SPDY_ARRAYSIZE(dest1)));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+  EXPECT_EQ("foo", SpdyStringPiece(dest1, SPDY_ARRAYSIZE(dest1)));
+
+  char dest2[2] = {};
+  EXPECT_TRUE(frame_reader.ReadBytes(&dest2, SPDY_ARRAYSIZE(dest2)));
+  EXPECT_TRUE(frame_reader.IsDoneReading());
+  EXPECT_EQ("Hi", SpdyStringPiece(dest2, SPDY_ARRAYSIZE(dest2)));
+}
+
+TEST(SpdyFrameReaderTest, ReadBytesWithBufferTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+      0x01,
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  char dest[SPDY_ARRAYSIZE(kFrameData) + 2] = {};
+  EXPECT_FALSE(frame_reader.ReadBytes(&dest, SPDY_ARRAYSIZE(kFrameData) + 1));
+  EXPECT_STREQ("", dest);
+}
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_framer.cc b/spdy/core/spdy_framer.cc
new file mode 100644
index 0000000..fc9cc2e
--- /dev/null
+++ b/spdy/core/spdy_framer.cc
@@ -0,0 +1,1295 @@
+// Copyright (c) 2012 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/spdy/core/spdy_framer.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <iterator>
+#include <list>
+#include <new>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+namespace {
+
+// Pack parent stream ID and exclusive flag into the format used by HTTP/2
+// headers and priority frames.
+uint32_t PackStreamDependencyValues(bool exclusive,
+                                    SpdyStreamId parent_stream_id) {
+  // Make sure the highest-order bit in the parent stream id is zeroed out.
+  uint32_t parent = parent_stream_id & 0x7fffffff;
+  // Set the one-bit exclusivity flag.
+  uint32_t e_bit = exclusive ? 0x80000000 : 0;
+  return parent | e_bit;
+}
+
+// Used to indicate no flags in a HTTP2 flags field.
+const uint8_t kNoFlags = 0;
+
+// Wire size of pad length field.
+const size_t kPadLengthFieldSize = 1;
+
+// The size of one parameter in SETTINGS frame.
+const size_t kOneSettingParameterSize = 6;
+
+size_t GetUncompressedSerializedLength(const SpdyHeaderBlock& headers) {
+  const size_t num_name_value_pairs_size = sizeof(uint32_t);
+  const size_t length_of_name_size = num_name_value_pairs_size;
+  const size_t length_of_value_size = num_name_value_pairs_size;
+
+  size_t total_length = num_name_value_pairs_size;
+  for (const auto& header : headers) {
+    // We add space for the length of the name and the length of the value as
+    // well as the length of the name and the length of the value.
+    total_length += length_of_name_size + header.first.size() +
+                    length_of_value_size + header.second.size();
+  }
+  return total_length;
+}
+
+// Serializes the flags octet for a given SpdyHeadersIR.
+uint8_t SerializeHeaderFrameFlags(const SpdyHeadersIR& header_ir,
+                                  const bool end_headers) {
+  uint8_t flags = 0;
+  if (header_ir.fin()) {
+    flags |= CONTROL_FLAG_FIN;
+  }
+  if (end_headers) {
+    flags |= HEADERS_FLAG_END_HEADERS;
+  }
+  if (header_ir.padded()) {
+    flags |= HEADERS_FLAG_PADDED;
+  }
+  if (header_ir.has_priority()) {
+    flags |= HEADERS_FLAG_PRIORITY;
+  }
+  return flags;
+}
+
+// Serializes the flags octet for a given SpdyPushPromiseIR.
+uint8_t SerializePushPromiseFrameFlags(const SpdyPushPromiseIR& push_promise_ir,
+                                       const bool end_headers) {
+  uint8_t flags = 0;
+  if (push_promise_ir.padded()) {
+    flags = flags | PUSH_PROMISE_FLAG_PADDED;
+  }
+  if (end_headers) {
+    flags |= PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+  }
+  return flags;
+}
+
+// Serializes a HEADERS frame from the given SpdyHeadersIR and encoded header
+// block. Does not need or use the SpdyHeaderBlock inside SpdyHeadersIR.
+// Return false if the serialization fails. |encoding| should not be empty.
+bool SerializeHeadersGivenEncoding(const SpdyHeadersIR& headers,
+                                   const SpdyString& encoding,
+                                   const bool end_headers,
+                                   ZeroCopyOutputBuffer* output) {
+  const size_t frame_size =
+      GetHeaderFrameSizeSansBlock(headers) + encoding.size();
+  SpdyFrameBuilder builder(frame_size, output);
+  bool ret = builder.BeginNewFrame(
+      SpdyFrameType::HEADERS, SerializeHeaderFrameFlags(headers, end_headers),
+      headers.stream_id(), frame_size - kFrameHeaderSize);
+  DCHECK_EQ(kFrameHeaderSize, builder.length());
+
+  if (ret && headers.padded()) {
+    ret &= builder.WriteUInt8(headers.padding_payload_len());
+  }
+
+  if (ret && headers.has_priority()) {
+    int weight = ClampHttp2Weight(headers.weight());
+    ret &= builder.WriteUInt32(PackStreamDependencyValues(
+        headers.exclusive(), headers.parent_stream_id()));
+    // Per RFC 7540 section 6.3, serialized weight value is actual value - 1.
+    ret &= builder.WriteUInt8(weight - 1);
+  }
+
+  if (ret) {
+    ret &= builder.WriteBytes(encoding.data(), encoding.size());
+  }
+
+  if (ret && headers.padding_payload_len() > 0) {
+    SpdyString padding(headers.padding_payload_len(), 0);
+    ret &= builder.WriteBytes(padding.data(), padding.length());
+  }
+
+  if (!ret) {
+    DLOG(WARNING) << "Failed to build HEADERS. Not enough space in output";
+  }
+  return ret;
+}
+
+// Serializes a PUSH_PROMISE frame from the given SpdyPushPromiseIR and
+// encoded header block. Does not need or use the SpdyHeaderBlock inside
+// SpdyPushPromiseIR.
+bool SerializePushPromiseGivenEncoding(const SpdyPushPromiseIR& push_promise,
+                                       const SpdyString& encoding,
+                                       const bool end_headers,
+                                       ZeroCopyOutputBuffer* output) {
+  const size_t frame_size =
+      GetPushPromiseFrameSizeSansBlock(push_promise) + encoding.size();
+  SpdyFrameBuilder builder(frame_size, output);
+  bool ok = builder.BeginNewFrame(
+      SpdyFrameType::PUSH_PROMISE,
+      SerializePushPromiseFrameFlags(push_promise, end_headers),
+      push_promise.stream_id(), frame_size - kFrameHeaderSize);
+
+  if (push_promise.padded()) {
+    ok = ok && builder.WriteUInt8(push_promise.padding_payload_len());
+  }
+  ok = ok && builder.WriteUInt32(push_promise.promised_stream_id()) &&
+       builder.WriteBytes(encoding.data(), encoding.size());
+  if (ok && push_promise.padding_payload_len() > 0) {
+    SpdyString padding(push_promise.padding_payload_len(), 0);
+    ok = builder.WriteBytes(padding.data(), padding.length());
+  }
+
+  DLOG_IF(ERROR, !ok) << "Failed to write PUSH_PROMISE encoding, not enough "
+                      << "space in output";
+  return ok;
+}
+
+bool WritePayloadWithContinuation(SpdyFrameBuilder* builder,
+                                  const SpdyString& hpack_encoding,
+                                  SpdyStreamId stream_id,
+                                  SpdyFrameType type,
+                                  int padding_payload_len) {
+  uint8_t end_flag = 0;
+  uint8_t flags = 0;
+  if (type == SpdyFrameType::HEADERS) {
+    end_flag = HEADERS_FLAG_END_HEADERS;
+  } else if (type == SpdyFrameType::PUSH_PROMISE) {
+    end_flag = PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+  } else {
+    DLOG(FATAL) << "CONTINUATION frames cannot be used with frame type "
+                << FrameTypeToString(type);
+  }
+
+  // Write all the padding payload and as much of the data payload as possible
+  // into the initial frame.
+  size_t bytes_remaining = 0;
+  bytes_remaining = hpack_encoding.size() -
+                    std::min(hpack_encoding.size(),
+                             kHttp2MaxControlFrameSendSize - builder->length() -
+                                 padding_payload_len);
+  bool ret = builder->WriteBytes(&hpack_encoding[0],
+                                 hpack_encoding.size() - bytes_remaining);
+  if (padding_payload_len > 0) {
+    SpdyString padding = SpdyString(padding_payload_len, 0);
+    ret &= builder->WriteBytes(padding.data(), padding.length());
+  }
+
+  // Tack on CONTINUATION frames for the overflow.
+  while (bytes_remaining > 0 && ret) {
+    size_t bytes_to_write =
+        std::min(bytes_remaining,
+                 kHttp2MaxControlFrameSendSize - kContinuationFrameMinimumSize);
+    // Write CONTINUATION frame prefix.
+    if (bytes_remaining == bytes_to_write) {
+      flags |= end_flag;
+    }
+    ret &= builder->BeginNewFrame(SpdyFrameType::CONTINUATION, flags, stream_id,
+                                  bytes_to_write);
+    // Write payload fragment.
+    ret &= builder->WriteBytes(
+        &hpack_encoding[hpack_encoding.size() - bytes_remaining],
+        bytes_to_write);
+    bytes_remaining -= bytes_to_write;
+  }
+  return ret;
+}
+
+void SerializeDataBuilderHelper(const SpdyDataIR& data_ir,
+                                uint8_t* flags,
+                                int* num_padding_fields,
+                                size_t* size_with_padding) {
+  if (data_ir.fin()) {
+    *flags = DATA_FLAG_FIN;
+  }
+
+  if (data_ir.padded()) {
+    *flags = *flags | DATA_FLAG_PADDED;
+    ++*num_padding_fields;
+  }
+
+  *size_with_padding = *num_padding_fields + data_ir.data_len() +
+                       data_ir.padding_payload_len() + kDataFrameMinimumSize;
+}
+
+void SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper(
+    const SpdyDataIR& data_ir,
+    uint8_t* flags,
+    size_t* frame_size,
+    size_t* num_padding_fields) {
+  *flags = DATA_FLAG_NONE;
+  if (data_ir.fin()) {
+    *flags = DATA_FLAG_FIN;
+  }
+
+  *frame_size = kDataFrameMinimumSize;
+  if (data_ir.padded()) {
+    *flags = *flags | DATA_FLAG_PADDED;
+    ++(*num_padding_fields);
+    *frame_size = *frame_size + *num_padding_fields;
+  }
+}
+
+void SerializeSettingsBuilderHelper(const SpdySettingsIR& settings,
+                                    uint8_t* flags,
+                                    const SettingsMap* values,
+                                    size_t* size) {
+  if (settings.is_ack()) {
+    *flags = *flags | SETTINGS_FLAG_ACK;
+  }
+  *size =
+      kSettingsFrameMinimumSize + (values->size() * kOneSettingParameterSize);
+}
+
+void SerializeAltSvcBuilderHelper(const SpdyAltSvcIR& altsvc_ir,
+                                  SpdyString* value,
+                                  size_t* size) {
+  *size = kGetAltSvcFrameMinimumSize;
+  *size = *size + altsvc_ir.origin().length();
+  *value = SpdyAltSvcWireFormat::SerializeHeaderFieldValue(
+      altsvc_ir.altsvc_vector());
+  *size = *size + value->length();
+}
+
+}  // namespace
+
+SpdyFramer::SpdyFramer(CompressionOption option)
+    : debug_visitor_(nullptr), compression_option_(option) {
+  static_assert(kHttp2MaxControlFrameSendSize <= kHttp2DefaultFrameSizeLimit,
+                "Our send limit should be at most our receive limit.");
+}
+
+SpdyFramer::~SpdyFramer() = default;
+
+void SpdyFramer::set_debug_visitor(
+    SpdyFramerDebugVisitorInterface* debug_visitor) {
+  debug_visitor_ = debug_visitor;
+}
+
+SpdyFramer::SpdyFrameIterator::SpdyFrameIterator(SpdyFramer* framer)
+    : framer_(framer), is_first_frame_(true), has_next_frame_(true) {}
+
+SpdyFramer::SpdyFrameIterator::~SpdyFrameIterator() = default;
+
+size_t SpdyFramer::SpdyFrameIterator::NextFrame(ZeroCopyOutputBuffer* output) {
+  const SpdyFrameIR& frame_ir = GetIR();
+  if (!has_next_frame_) {
+    SPDY_BUG << "SpdyFramer::SpdyFrameIterator::NextFrame called without "
+             << "a next frame.";
+    return false;
+  }
+
+  const size_t size_without_block =
+      is_first_frame_ ? GetFrameSizeSansBlock() : kContinuationFrameMinimumSize;
+  auto encoding = SpdyMakeUnique<SpdyString>();
+  encoder_->Next(kHttp2MaxControlFrameSendSize - size_without_block,
+                 encoding.get());
+  has_next_frame_ = encoder_->HasNext();
+
+  if (framer_->debug_visitor_ != nullptr) {
+    const auto& header_block_frame_ir =
+        down_cast<const SpdyFrameWithHeaderBlockIR&>(frame_ir);
+    const size_t header_list_size =
+        GetUncompressedSerializedLength(header_block_frame_ir.header_block());
+    framer_->debug_visitor_->OnSendCompressedFrame(
+        frame_ir.stream_id(),
+        is_first_frame_ ? frame_ir.frame_type() : SpdyFrameType::CONTINUATION,
+        header_list_size, size_without_block + encoding->size());
+  }
+
+  const size_t free_bytes_before = output->BytesFree();
+  bool ok = false;
+  if (is_first_frame_) {
+    is_first_frame_ = false;
+    ok = SerializeGivenEncoding(*encoding, output);
+  } else {
+    SpdyContinuationIR continuation_ir(frame_ir.stream_id());
+    continuation_ir.take_encoding(std::move(encoding));
+    continuation_ir.set_end_headers(!has_next_frame_);
+    ok = framer_->SerializeContinuation(continuation_ir, output);
+  }
+  return ok ? free_bytes_before - output->BytesFree() : 0;
+}
+
+bool SpdyFramer::SpdyFrameIterator::HasNextFrame() const {
+  return has_next_frame_;
+}
+
+SpdyFramer::SpdyHeaderFrameIterator::SpdyHeaderFrameIterator(
+    SpdyFramer* framer,
+    std::unique_ptr<const SpdyHeadersIR> headers_ir)
+    : SpdyFrameIterator(framer), headers_ir_(std::move(headers_ir)) {
+  SetEncoder(headers_ir_.get());
+}
+
+SpdyFramer::SpdyHeaderFrameIterator::~SpdyHeaderFrameIterator() = default;
+
+const SpdyFrameIR& SpdyFramer::SpdyHeaderFrameIterator::GetIR() const {
+  return *(headers_ir_.get());
+}
+
+size_t SpdyFramer::SpdyHeaderFrameIterator::GetFrameSizeSansBlock() const {
+  return GetHeaderFrameSizeSansBlock(*headers_ir_);
+}
+
+bool SpdyFramer::SpdyHeaderFrameIterator::SerializeGivenEncoding(
+    const SpdyString& encoding,
+    ZeroCopyOutputBuffer* output) const {
+  return SerializeHeadersGivenEncoding(*headers_ir_, encoding,
+                                       !has_next_frame(), output);
+}
+
+SpdyFramer::SpdyPushPromiseFrameIterator::SpdyPushPromiseFrameIterator(
+    SpdyFramer* framer,
+    std::unique_ptr<const SpdyPushPromiseIR> push_promise_ir)
+    : SpdyFrameIterator(framer), push_promise_ir_(std::move(push_promise_ir)) {
+  SetEncoder(push_promise_ir_.get());
+}
+
+SpdyFramer::SpdyPushPromiseFrameIterator::~SpdyPushPromiseFrameIterator() =
+    default;
+
+const SpdyFrameIR& SpdyFramer::SpdyPushPromiseFrameIterator::GetIR() const {
+  return *(push_promise_ir_.get());
+}
+
+size_t SpdyFramer::SpdyPushPromiseFrameIterator::GetFrameSizeSansBlock() const {
+  return GetPushPromiseFrameSizeSansBlock(*push_promise_ir_);
+}
+
+bool SpdyFramer::SpdyPushPromiseFrameIterator::SerializeGivenEncoding(
+    const SpdyString& encoding,
+    ZeroCopyOutputBuffer* output) const {
+  return SerializePushPromiseGivenEncoding(*push_promise_ir_, encoding,
+                                           !has_next_frame(), output);
+}
+
+SpdyFramer::SpdyControlFrameIterator::SpdyControlFrameIterator(
+    SpdyFramer* framer,
+    std::unique_ptr<const SpdyFrameIR> frame_ir)
+    : framer_(framer), frame_ir_(std::move(frame_ir)) {}
+
+SpdyFramer::SpdyControlFrameIterator::~SpdyControlFrameIterator() = default;
+
+size_t SpdyFramer::SpdyControlFrameIterator::NextFrame(
+    ZeroCopyOutputBuffer* output) {
+  size_t size_written = framer_->SerializeFrame(*frame_ir_, output);
+  has_next_frame_ = false;
+  return size_written;
+}
+
+bool SpdyFramer::SpdyControlFrameIterator::HasNextFrame() const {
+  return has_next_frame_;
+}
+
+const SpdyFrameIR& SpdyFramer::SpdyControlFrameIterator::GetIR() const {
+  return *(frame_ir_.get());
+}
+
+// TODO(yasong): remove all the down_casts.
+std::unique_ptr<SpdyFrameSequence> SpdyFramer::CreateIterator(
+    SpdyFramer* framer,
+    std::unique_ptr<const SpdyFrameIR> frame_ir) {
+  switch (frame_ir->frame_type()) {
+    case SpdyFrameType::HEADERS: {
+      return SpdyMakeUnique<SpdyHeaderFrameIterator>(
+          framer,
+          SpdyWrapUnique(down_cast<const SpdyHeadersIR*>(frame_ir.release())));
+    }
+    case SpdyFrameType::PUSH_PROMISE: {
+      return SpdyMakeUnique<SpdyPushPromiseFrameIterator>(
+          framer, SpdyWrapUnique(
+                      down_cast<const SpdyPushPromiseIR*>(frame_ir.release())));
+    }
+    case SpdyFrameType::DATA: {
+      DVLOG(1) << "Serialize a stream end DATA frame for VTL";
+      HTTP2_FALLTHROUGH;
+    }
+    default: {
+      return SpdyMakeUnique<SpdyControlFrameIterator>(framer,
+                                                      std::move(frame_ir));
+    }
+  }
+}
+
+SpdySerializedFrame SpdyFramer::SerializeData(const SpdyDataIR& data_ir) {
+  uint8_t flags = DATA_FLAG_NONE;
+  int num_padding_fields = 0;
+  size_t size_with_padding = 0;
+  SerializeDataBuilderHelper(data_ir, &flags, &num_padding_fields,
+                             &size_with_padding);
+
+  SpdyFrameBuilder builder(size_with_padding);
+  builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id());
+  if (data_ir.padded()) {
+    builder.WriteUInt8(data_ir.padding_payload_len() & 0xff);
+  }
+  builder.WriteBytes(data_ir.data(), data_ir.data_len());
+  if (data_ir.padding_payload_len() > 0) {
+    SpdyString padding(data_ir.padding_payload_len(), 0);
+    builder.WriteBytes(padding.data(), padding.length());
+  }
+  DCHECK_EQ(size_with_padding, builder.length());
+  return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeDataFrameHeaderWithPaddingLengthField(
+    const SpdyDataIR& data_ir) {
+  uint8_t flags = DATA_FLAG_NONE;
+  size_t frame_size = 0;
+  size_t num_padding_fields = 0;
+  SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper(
+      data_ir, &flags, &frame_size, &num_padding_fields);
+
+  SpdyFrameBuilder builder(frame_size);
+  builder.BeginNewFrame(
+      SpdyFrameType::DATA, flags, data_ir.stream_id(),
+      num_padding_fields + data_ir.data_len() + data_ir.padding_payload_len());
+  if (data_ir.padded()) {
+    builder.WriteUInt8(data_ir.padding_payload_len() & 0xff);
+  }
+  DCHECK_EQ(frame_size, builder.length());
+  return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeRstStream(
+    const SpdyRstStreamIR& rst_stream) const {
+  size_t expected_length = kRstStreamFrameSize;
+  SpdyFrameBuilder builder(expected_length);
+
+  builder.BeginNewFrame(SpdyFrameType::RST_STREAM, 0, rst_stream.stream_id());
+
+  builder.WriteUInt32(rst_stream.error_code());
+
+  DCHECK_EQ(expected_length, builder.length());
+  return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeSettings(
+    const SpdySettingsIR& settings) const {
+  uint8_t flags = 0;
+  // Size, in bytes, of this SETTINGS frame.
+  size_t size = 0;
+  const SettingsMap* values = &(settings.values());
+  SerializeSettingsBuilderHelper(settings, &flags, values, &size);
+  SpdyFrameBuilder builder(size);
+  builder.BeginNewFrame(SpdyFrameType::SETTINGS, flags, 0);
+
+  // If this is an ACK, payload should be empty.
+  if (settings.is_ack()) {
+    return builder.take();
+  }
+
+  DCHECK_EQ(kSettingsFrameMinimumSize, builder.length());
+  for (auto it = values->begin(); it != values->end(); ++it) {
+    int setting_id = it->first;
+    DCHECK_GE(setting_id, 0);
+    builder.WriteUInt16(static_cast<SpdySettingsId>(setting_id));
+    builder.WriteUInt32(it->second);
+  }
+  DCHECK_EQ(size, builder.length());
+  return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializePing(const SpdyPingIR& ping) const {
+  SpdyFrameBuilder builder(kPingFrameSize);
+  uint8_t flags = 0;
+  if (ping.is_ack()) {
+    flags |= PING_FLAG_ACK;
+  }
+  builder.BeginNewFrame(SpdyFrameType::PING, flags, 0);
+  builder.WriteUInt64(ping.id());
+  DCHECK_EQ(kPingFrameSize, builder.length());
+  return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeGoAway(
+    const SpdyGoAwayIR& goaway) const {
+  // Compute the output buffer size, take opaque data into account.
+  size_t expected_length = kGoawayFrameMinimumSize;
+  expected_length += goaway.description().size();
+  SpdyFrameBuilder builder(expected_length);
+
+  // Serialize the GOAWAY frame.
+  builder.BeginNewFrame(SpdyFrameType::GOAWAY, 0, 0);
+
+  // GOAWAY frames specify the last good stream id.
+  builder.WriteUInt32(goaway.last_good_stream_id());
+
+  // GOAWAY frames also specify the error code.
+  builder.WriteUInt32(goaway.error_code());
+
+  // GOAWAY frames may also specify opaque data.
+  if (!goaway.description().empty()) {
+    builder.WriteBytes(goaway.description().data(),
+                       goaway.description().size());
+  }
+
+  DCHECK_EQ(expected_length, builder.length());
+  return builder.take();
+}
+
+void SpdyFramer::SerializeHeadersBuilderHelper(const SpdyHeadersIR& headers,
+                                               uint8_t* flags,
+                                               size_t* size,
+                                               SpdyString* hpack_encoding,
+                                               int* weight,
+                                               size_t* length_field) {
+  if (headers.fin()) {
+    *flags = *flags | CONTROL_FLAG_FIN;
+  }
+  // This will get overwritten if we overflow into a CONTINUATION frame.
+  *flags = *flags | HEADERS_FLAG_END_HEADERS;
+  if (headers.has_priority()) {
+    *flags = *flags | HEADERS_FLAG_PRIORITY;
+  }
+  if (headers.padded()) {
+    *flags = *flags | HEADERS_FLAG_PADDED;
+  }
+
+  *size = kHeadersFrameMinimumSize;
+
+  if (headers.padded()) {
+    *size = *size + kPadLengthFieldSize;
+    *size = *size + headers.padding_payload_len();
+  }
+
+  if (headers.has_priority()) {
+    *weight = ClampHttp2Weight(headers.weight());
+    *size = *size + 5;
+  }
+
+  GetHpackEncoder()->EncodeHeaderSet(headers.header_block(), hpack_encoding);
+  *size = *size + hpack_encoding->size();
+  if (*size > kHttp2MaxControlFrameSendSize) {
+    *size = *size + GetNumberRequiredContinuationFrames(*size) *
+                        kContinuationFrameMinimumSize;
+    *flags = *flags & ~HEADERS_FLAG_END_HEADERS;
+  }
+  // Compute frame length field.
+  if (headers.padded()) {
+    *length_field = *length_field + kPadLengthFieldSize;
+  }
+  if (headers.has_priority()) {
+    *length_field = *length_field + 4;  // Dependency field.
+    *length_field = *length_field + 1;  // Weight field.
+  }
+  *length_field = *length_field + headers.padding_payload_len();
+  *length_field = *length_field + hpack_encoding->size();
+  // If the HEADERS frame with payload would exceed the max frame size, then
+  // WritePayloadWithContinuation() will serialize CONTINUATION frames as
+  // necessary.
+  *length_field =
+      std::min(*length_field, kHttp2MaxControlFrameSendSize - kFrameHeaderSize);
+}
+
+SpdySerializedFrame SpdyFramer::SerializeHeaders(const SpdyHeadersIR& headers) {
+  uint8_t flags = 0;
+  // The size of this frame, including padding (if there is any) and
+  // variable-length header block.
+  size_t size = 0;
+  SpdyString hpack_encoding;
+  int weight = 0;
+  size_t length_field = 0;
+  SerializeHeadersBuilderHelper(headers, &flags, &size, &hpack_encoding,
+                                &weight, &length_field);
+
+  SpdyFrameBuilder builder(size);
+  builder.BeginNewFrame(SpdyFrameType::HEADERS, flags, headers.stream_id(),
+                        length_field);
+
+  DCHECK_EQ(kHeadersFrameMinimumSize, builder.length());
+
+  int padding_payload_len = 0;
+  if (headers.padded()) {
+    builder.WriteUInt8(headers.padding_payload_len());
+    padding_payload_len = headers.padding_payload_len();
+  }
+  if (headers.has_priority()) {
+    builder.WriteUInt32(PackStreamDependencyValues(headers.exclusive(),
+                                                   headers.parent_stream_id()));
+    // Per RFC 7540 section 6.3, serialized weight value is actual value - 1.
+    builder.WriteUInt8(weight - 1);
+  }
+  WritePayloadWithContinuation(&builder, hpack_encoding, headers.stream_id(),
+                               SpdyFrameType::HEADERS, padding_payload_len);
+
+  if (debug_visitor_) {
+    const size_t header_list_size =
+        GetUncompressedSerializedLength(headers.header_block());
+    debug_visitor_->OnSendCompressedFrame(headers.stream_id(),
+                                          SpdyFrameType::HEADERS,
+                                          header_list_size, builder.length());
+  }
+
+  return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeWindowUpdate(
+    const SpdyWindowUpdateIR& window_update) {
+  SpdyFrameBuilder builder(kWindowUpdateFrameSize);
+  builder.BeginNewFrame(SpdyFrameType::WINDOW_UPDATE, kNoFlags,
+                        window_update.stream_id());
+  builder.WriteUInt32(window_update.delta());
+  DCHECK_EQ(kWindowUpdateFrameSize, builder.length());
+  return builder.take();
+}
+
+void SpdyFramer::SerializePushPromiseBuilderHelper(
+    const SpdyPushPromiseIR& push_promise,
+    uint8_t* flags,
+    SpdyString* hpack_encoding,
+    size_t* size) {
+  *flags = 0;
+  // This will get overwritten if we overflow into a CONTINUATION frame.
+  *flags = *flags | PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+  // The size of this frame, including variable-length name-value block.
+  *size = kPushPromiseFrameMinimumSize;
+
+  if (push_promise.padded()) {
+    *flags = *flags | PUSH_PROMISE_FLAG_PADDED;
+    *size = *size + kPadLengthFieldSize;
+    *size = *size + push_promise.padding_payload_len();
+  }
+
+  GetHpackEncoder()->EncodeHeaderSet(push_promise.header_block(),
+                                     hpack_encoding);
+  *size = *size + hpack_encoding->size();
+  if (*size > kHttp2MaxControlFrameSendSize) {
+    *size = *size + GetNumberRequiredContinuationFrames(*size) *
+                        kContinuationFrameMinimumSize;
+    *flags = *flags & ~PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+  }
+}
+
+SpdySerializedFrame SpdyFramer::SerializePushPromise(
+    const SpdyPushPromiseIR& push_promise) {
+  uint8_t flags = 0;
+  size_t size = 0;
+  SpdyString hpack_encoding;
+  SerializePushPromiseBuilderHelper(push_promise, &flags, &hpack_encoding,
+                                    &size);
+
+  SpdyFrameBuilder builder(size);
+  size_t length =
+      std::min(size, kHttp2MaxControlFrameSendSize) - kFrameHeaderSize;
+  builder.BeginNewFrame(SpdyFrameType::PUSH_PROMISE, flags,
+                        push_promise.stream_id(), length);
+  int padding_payload_len = 0;
+  if (push_promise.padded()) {
+    builder.WriteUInt8(push_promise.padding_payload_len());
+    builder.WriteUInt32(push_promise.promised_stream_id());
+    DCHECK_EQ(kPushPromiseFrameMinimumSize + kPadLengthFieldSize,
+              builder.length());
+
+    padding_payload_len = push_promise.padding_payload_len();
+  } else {
+    builder.WriteUInt32(push_promise.promised_stream_id());
+    DCHECK_EQ(kPushPromiseFrameMinimumSize, builder.length());
+  }
+
+  WritePayloadWithContinuation(
+      &builder, hpack_encoding, push_promise.stream_id(),
+      SpdyFrameType::PUSH_PROMISE, padding_payload_len);
+
+  if (debug_visitor_) {
+    const size_t header_list_size =
+        GetUncompressedSerializedLength(push_promise.header_block());
+    debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(),
+                                          SpdyFrameType::PUSH_PROMISE,
+                                          header_list_size, builder.length());
+  }
+
+  return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeContinuation(
+    const SpdyContinuationIR& continuation) const {
+  const SpdyString& encoding = continuation.encoding();
+  size_t frame_size = kContinuationFrameMinimumSize + encoding.size();
+  SpdyFrameBuilder builder(frame_size);
+  uint8_t flags = continuation.end_headers() ? HEADERS_FLAG_END_HEADERS : 0;
+  builder.BeginNewFrame(SpdyFrameType::CONTINUATION, flags,
+                        continuation.stream_id());
+  DCHECK_EQ(kFrameHeaderSize, builder.length());
+
+  builder.WriteBytes(encoding.data(), encoding.size());
+  return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeAltSvc(const SpdyAltSvcIR& altsvc_ir) {
+  SpdyString value;
+  size_t size = 0;
+  SerializeAltSvcBuilderHelper(altsvc_ir, &value, &size);
+  SpdyFrameBuilder builder(size);
+  builder.BeginNewFrame(SpdyFrameType::ALTSVC, kNoFlags, altsvc_ir.stream_id());
+
+  builder.WriteUInt16(altsvc_ir.origin().length());
+  builder.WriteBytes(altsvc_ir.origin().data(), altsvc_ir.origin().length());
+  builder.WriteBytes(value.data(), value.length());
+  DCHECK_LT(kGetAltSvcFrameMinimumSize, builder.length());
+  return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializePriority(
+    const SpdyPriorityIR& priority) const {
+  SpdyFrameBuilder builder(kPriorityFrameSize);
+  builder.BeginNewFrame(SpdyFrameType::PRIORITY, kNoFlags,
+                        priority.stream_id());
+
+  builder.WriteUInt32(PackStreamDependencyValues(priority.exclusive(),
+                                                 priority.parent_stream_id()));
+  // Per RFC 7540 section 6.3, serialized weight value is actual value - 1.
+  builder.WriteUInt8(priority.weight() - 1);
+  DCHECK_EQ(kPriorityFrameSize, builder.length());
+  return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeUnknown(
+    const SpdyUnknownIR& unknown) const {
+  const size_t total_size = kFrameHeaderSize + unknown.payload().size();
+  SpdyFrameBuilder builder(total_size);
+  builder.BeginNewUncheckedFrame(unknown.type(), unknown.flags(),
+                                 unknown.stream_id(), unknown.length());
+  builder.WriteBytes(unknown.payload().data(), unknown.payload().size());
+  return builder.take();
+}
+
+namespace {
+
+class FrameSerializationVisitor : public SpdyFrameVisitor {
+ public:
+  explicit FrameSerializationVisitor(SpdyFramer* framer)
+      : framer_(framer), frame_() {}
+  ~FrameSerializationVisitor() override = default;
+
+  SpdySerializedFrame ReleaseSerializedFrame() { return std::move(frame_); }
+
+  void VisitData(const SpdyDataIR& data) override {
+    frame_ = framer_->SerializeData(data);
+  }
+  void VisitRstStream(const SpdyRstStreamIR& rst_stream) override {
+    frame_ = framer_->SerializeRstStream(rst_stream);
+  }
+  void VisitSettings(const SpdySettingsIR& settings) override {
+    frame_ = framer_->SerializeSettings(settings);
+  }
+  void VisitPing(const SpdyPingIR& ping) override {
+    frame_ = framer_->SerializePing(ping);
+  }
+  void VisitGoAway(const SpdyGoAwayIR& goaway) override {
+    frame_ = framer_->SerializeGoAway(goaway);
+  }
+  void VisitHeaders(const SpdyHeadersIR& headers) override {
+    frame_ = framer_->SerializeHeaders(headers);
+  }
+  void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) override {
+    frame_ = framer_->SerializeWindowUpdate(window_update);
+  }
+  void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override {
+    frame_ = framer_->SerializePushPromise(push_promise);
+  }
+  void VisitContinuation(const SpdyContinuationIR& continuation) override {
+    frame_ = framer_->SerializeContinuation(continuation);
+  }
+  void VisitAltSvc(const SpdyAltSvcIR& altsvc) override {
+    frame_ = framer_->SerializeAltSvc(altsvc);
+  }
+  void VisitPriority(const SpdyPriorityIR& priority) override {
+    frame_ = framer_->SerializePriority(priority);
+  }
+  void VisitUnknown(const SpdyUnknownIR& unknown) override {
+    frame_ = framer_->SerializeUnknown(unknown);
+  }
+
+ private:
+  SpdyFramer* framer_;
+  SpdySerializedFrame frame_;
+};
+
+// TODO(diannahu): Use also in frame serialization.
+class FlagsSerializationVisitor : public SpdyFrameVisitor {
+ public:
+  void VisitData(const SpdyDataIR& data) override {
+    flags_ = DATA_FLAG_NONE;
+    if (data.fin()) {
+      flags_ |= DATA_FLAG_FIN;
+    }
+    if (data.padded()) {
+      flags_ |= DATA_FLAG_PADDED;
+    }
+  }
+
+  void VisitRstStream(const SpdyRstStreamIR& rst_stream) override {
+    flags_ = kNoFlags;
+  }
+
+  void VisitSettings(const SpdySettingsIR& settings) override {
+    flags_ = kNoFlags;
+    if (settings.is_ack()) {
+      flags_ |= SETTINGS_FLAG_ACK;
+    }
+  }
+
+  void VisitPing(const SpdyPingIR& ping) override {
+    flags_ = kNoFlags;
+    if (ping.is_ack()) {
+      flags_ |= PING_FLAG_ACK;
+    }
+  }
+
+  void VisitGoAway(const SpdyGoAwayIR& goaway) override { flags_ = kNoFlags; }
+
+  // TODO(diannahu): The END_HEADERS flag is incorrect for HEADERS that require
+  //     CONTINUATION frames.
+  void VisitHeaders(const SpdyHeadersIR& headers) override {
+    flags_ = HEADERS_FLAG_END_HEADERS;
+    if (headers.fin()) {
+      flags_ |= CONTROL_FLAG_FIN;
+    }
+    if (headers.padded()) {
+      flags_ |= HEADERS_FLAG_PADDED;
+    }
+    if (headers.has_priority()) {
+      flags_ |= HEADERS_FLAG_PRIORITY;
+    }
+  }
+
+  void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) override {
+    flags_ = kNoFlags;
+  }
+
+  // TODO(diannahu): The END_PUSH_PROMISE flag is incorrect for PUSH_PROMISEs
+  //     that require CONTINUATION frames.
+  void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override {
+    flags_ = PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+    if (push_promise.padded()) {
+      flags_ |= PUSH_PROMISE_FLAG_PADDED;
+    }
+  }
+
+  // TODO(diannahu): The END_HEADERS flag is incorrect for CONTINUATIONs that
+  //     require CONTINUATION frames.
+  void VisitContinuation(const SpdyContinuationIR& continuation) override {
+    flags_ = HEADERS_FLAG_END_HEADERS;
+  }
+
+  void VisitAltSvc(const SpdyAltSvcIR& altsvc) override { flags_ = kNoFlags; }
+
+  void VisitPriority(const SpdyPriorityIR& priority) override {
+    flags_ = kNoFlags;
+  }
+
+  uint8_t flags() const { return flags_; }
+
+ private:
+  uint8_t flags_ = kNoFlags;
+};
+
+}  // namespace
+
+SpdySerializedFrame SpdyFramer::SerializeFrame(const SpdyFrameIR& frame) {
+  FrameSerializationVisitor visitor(this);
+  frame.Visit(&visitor);
+  return visitor.ReleaseSerializedFrame();
+}
+
+uint8_t SpdyFramer::GetSerializedFlags(const SpdyFrameIR& frame) {
+  FlagsSerializationVisitor visitor;
+  frame.Visit(&visitor);
+  return visitor.flags();
+}
+
+bool SpdyFramer::SerializeData(const SpdyDataIR& data_ir,
+                               ZeroCopyOutputBuffer* output) const {
+  uint8_t flags = DATA_FLAG_NONE;
+  int num_padding_fields = 0;
+  size_t size_with_padding = 0;
+  SerializeDataBuilderHelper(data_ir, &flags, &num_padding_fields,
+                             &size_with_padding);
+  SpdyFrameBuilder builder(size_with_padding, output);
+
+  bool ok =
+      builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id());
+
+  if (data_ir.padded()) {
+    ok = ok && builder.WriteUInt8(data_ir.padding_payload_len() & 0xff);
+  }
+
+  ok = ok && builder.WriteBytes(data_ir.data(), data_ir.data_len());
+  if (data_ir.padding_payload_len() > 0) {
+    SpdyString padding;
+    padding = SpdyString(data_ir.padding_payload_len(), 0);
+    ok = ok && builder.WriteBytes(padding.data(), padding.length());
+  }
+  DCHECK_EQ(size_with_padding, builder.length());
+  return ok;
+}
+
+bool SpdyFramer::SerializeDataFrameHeaderWithPaddingLengthField(
+    const SpdyDataIR& data_ir,
+    ZeroCopyOutputBuffer* output) const {
+  uint8_t flags = DATA_FLAG_NONE;
+  size_t frame_size = 0;
+  size_t num_padding_fields = 0;
+  SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper(
+      data_ir, &flags, &frame_size, &num_padding_fields);
+
+  SpdyFrameBuilder builder(frame_size, output);
+  bool ok = true;
+  ok = ok &&
+       builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id(),
+                             num_padding_fields + data_ir.data_len() +
+                                 data_ir.padding_payload_len());
+  if (data_ir.padded()) {
+    ok = ok && builder.WriteUInt8(data_ir.padding_payload_len() & 0xff);
+  }
+  DCHECK_EQ(frame_size, builder.length());
+  return ok;
+}
+
+bool SpdyFramer::SerializeRstStream(const SpdyRstStreamIR& rst_stream,
+                                    ZeroCopyOutputBuffer* output) const {
+  size_t expected_length = kRstStreamFrameSize;
+  SpdyFrameBuilder builder(expected_length, output);
+  bool ok = builder.BeginNewFrame(SpdyFrameType::RST_STREAM, 0,
+                                  rst_stream.stream_id());
+  ok = ok && builder.WriteUInt32(rst_stream.error_code());
+
+  DCHECK_EQ(expected_length, builder.length());
+  return ok;
+}
+
+bool SpdyFramer::SerializeSettings(const SpdySettingsIR& settings,
+                                   ZeroCopyOutputBuffer* output) const {
+  uint8_t flags = 0;
+  // Size, in bytes, of this SETTINGS frame.
+  size_t size = 0;
+  const SettingsMap* values = &(settings.values());
+  SerializeSettingsBuilderHelper(settings, &flags, values, &size);
+  SpdyFrameBuilder builder(size, output);
+  bool ok = builder.BeginNewFrame(SpdyFrameType::SETTINGS, flags, 0);
+
+  // If this is an ACK, payload should be empty.
+  if (settings.is_ack()) {
+    return ok;
+  }
+
+  DCHECK_EQ(kSettingsFrameMinimumSize, builder.length());
+  for (auto it = values->begin(); it != values->end(); ++it) {
+    int setting_id = it->first;
+    DCHECK_GE(setting_id, 0);
+    ok = ok && builder.WriteUInt16(static_cast<SpdySettingsId>(setting_id)) &&
+         builder.WriteUInt32(it->second);
+  }
+  DCHECK_EQ(size, builder.length());
+  return ok;
+}
+
+bool SpdyFramer::SerializePing(const SpdyPingIR& ping,
+                               ZeroCopyOutputBuffer* output) const {
+  SpdyFrameBuilder builder(kPingFrameSize, output);
+  uint8_t flags = 0;
+  if (ping.is_ack()) {
+    flags |= PING_FLAG_ACK;
+  }
+  bool ok = builder.BeginNewFrame(SpdyFrameType::PING, flags, 0);
+  ok = ok && builder.WriteUInt64(ping.id());
+  DCHECK_EQ(kPingFrameSize, builder.length());
+  return ok;
+}
+
+bool SpdyFramer::SerializeGoAway(const SpdyGoAwayIR& goaway,
+                                 ZeroCopyOutputBuffer* output) const {
+  // Compute the output buffer size, take opaque data into account.
+  size_t expected_length = kGoawayFrameMinimumSize;
+  expected_length += goaway.description().size();
+  SpdyFrameBuilder builder(expected_length, output);
+
+  // Serialize the GOAWAY frame.
+  bool ok = builder.BeginNewFrame(SpdyFrameType::GOAWAY, 0, 0);
+
+  // GOAWAY frames specify the last good stream id.
+  ok = ok && builder.WriteUInt32(goaway.last_good_stream_id()) &&
+       // GOAWAY frames also specify the error status code.
+       builder.WriteUInt32(goaway.error_code());
+
+  // GOAWAY frames may also specify opaque data.
+  if (!goaway.description().empty()) {
+    ok = ok && builder.WriteBytes(goaway.description().data(),
+                                  goaway.description().size());
+  }
+
+  DCHECK_EQ(expected_length, builder.length());
+  return ok;
+}
+
+bool SpdyFramer::SerializeHeaders(const SpdyHeadersIR& headers,
+                                  ZeroCopyOutputBuffer* output) {
+  uint8_t flags = 0;
+  // The size of this frame, including padding (if there is any) and
+  // variable-length header block.
+  size_t size = 0;
+  SpdyString hpack_encoding;
+  int weight = 0;
+  size_t length_field = 0;
+  SerializeHeadersBuilderHelper(headers, &flags, &size, &hpack_encoding,
+                                &weight, &length_field);
+
+  bool ok = true;
+  SpdyFrameBuilder builder(size, output);
+  ok = ok && builder.BeginNewFrame(SpdyFrameType::HEADERS, flags,
+                                   headers.stream_id(), length_field);
+  DCHECK_EQ(kHeadersFrameMinimumSize, builder.length());
+
+  int padding_payload_len = 0;
+  if (headers.padded()) {
+    ok = ok && builder.WriteUInt8(headers.padding_payload_len());
+    padding_payload_len = headers.padding_payload_len();
+  }
+  if (headers.has_priority()) {
+    ok = ok &&
+         builder.WriteUInt32(PackStreamDependencyValues(
+             headers.exclusive(), headers.parent_stream_id())) &&
+         // Per RFC 7540 section 6.3, serialized weight value is weight - 1.
+         builder.WriteUInt8(weight - 1);
+  }
+  ok = ok && WritePayloadWithContinuation(
+                 &builder, hpack_encoding, headers.stream_id(),
+                 SpdyFrameType::HEADERS, padding_payload_len);
+
+  if (debug_visitor_) {
+    const size_t header_list_size =
+        GetUncompressedSerializedLength(headers.header_block());
+    debug_visitor_->OnSendCompressedFrame(headers.stream_id(),
+                                          SpdyFrameType::HEADERS,
+                                          header_list_size, builder.length());
+  }
+
+  return ok;
+}
+
+bool SpdyFramer::SerializeWindowUpdate(const SpdyWindowUpdateIR& window_update,
+                                       ZeroCopyOutputBuffer* output) const {
+  SpdyFrameBuilder builder(kWindowUpdateFrameSize, output);
+  bool ok = builder.BeginNewFrame(SpdyFrameType::WINDOW_UPDATE, kNoFlags,
+                                  window_update.stream_id());
+  ok = ok && builder.WriteUInt32(window_update.delta());
+  DCHECK_EQ(kWindowUpdateFrameSize, builder.length());
+  return ok;
+}
+
+bool SpdyFramer::SerializePushPromise(const SpdyPushPromiseIR& push_promise,
+                                      ZeroCopyOutputBuffer* output) {
+  uint8_t flags = 0;
+  size_t size = 0;
+  SpdyString hpack_encoding;
+  SerializePushPromiseBuilderHelper(push_promise, &flags, &hpack_encoding,
+                                    &size);
+
+  bool ok = true;
+  SpdyFrameBuilder builder(size, output);
+  size_t length =
+      std::min(size, kHttp2MaxControlFrameSendSize) - kFrameHeaderSize;
+  ok = builder.BeginNewFrame(SpdyFrameType::PUSH_PROMISE, flags,
+                             push_promise.stream_id(), length);
+
+  int padding_payload_len = 0;
+  if (push_promise.padded()) {
+    ok = ok && builder.WriteUInt8(push_promise.padding_payload_len()) &&
+         builder.WriteUInt32(push_promise.promised_stream_id());
+    DCHECK_EQ(kPushPromiseFrameMinimumSize + kPadLengthFieldSize,
+              builder.length());
+
+    padding_payload_len = push_promise.padding_payload_len();
+  } else {
+    ok = ok && builder.WriteUInt32(push_promise.promised_stream_id());
+    DCHECK_EQ(kPushPromiseFrameMinimumSize, builder.length());
+  }
+
+  ok = ok && WritePayloadWithContinuation(
+                 &builder, hpack_encoding, push_promise.stream_id(),
+                 SpdyFrameType::PUSH_PROMISE, padding_payload_len);
+
+  if (debug_visitor_) {
+    const size_t header_list_size =
+        GetUncompressedSerializedLength(push_promise.header_block());
+    debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(),
+                                          SpdyFrameType::PUSH_PROMISE,
+                                          header_list_size, builder.length());
+  }
+
+  return ok;
+}
+
+bool SpdyFramer::SerializeContinuation(const SpdyContinuationIR& continuation,
+                                       ZeroCopyOutputBuffer* output) const {
+  const SpdyString& encoding = continuation.encoding();
+  size_t frame_size = kContinuationFrameMinimumSize + encoding.size();
+  SpdyFrameBuilder builder(frame_size, output);
+  uint8_t flags = continuation.end_headers() ? HEADERS_FLAG_END_HEADERS : 0;
+  bool ok = builder.BeginNewFrame(SpdyFrameType::CONTINUATION, flags,
+                                  continuation.stream_id(),
+                                  frame_size - kFrameHeaderSize);
+  DCHECK_EQ(kFrameHeaderSize, builder.length());
+
+  ok = ok && builder.WriteBytes(encoding.data(), encoding.size());
+  return ok;
+}
+
+bool SpdyFramer::SerializeAltSvc(const SpdyAltSvcIR& altsvc_ir,
+                                 ZeroCopyOutputBuffer* output) {
+  SpdyString value;
+  size_t size = 0;
+  SerializeAltSvcBuilderHelper(altsvc_ir, &value, &size);
+  SpdyFrameBuilder builder(size, output);
+  bool ok = builder.BeginNewFrame(SpdyFrameType::ALTSVC, kNoFlags,
+                                  altsvc_ir.stream_id()) &&
+            builder.WriteUInt16(altsvc_ir.origin().length()) &&
+            builder.WriteBytes(altsvc_ir.origin().data(),
+                               altsvc_ir.origin().length()) &&
+            builder.WriteBytes(value.data(), value.length());
+  DCHECK_LT(kGetAltSvcFrameMinimumSize, builder.length());
+  return ok;
+}
+
+bool SpdyFramer::SerializePriority(const SpdyPriorityIR& priority,
+                                   ZeroCopyOutputBuffer* output) const {
+  SpdyFrameBuilder builder(kPriorityFrameSize, output);
+  bool ok = builder.BeginNewFrame(SpdyFrameType::PRIORITY, kNoFlags,
+                                  priority.stream_id());
+  ok = ok &&
+       builder.WriteUInt32(PackStreamDependencyValues(
+           priority.exclusive(), priority.parent_stream_id())) &&
+       // Per RFC 7540 section 6.3, serialized weight value is actual value - 1.
+       builder.WriteUInt8(priority.weight() - 1);
+  DCHECK_EQ(kPriorityFrameSize, builder.length());
+  return ok;
+}
+
+bool SpdyFramer::SerializeUnknown(const SpdyUnknownIR& unknown,
+                                  ZeroCopyOutputBuffer* output) const {
+  const size_t total_size = kFrameHeaderSize + unknown.payload().size();
+  SpdyFrameBuilder builder(total_size, output);
+  bool ok = builder.BeginNewUncheckedFrame(
+      unknown.type(), unknown.flags(), unknown.stream_id(), unknown.length());
+  ok = ok &&
+       builder.WriteBytes(unknown.payload().data(), unknown.payload().size());
+  return ok;
+}
+
+namespace {
+
+class FrameSerializationVisitorWithOutput : public SpdyFrameVisitor {
+ public:
+  explicit FrameSerializationVisitorWithOutput(SpdyFramer* framer,
+                                               ZeroCopyOutputBuffer* output)
+      : framer_(framer), output_(output), result_(false) {}
+  ~FrameSerializationVisitorWithOutput() override = default;
+
+  size_t Result() { return result_; }
+
+  void VisitData(const SpdyDataIR& data) override {
+    result_ = framer_->SerializeData(data, output_);
+  }
+  void VisitRstStream(const SpdyRstStreamIR& rst_stream) override {
+    result_ = framer_->SerializeRstStream(rst_stream, output_);
+  }
+  void VisitSettings(const SpdySettingsIR& settings) override {
+    result_ = framer_->SerializeSettings(settings, output_);
+  }
+  void VisitPing(const SpdyPingIR& ping) override {
+    result_ = framer_->SerializePing(ping, output_);
+  }
+  void VisitGoAway(const SpdyGoAwayIR& goaway) override {
+    result_ = framer_->SerializeGoAway(goaway, output_);
+  }
+  void VisitHeaders(const SpdyHeadersIR& headers) override {
+    result_ = framer_->SerializeHeaders(headers, output_);
+  }
+  void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) override {
+    result_ = framer_->SerializeWindowUpdate(window_update, output_);
+  }
+  void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override {
+    result_ = framer_->SerializePushPromise(push_promise, output_);
+  }
+  void VisitContinuation(const SpdyContinuationIR& continuation) override {
+    result_ = framer_->SerializeContinuation(continuation, output_);
+  }
+  void VisitAltSvc(const SpdyAltSvcIR& altsvc) override {
+    result_ = framer_->SerializeAltSvc(altsvc, output_);
+  }
+  void VisitPriority(const SpdyPriorityIR& priority) override {
+    result_ = framer_->SerializePriority(priority, output_);
+  }
+  void VisitUnknown(const SpdyUnknownIR& unknown) override {
+    result_ = framer_->SerializeUnknown(unknown, output_);
+  }
+
+ private:
+  SpdyFramer* framer_;
+  ZeroCopyOutputBuffer* output_;
+  bool result_;
+};
+
+}  // namespace
+
+size_t SpdyFramer::SerializeFrame(const SpdyFrameIR& frame,
+                                  ZeroCopyOutputBuffer* output) {
+  FrameSerializationVisitorWithOutput visitor(this, output);
+  size_t free_bytes_before = output->BytesFree();
+  frame.Visit(&visitor);
+  return visitor.Result() ? free_bytes_before - output->BytesFree() : 0;
+}
+
+HpackEncoder* SpdyFramer::GetHpackEncoder() {
+  if (hpack_encoder_ == nullptr) {
+    hpack_encoder_ = SpdyMakeUnique<HpackEncoder>(ObtainHpackHuffmanTable());
+    if (!compression_enabled()) {
+      hpack_encoder_->DisableCompression();
+    }
+  }
+  return hpack_encoder_.get();
+}
+
+void SpdyFramer::UpdateHeaderEncoderTableSize(uint32_t value) {
+  GetHpackEncoder()->ApplyHeaderTableSizeSetting(value);
+}
+
+size_t SpdyFramer::header_encoder_table_size() const {
+  if (hpack_encoder_ == nullptr) {
+    return kDefaultHeaderTableSizeSetting;
+  } else {
+    return hpack_encoder_->CurrentHeaderTableSizeSetting();
+  }
+}
+
+void SpdyFramer::SetEncoderHeaderTableDebugVisitor(
+    std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) {
+  GetHpackEncoder()->SetHeaderTableDebugVisitor(std::move(visitor));
+}
+
+size_t SpdyFramer::EstimateMemoryUsage() const {
+  return SpdyEstimateMemoryUsage(hpack_encoder_);
+}
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_framer.h b/spdy/core/spdy_framer.h
new file mode 100644
index 0000000..e268994
--- /dev/null
+++ b/spdy/core/spdy_framer.h
@@ -0,0 +1,365 @@
+// Copyright (c) 2012 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_SPDY_CORE_SPDY_FRAMER_H_
+#define QUICHE_SPDY_CORE_SPDY_FRAMER_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+
+class SpdyFramerPeer;
+class SpdyFramerTest_MultipleContinuationFramesWithIterator_Test;
+class SpdyFramerTest_PushPromiseFramesWithIterator_Test;
+
+}  // namespace test
+
+class SPDY_EXPORT_PRIVATE SpdyFrameSequence {
+ public:
+  virtual ~SpdyFrameSequence() {}
+
+  // Serializes the next frame in the sequence to |output|. Returns the number
+  // of bytes written to |output|.
+  virtual size_t NextFrame(ZeroCopyOutputBuffer* output) = 0;
+
+  // Returns true iff there is at least one more frame in the sequence.
+  virtual bool HasNextFrame() const = 0;
+
+  // Get SpdyFrameIR of the frame to be serialized.
+  virtual const SpdyFrameIR& GetIR() const = 0;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyFramer {
+ public:
+  enum CompressionOption {
+    ENABLE_COMPRESSION,
+    DISABLE_COMPRESSION,
+  };
+
+  // Create a SpdyFrameSequence to serialize |frame_ir|.
+  static std::unique_ptr<SpdyFrameSequence> CreateIterator(
+      SpdyFramer* framer,
+      std::unique_ptr<const SpdyFrameIR> frame_ir);
+
+  // Gets the serialized flags for the given |frame|.
+  static uint8_t GetSerializedFlags(const SpdyFrameIR& frame);
+
+  // Serialize a data frame.
+  static SpdySerializedFrame SerializeData(const SpdyDataIR& data_ir);
+  // Serializes the data frame header and optionally padding length fields,
+  // excluding actual data payload and padding.
+  static SpdySerializedFrame SerializeDataFrameHeaderWithPaddingLengthField(
+      const SpdyDataIR& data_ir);
+
+  // Serializes a WINDOW_UPDATE frame. The WINDOW_UPDATE
+  // frame is used to implement per stream flow control.
+  static SpdySerializedFrame SerializeWindowUpdate(
+      const SpdyWindowUpdateIR& window_update);
+
+  explicit SpdyFramer(CompressionOption option);
+
+  virtual ~SpdyFramer();
+
+  // Set debug callbacks to be called from the framer. The debug visitor is
+  // completely optional and need not be set in order for normal operation.
+  // If this is called multiple times, only the last visitor will be used.
+  void set_debug_visitor(SpdyFramerDebugVisitorInterface* debug_visitor);
+
+  SpdySerializedFrame SerializeRstStream(
+      const SpdyRstStreamIR& rst_stream) const;
+
+  // Serializes a SETTINGS frame. The SETTINGS frame is
+  // used to communicate name/value pairs relevant to the communication channel.
+  SpdySerializedFrame SerializeSettings(const SpdySettingsIR& settings) const;
+
+  // Serializes a PING frame. The unique_id is used to
+  // identify the ping request/response.
+  SpdySerializedFrame SerializePing(const SpdyPingIR& ping) const;
+
+  // Serializes a GOAWAY frame. The GOAWAY frame is used
+  // prior to the shutting down of the TCP connection, and includes the
+  // stream_id of the last stream the sender of the frame is willing to process
+  // to completion.
+  SpdySerializedFrame SerializeGoAway(const SpdyGoAwayIR& goaway) const;
+
+  // Serializes a HEADERS frame. The HEADERS frame is used
+  // for sending headers.
+  SpdySerializedFrame SerializeHeaders(const SpdyHeadersIR& headers);
+
+  // Serializes a PUSH_PROMISE frame. The PUSH_PROMISE frame is used
+  // to inform the client that it will be receiving an additional stream
+  // in response to the original request. The frame includes synthesized
+  // headers to explain the upcoming data.
+  SpdySerializedFrame SerializePushPromise(
+      const SpdyPushPromiseIR& push_promise);
+
+  // Serializes a CONTINUATION frame. The CONTINUATION frame is used
+  // to continue a sequence of header block fragments.
+  SpdySerializedFrame SerializeContinuation(
+      const SpdyContinuationIR& continuation) const;
+
+  // Serializes an ALTSVC frame. The ALTSVC frame advertises the
+  // availability of an alternative service to the client.
+  SpdySerializedFrame SerializeAltSvc(const SpdyAltSvcIR& altsvc);
+
+  // Serializes a PRIORITY frame. The PRIORITY frame advises a change in
+  // the relative priority of the given stream.
+  SpdySerializedFrame SerializePriority(const SpdyPriorityIR& priority) const;
+
+  // Serializes an unknown frame given a frame header and payload.
+  SpdySerializedFrame SerializeUnknown(const SpdyUnknownIR& unknown) const;
+
+  // Serialize a frame of unknown type.
+  SpdySerializedFrame SerializeFrame(const SpdyFrameIR& frame);
+
+  // Serialize a data frame.
+  bool SerializeData(const SpdyDataIR& data,
+                     ZeroCopyOutputBuffer* output) const;
+
+  // Serializes the data frame header and optionally padding length fields,
+  // excluding actual data payload and padding.
+  bool SerializeDataFrameHeaderWithPaddingLengthField(
+      const SpdyDataIR& data,
+      ZeroCopyOutputBuffer* output) const;
+
+  bool SerializeRstStream(const SpdyRstStreamIR& rst_stream,
+                          ZeroCopyOutputBuffer* output) const;
+
+  // Serializes a SETTINGS frame. The SETTINGS frame is
+  // used to communicate name/value pairs relevant to the communication channel.
+  bool SerializeSettings(const SpdySettingsIR& settings,
+                         ZeroCopyOutputBuffer* output) const;
+
+  // Serializes a PING frame. The unique_id is used to
+  // identify the ping request/response.
+  bool SerializePing(const SpdyPingIR& ping,
+                     ZeroCopyOutputBuffer* output) const;
+
+  // Serializes a GOAWAY frame. The GOAWAY frame is used
+  // prior to the shutting down of the TCP connection, and includes the
+  // stream_id of the last stream the sender of the frame is willing to process
+  // to completion.
+  bool SerializeGoAway(const SpdyGoAwayIR& goaway,
+                       ZeroCopyOutputBuffer* output) const;
+
+  // Serializes a HEADERS frame. The HEADERS frame is used
+  // for sending headers.
+  bool SerializeHeaders(const SpdyHeadersIR& headers,
+                        ZeroCopyOutputBuffer* output);
+
+  // Serializes a WINDOW_UPDATE frame. The WINDOW_UPDATE
+  // frame is used to implement per stream flow control.
+  bool SerializeWindowUpdate(const SpdyWindowUpdateIR& window_update,
+                             ZeroCopyOutputBuffer* output) const;
+
+  // Serializes a PUSH_PROMISE frame. The PUSH_PROMISE frame is used
+  // to inform the client that it will be receiving an additional stream
+  // in response to the original request. The frame includes synthesized
+  // headers to explain the upcoming data.
+  bool SerializePushPromise(const SpdyPushPromiseIR& push_promise,
+                            ZeroCopyOutputBuffer* output);
+
+  // Serializes a CONTINUATION frame. The CONTINUATION frame is used
+  // to continue a sequence of header block fragments.
+  bool SerializeContinuation(const SpdyContinuationIR& continuation,
+                             ZeroCopyOutputBuffer* output) const;
+
+  // Serializes an ALTSVC frame. The ALTSVC frame advertises the
+  // availability of an alternative service to the client.
+  bool SerializeAltSvc(const SpdyAltSvcIR& altsvc,
+                       ZeroCopyOutputBuffer* output);
+
+  // Serializes a PRIORITY frame. The PRIORITY frame advises a change in
+  // the relative priority of the given stream.
+  bool SerializePriority(const SpdyPriorityIR& priority,
+                         ZeroCopyOutputBuffer* output) const;
+
+  // Serializes an unknown frame given a frame header and payload.
+  bool SerializeUnknown(const SpdyUnknownIR& unknown,
+                        ZeroCopyOutputBuffer* output) const;
+
+  // Serialize a frame of unknown type.
+  size_t SerializeFrame(const SpdyFrameIR& frame, ZeroCopyOutputBuffer* output);
+
+  // Returns whether this SpdyFramer will compress header blocks using HPACK.
+  bool compression_enabled() const {
+    return compression_option_ == ENABLE_COMPRESSION;
+  }
+
+  void SetHpackIndexingPolicy(HpackEncoder::IndexingPolicy policy) {
+    GetHpackEncoder()->SetIndexingPolicy(std::move(policy));
+  }
+
+  // Updates the maximum size of the header encoder compression table.
+  void UpdateHeaderEncoderTableSize(uint32_t value);
+
+  // Returns the maximum size of the header encoder compression table.
+  size_t header_encoder_table_size() const;
+
+  void SetEncoderHeaderTableDebugVisitor(
+      std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor);
+
+  // Get (and lazily initialize) the HPACK encoder state.
+  HpackEncoder* GetHpackEncoder();
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+ protected:
+  friend class test::SpdyFramerPeer;
+  friend class test::SpdyFramerTest_MultipleContinuationFramesWithIterator_Test;
+  friend class test::SpdyFramerTest_PushPromiseFramesWithIterator_Test;
+
+  // Iteratively converts a SpdyFrameIR into an appropriate sequence of Spdy
+  // frames.
+  // Example usage:
+  // std::unique_ptr<SpdyFrameSequence> it = CreateIterator(framer, frame_ir);
+  // while (it->HasNextFrame()) {
+  //   if(it->NextFrame(output) == 0) {
+  //     // Write failed;
+  //   }
+  // }
+  class SPDY_EXPORT_PRIVATE SpdyFrameIterator : public SpdyFrameSequence {
+   public:
+    // Creates an iterator with the provided framer.
+    // Does not take ownership of |framer|.
+    // |framer| must outlive this instance.
+    explicit SpdyFrameIterator(SpdyFramer* framer);
+    ~SpdyFrameIterator() override;
+
+    // Serializes the next frame in the sequence to |output|. Returns the number
+    // of bytes written to |output|.
+    size_t NextFrame(ZeroCopyOutputBuffer* output) override;
+
+    // Returns true iff there is at least one more frame in the sequence.
+    bool HasNextFrame() const override;
+
+    // SpdyFrameIterator is neither copyable nor movable.
+    SpdyFrameIterator(const SpdyFrameIterator&) = delete;
+    SpdyFrameIterator& operator=(const SpdyFrameIterator&) = delete;
+
+   protected:
+    virtual size_t GetFrameSizeSansBlock() const = 0;
+    virtual bool SerializeGivenEncoding(const SpdyString& encoding,
+                                        ZeroCopyOutputBuffer* output) const = 0;
+
+    SpdyFramer* GetFramer() const { return framer_; }
+
+    void SetEncoder(const SpdyFrameWithHeaderBlockIR* ir) {
+      encoder_ =
+          framer_->GetHpackEncoder()->EncodeHeaderSet(ir->header_block());
+    }
+
+    bool has_next_frame() const { return has_next_frame_; }
+
+   private:
+    SpdyFramer* const framer_;
+    std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoder_;
+    bool is_first_frame_;
+    bool has_next_frame_;
+  };
+
+  // Iteratively converts a SpdyHeadersIR (with a possibly huge
+  // SpdyHeaderBlock) into an appropriate sequence of SpdySerializedFrames, and
+  // write to the output.
+  class SPDY_EXPORT_PRIVATE SpdyHeaderFrameIterator : public SpdyFrameIterator {
+   public:
+    // Does not take ownership of |framer|. Take ownership of |headers_ir|.
+    SpdyHeaderFrameIterator(SpdyFramer* framer,
+                            std::unique_ptr<const SpdyHeadersIR> headers_ir);
+
+    ~SpdyHeaderFrameIterator() override;
+
+   private:
+    const SpdyFrameIR& GetIR() const override;
+    size_t GetFrameSizeSansBlock() const override;
+    bool SerializeGivenEncoding(const SpdyString& encoding,
+                                ZeroCopyOutputBuffer* output) const override;
+
+    const std::unique_ptr<const SpdyHeadersIR> headers_ir_;
+  };
+
+  // Iteratively converts a SpdyPushPromiseIR (with a possibly huge
+  // SpdyHeaderBlock) into an appropriate sequence of SpdySerializedFrames, and
+  // write to the output.
+  class SPDY_EXPORT_PRIVATE SpdyPushPromiseFrameIterator
+      : public SpdyFrameIterator {
+   public:
+    // Does not take ownership of |framer|. Take ownership of |push_promise_ir|.
+    SpdyPushPromiseFrameIterator(
+        SpdyFramer* framer,
+        std::unique_ptr<const SpdyPushPromiseIR> push_promise_ir);
+
+    ~SpdyPushPromiseFrameIterator() override;
+
+   private:
+    const SpdyFrameIR& GetIR() const override;
+    size_t GetFrameSizeSansBlock() const override;
+    bool SerializeGivenEncoding(const SpdyString& encoding,
+                                ZeroCopyOutputBuffer* output) const override;
+
+    const std::unique_ptr<const SpdyPushPromiseIR> push_promise_ir_;
+  };
+
+  // Converts a SpdyFrameIR into one Spdy frame (a sequence of length 1), and
+  // write it to the output.
+  class SPDY_EXPORT_PRIVATE SpdyControlFrameIterator
+      : public SpdyFrameSequence {
+   public:
+    SpdyControlFrameIterator(SpdyFramer* framer,
+                             std::unique_ptr<const SpdyFrameIR> frame_ir);
+    ~SpdyControlFrameIterator() override;
+
+    size_t NextFrame(ZeroCopyOutputBuffer* output) override;
+
+    bool HasNextFrame() const override;
+
+    const SpdyFrameIR& GetIR() const override;
+
+   private:
+    SpdyFramer* const framer_;
+    std::unique_ptr<const SpdyFrameIR> frame_ir_;
+    bool has_next_frame_ = true;
+  };
+
+ private:
+  void SerializeHeadersBuilderHelper(const SpdyHeadersIR& headers,
+                                     uint8_t* flags,
+                                     size_t* size,
+                                     SpdyString* hpack_encoding,
+                                     int* weight,
+                                     size_t* length_field);
+  void SerializePushPromiseBuilderHelper(const SpdyPushPromiseIR& push_promise,
+                                         uint8_t* flags,
+                                         SpdyString* hpack_encoding,
+                                         size_t* size);
+
+  std::unique_ptr<HpackEncoder> hpack_encoder_;
+
+  SpdyFramerDebugVisitorInterface* debug_visitor_;
+
+  // Determines whether HPACK compression is used.
+  const CompressionOption compression_option_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_FRAMER_H_
diff --git a/spdy/core/spdy_framer_test.cc b/spdy/core/spdy_framer_test.cc
new file mode 100644
index 0000000..a08d7d7
--- /dev/null
+++ b/spdy/core/spdy_framer_test.cc
@@ -0,0 +1,4833 @@
+// Copyright (c) 2012 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/spdy/core/spdy_framer.h"
+
+#include <stdlib.h>
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <tuple>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/array_output_buffer.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_flags.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+using ::http2::Http2DecoderAdapter;
+using ::testing::_;
+
+namespace spdy {
+
+namespace test {
+
+namespace {
+
+const int64_t kSize = 1024 * 1024;
+char output_buffer[kSize] = "";
+
+// frame_list_char is used to hold frames to be compared with output_buffer.
+const int64_t buffer_size = 64 * 1024;
+char frame_list_char[buffer_size] = "";
+}  // namespace
+
+class MockDebugVisitor : public SpdyFramerDebugVisitorInterface {
+ public:
+  MOCK_METHOD4(OnSendCompressedFrame,
+               void(SpdyStreamId stream_id,
+                    SpdyFrameType type,
+                    size_t payload_len,
+                    size_t frame_len));
+
+  MOCK_METHOD3(OnReceiveCompressedFrame,
+               void(SpdyStreamId stream_id,
+                    SpdyFrameType type,
+                    size_t frame_len));
+};
+
+MATCHER_P(IsFrameUnionOf, frame_list, "") {
+  size_t size_verified = 0;
+  for (const auto& frame : *frame_list) {
+    if (arg.size() < size_verified + frame.size()) {
+      LOG(FATAL) << "Incremental header serialization should not lead to a "
+                 << "higher total frame length than non-incremental method.";
+      return false;
+    }
+    if (memcmp(arg.data() + size_verified, frame.data(), frame.size())) {
+      CompareCharArraysWithHexError(
+          "Header serialization methods should be equivalent: ",
+          reinterpret_cast<unsigned char*>(arg.data() + size_verified),
+          frame.size(), reinterpret_cast<unsigned char*>(frame.data()),
+          frame.size());
+      return false;
+    }
+    size_verified += frame.size();
+  }
+  return size_verified == arg.size();
+}
+
+class SpdyFramerPeer {
+ public:
+  // TODO(dahollings): Remove these methods when deprecating non-incremental
+  // header serialization path.
+  static std::unique_ptr<SpdyHeadersIR> CloneSpdyHeadersIR(
+      const SpdyHeadersIR& headers) {
+    auto new_headers = SpdyMakeUnique<SpdyHeadersIR>(
+        headers.stream_id(), headers.header_block().Clone());
+    new_headers->set_fin(headers.fin());
+    new_headers->set_has_priority(headers.has_priority());
+    new_headers->set_weight(headers.weight());
+    new_headers->set_parent_stream_id(headers.parent_stream_id());
+    new_headers->set_exclusive(headers.exclusive());
+    if (headers.padded()) {
+      new_headers->set_padding_len(headers.padding_payload_len() + 1);
+    }
+    return new_headers;
+  }
+
+  static SpdySerializedFrame SerializeHeaders(SpdyFramer* framer,
+                                              const SpdyHeadersIR& headers) {
+    SpdySerializedFrame serialized_headers_old_version(
+        framer->SerializeHeaders(headers));
+    framer->hpack_encoder_.reset(nullptr);
+    auto* saved_debug_visitor = framer->debug_visitor_;
+    framer->debug_visitor_ = nullptr;
+
+    std::vector<SpdySerializedFrame> frame_list;
+    ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size);
+    SpdyFramer::SpdyHeaderFrameIterator it(framer, CloneSpdyHeadersIR(headers));
+    while (it.HasNextFrame()) {
+      size_t size_before = frame_list_buffer.Size();
+      EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u);
+      frame_list.emplace_back(
+          SpdySerializedFrame(frame_list_buffer.Begin() + size_before,
+                              frame_list_buffer.Size() - size_before, false));
+    }
+    framer->debug_visitor_ = saved_debug_visitor;
+
+    EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list));
+    return serialized_headers_old_version;
+  }
+
+  static SpdySerializedFrame SerializeHeaders(SpdyFramer* framer,
+                                              const SpdyHeadersIR& headers,
+                                              ArrayOutputBuffer* output) {
+    if (output == nullptr) {
+      return SerializeHeaders(framer, headers);
+    }
+    output->Reset();
+    EXPECT_TRUE(framer->SerializeHeaders(headers, output));
+    SpdySerializedFrame serialized_headers_old_version(output->Begin(),
+                                                       output->Size(), false);
+    framer->hpack_encoder_.reset(nullptr);
+    auto* saved_debug_visitor = framer->debug_visitor_;
+    framer->debug_visitor_ = nullptr;
+
+    std::vector<SpdySerializedFrame> frame_list;
+    ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size);
+    SpdyFramer::SpdyHeaderFrameIterator it(framer, CloneSpdyHeadersIR(headers));
+    while (it.HasNextFrame()) {
+      size_t size_before = frame_list_buffer.Size();
+      EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u);
+      frame_list.emplace_back(
+          SpdySerializedFrame(frame_list_buffer.Begin() + size_before,
+                              frame_list_buffer.Size() - size_before, false));
+    }
+    framer->debug_visitor_ = saved_debug_visitor;
+
+    EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list));
+    return serialized_headers_old_version;
+  }
+
+  static std::unique_ptr<SpdyPushPromiseIR> CloneSpdyPushPromiseIR(
+      const SpdyPushPromiseIR& push_promise) {
+    auto new_push_promise = SpdyMakeUnique<SpdyPushPromiseIR>(
+        push_promise.stream_id(), push_promise.promised_stream_id(),
+        push_promise.header_block().Clone());
+    new_push_promise->set_fin(push_promise.fin());
+    if (push_promise.padded()) {
+      new_push_promise->set_padding_len(push_promise.padding_payload_len() + 1);
+    }
+    return new_push_promise;
+  }
+
+  static SpdySerializedFrame SerializePushPromise(
+      SpdyFramer* framer,
+      const SpdyPushPromiseIR& push_promise) {
+    SpdySerializedFrame serialized_headers_old_version =
+        framer->SerializePushPromise(push_promise);
+    framer->hpack_encoder_.reset(nullptr);
+    auto* saved_debug_visitor = framer->debug_visitor_;
+    framer->debug_visitor_ = nullptr;
+
+    std::vector<SpdySerializedFrame> frame_list;
+    ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size);
+    frame_list_buffer.Reset();
+    SpdyFramer::SpdyPushPromiseFrameIterator it(
+        framer, CloneSpdyPushPromiseIR(push_promise));
+    while (it.HasNextFrame()) {
+      size_t size_before = frame_list_buffer.Size();
+      EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u);
+      frame_list.emplace_back(
+          SpdySerializedFrame(frame_list_buffer.Begin() + size_before,
+                              frame_list_buffer.Size() - size_before, false));
+    }
+    framer->debug_visitor_ = saved_debug_visitor;
+
+    EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list));
+    return serialized_headers_old_version;
+  }
+
+  static SpdySerializedFrame SerializePushPromise(
+      SpdyFramer* framer,
+      const SpdyPushPromiseIR& push_promise,
+      ArrayOutputBuffer* output) {
+    if (output == nullptr) {
+      return SerializePushPromise(framer, push_promise);
+    }
+    output->Reset();
+    EXPECT_TRUE(framer->SerializePushPromise(push_promise, output));
+    SpdySerializedFrame serialized_headers_old_version(output->Begin(),
+                                                       output->Size(), false);
+    framer->hpack_encoder_.reset(nullptr);
+    auto* saved_debug_visitor = framer->debug_visitor_;
+    framer->debug_visitor_ = nullptr;
+
+    std::vector<SpdySerializedFrame> frame_list;
+    ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size);
+    frame_list_buffer.Reset();
+    SpdyFramer::SpdyPushPromiseFrameIterator it(
+        framer, CloneSpdyPushPromiseIR(push_promise));
+    while (it.HasNextFrame()) {
+      size_t size_before = frame_list_buffer.Size();
+      EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u);
+      frame_list.emplace_back(
+          SpdySerializedFrame(frame_list_buffer.Begin() + size_before,
+                              frame_list_buffer.Size() - size_before, false));
+    }
+    framer->debug_visitor_ = saved_debug_visitor;
+
+    EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list));
+    return serialized_headers_old_version;
+  }
+};
+
+class TestSpdyVisitor : public SpdyFramerVisitorInterface,
+                        public SpdyFramerDebugVisitorInterface {
+ public:
+  // This is larger than our max frame size because header blocks that
+  // are too long can spill over into CONTINUATION frames.
+  static const size_t kDefaultHeaderBufferSize = 16 * 1024 * 1024;
+
+  explicit TestSpdyVisitor(SpdyFramer::CompressionOption option)
+      : framer_(option),
+        error_count_(0),
+        headers_frame_count_(0),
+        push_promise_frame_count_(0),
+        goaway_count_(0),
+        setting_count_(0),
+        settings_ack_sent_(0),
+        settings_ack_received_(0),
+        continuation_count_(0),
+        altsvc_count_(0),
+        priority_count_(0),
+        on_unknown_frame_result_(false),
+        last_window_update_stream_(0),
+        last_window_update_delta_(0),
+        last_push_promise_stream_(0),
+        last_push_promise_promised_stream_(0),
+        data_bytes_(0),
+        fin_frame_count_(0),
+        fin_flag_count_(0),
+        end_of_stream_count_(0),
+        control_frame_header_data_count_(0),
+        zero_length_control_frame_header_data_count_(0),
+        data_frame_count_(0),
+        last_payload_len_(0),
+        last_frame_len_(0),
+        header_buffer_(new char[kDefaultHeaderBufferSize]),
+        header_buffer_length_(0),
+        header_buffer_size_(kDefaultHeaderBufferSize),
+        header_stream_id_(static_cast<SpdyStreamId>(-1)),
+        header_control_type_(SpdyFrameType::DATA),
+        header_buffer_valid_(false) {}
+
+  void OnError(Http2DecoderAdapter::SpdyFramerError error) override {
+    VLOG(1) << "SpdyFramer Error: "
+            << Http2DecoderAdapter::SpdyFramerErrorToString(error);
+    ++error_count_;
+  }
+
+  void OnDataFrameHeader(SpdyStreamId stream_id,
+                         size_t length,
+                         bool fin) override {
+    VLOG(1) << "OnDataFrameHeader(" << stream_id << ", " << length << ", "
+            << fin << ")";
+    ++data_frame_count_;
+    header_stream_id_ = stream_id;
+  }
+
+  void OnStreamFrameData(SpdyStreamId stream_id,
+                         const char* data,
+                         size_t len) override {
+    VLOG(1) << "OnStreamFrameData(" << stream_id << ", data, " << len << ", "
+            << ")   data:\n"
+            << SpdyHexDump(SpdyStringPiece(data, len));
+    EXPECT_EQ(header_stream_id_, stream_id);
+
+    data_bytes_ += len;
+  }
+
+  void OnStreamEnd(SpdyStreamId stream_id) override {
+    VLOG(1) << "OnStreamEnd(" << stream_id << ")";
+    EXPECT_EQ(header_stream_id_, stream_id);
+    ++end_of_stream_count_;
+  }
+
+  void OnStreamPadLength(SpdyStreamId stream_id, size_t value) override {
+    VLOG(1) << "OnStreamPadding(" << stream_id << ", " << value << ")\n";
+    EXPECT_EQ(header_stream_id_, stream_id);
+    // Count the padding length field byte against total data bytes.
+    data_bytes_ += 1;
+  }
+
+  void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {
+    VLOG(1) << "OnStreamPadding(" << stream_id << ", " << len << ")\n";
+    EXPECT_EQ(header_stream_id_, stream_id);
+    data_bytes_ += len;
+  }
+
+  SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+      SpdyStreamId stream_id) override {
+    if (headers_handler_ == nullptr) {
+      headers_handler_ = SpdyMakeUnique<TestHeadersHandler>();
+    }
+    return headers_handler_.get();
+  }
+
+  void OnHeaderFrameEnd(SpdyStreamId stream_id) override {
+    CHECK(headers_handler_ != nullptr);
+    headers_ = headers_handler_->decoded_block().Clone();
+    header_bytes_received_ = headers_handler_->header_bytes_parsed();
+    headers_handler_.reset();
+  }
+
+  void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override {
+    VLOG(1) << "OnRstStream(" << stream_id << ", " << error_code << ")";
+    ++fin_frame_count_;
+  }
+
+  void OnSetting(SpdySettingsId id, uint32_t value) override {
+    VLOG(1) << "OnSetting(" << id << ", " << std::hex << value << ")";
+    ++setting_count_;
+  }
+
+  void OnSettingsAck() override {
+    VLOG(1) << "OnSettingsAck";
+    ++settings_ack_received_;
+  }
+
+  void OnSettingsEnd() override {
+    VLOG(1) << "OnSettingsEnd";
+    ++settings_ack_sent_;
+  }
+
+  void OnPing(SpdyPingId unique_id, bool is_ack) override {
+    LOG(DFATAL) << "OnPing(" << unique_id << ", " << (is_ack ? 1 : 0) << ")";
+  }
+
+  void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                SpdyErrorCode error_code) override {
+    VLOG(1) << "OnGoAway(" << last_accepted_stream_id << ", " << error_code
+            << ")";
+    ++goaway_count_;
+  }
+
+  void OnHeaders(SpdyStreamId stream_id,
+                 bool has_priority,
+                 int weight,
+                 SpdyStreamId parent_stream_id,
+                 bool exclusive,
+                 bool fin,
+                 bool end) override {
+    VLOG(1) << "OnHeaders(" << stream_id << ", " << has_priority << ", "
+            << weight << ", " << parent_stream_id << ", " << exclusive << ", "
+            << fin << ", " << end << ")";
+    ++headers_frame_count_;
+    InitHeaderStreaming(SpdyFrameType::HEADERS, stream_id);
+    if (fin) {
+      ++fin_flag_count_;
+    }
+    header_has_priority_ = has_priority;
+    header_parent_stream_id_ = parent_stream_id;
+    header_exclusive_ = exclusive;
+  }
+
+  void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override {
+    VLOG(1) << "OnWindowUpdate(" << stream_id << ", " << delta_window_size
+            << ")";
+    last_window_update_stream_ = stream_id;
+    last_window_update_delta_ = delta_window_size;
+  }
+
+  void OnPushPromise(SpdyStreamId stream_id,
+                     SpdyStreamId promised_stream_id,
+                     bool end) override {
+    VLOG(1) << "OnPushPromise(" << stream_id << ", " << promised_stream_id
+            << ", " << end << ")";
+    ++push_promise_frame_count_;
+    InitHeaderStreaming(SpdyFrameType::PUSH_PROMISE, stream_id);
+    last_push_promise_stream_ = stream_id;
+    last_push_promise_promised_stream_ = promised_stream_id;
+  }
+
+  void OnContinuation(SpdyStreamId stream_id, bool end) override {
+    VLOG(1) << "OnContinuation(" << stream_id << ", " << end << ")";
+    ++continuation_count_;
+  }
+
+  void OnAltSvc(SpdyStreamId stream_id,
+                SpdyStringPiece origin,
+                const SpdyAltSvcWireFormat::AlternativeServiceVector&
+                    altsvc_vector) override {
+    VLOG(1) << "OnAltSvc(" << stream_id << ", \"" << origin
+            << "\", altsvc_vector)";
+    test_altsvc_ir_ = SpdyMakeUnique<SpdyAltSvcIR>(stream_id);
+    if (origin.length() > 0) {
+      test_altsvc_ir_->set_origin(SpdyString(origin));
+    }
+    for (const auto& altsvc : altsvc_vector) {
+      test_altsvc_ir_->add_altsvc(altsvc);
+    }
+    ++altsvc_count_;
+  }
+
+  void OnPriority(SpdyStreamId stream_id,
+                  SpdyStreamId parent_stream_id,
+                  int weight,
+                  bool exclusive) override {
+    VLOG(1) << "OnPriority(" << stream_id << ", " << parent_stream_id << ", "
+            << weight << ", " << (exclusive ? 1 : 0) << ")";
+    ++priority_count_;
+  }
+
+  bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override {
+    VLOG(1) << "OnUnknownFrame(" << stream_id << ", " << frame_type << ")";
+    return on_unknown_frame_result_;
+  }
+
+  void OnSendCompressedFrame(SpdyStreamId stream_id,
+                             SpdyFrameType type,
+                             size_t payload_len,
+                             size_t frame_len) override {
+    VLOG(1) << "OnSendCompressedFrame(" << stream_id << ", " << type << ", "
+            << payload_len << ", " << frame_len << ")";
+    last_payload_len_ = payload_len;
+    last_frame_len_ = frame_len;
+  }
+
+  void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+                                SpdyFrameType type,
+                                size_t frame_len) override {
+    VLOG(1) << "OnReceiveCompressedFrame(" << stream_id << ", " << type << ", "
+            << frame_len << ")";
+    last_frame_len_ = frame_len;
+  }
+
+  // Convenience function which runs a framer simulation with particular input.
+  void SimulateInFramer(const unsigned char* input, size_t size) {
+    deframer_.set_visitor(this);
+    size_t input_remaining = size;
+    const char* input_ptr = reinterpret_cast<const char*>(input);
+    while (input_remaining > 0 && deframer_.spdy_framer_error() ==
+                                      Http2DecoderAdapter::SPDY_NO_ERROR) {
+      // To make the tests more interesting, we feed random (and small) chunks
+      // into the framer.  This simulates getting strange-sized reads from
+      // the socket.
+      const size_t kMaxReadSize = 32;
+      size_t bytes_read =
+          (rand() % std::min(input_remaining, kMaxReadSize)) + 1;
+      size_t bytes_processed = deframer_.ProcessInput(input_ptr, bytes_read);
+      input_remaining -= bytes_processed;
+      input_ptr += bytes_processed;
+    }
+  }
+
+  void InitHeaderStreaming(SpdyFrameType header_control_type,
+                           SpdyStreamId stream_id) {
+    if (!IsDefinedFrameType(SerializeFrameType(header_control_type))) {
+      DLOG(FATAL) << "Attempted to init header streaming with "
+                  << "invalid control frame type: " << header_control_type;
+    }
+    memset(header_buffer_.get(), 0, header_buffer_size_);
+    header_buffer_length_ = 0;
+    header_stream_id_ = stream_id;
+    header_control_type_ = header_control_type;
+    header_buffer_valid_ = true;
+  }
+
+  void set_extension_visitor(ExtensionVisitorInterface* extension) {
+    deframer_.set_extension_visitor(extension);
+  }
+
+  // Override the default buffer size (16K). Call before using the framer!
+  void set_header_buffer_size(size_t header_buffer_size) {
+    header_buffer_size_ = header_buffer_size;
+    header_buffer_.reset(new char[header_buffer_size]);
+  }
+
+  SpdyFramer framer_;
+  Http2DecoderAdapter deframer_;
+
+  // Counters from the visitor callbacks.
+  int error_count_;
+  int headers_frame_count_;
+  int push_promise_frame_count_;
+  int goaway_count_;
+  int setting_count_;
+  int settings_ack_sent_;
+  int settings_ack_received_;
+  int continuation_count_;
+  int altsvc_count_;
+  int priority_count_;
+  std::unique_ptr<SpdyAltSvcIR> test_altsvc_ir_;
+  bool on_unknown_frame_result_;
+  SpdyStreamId last_window_update_stream_;
+  int last_window_update_delta_;
+  SpdyStreamId last_push_promise_stream_;
+  SpdyStreamId last_push_promise_promised_stream_;
+  int data_bytes_;
+  int fin_frame_count_;      // The count of RST_STREAM type frames received.
+  int fin_flag_count_;       // The count of frames with the FIN flag set.
+  int end_of_stream_count_;  // The count of zero-length data frames.
+  int control_frame_header_data_count_;  // The count of chunks received.
+  // The count of zero-length control frame header data chunks received.
+  int zero_length_control_frame_header_data_count_;
+  int data_frame_count_;
+  size_t last_payload_len_;
+  size_t last_frame_len_;
+
+  // Header block streaming state:
+  std::unique_ptr<char[]> header_buffer_;
+  size_t header_buffer_length_;
+  size_t header_buffer_size_;
+  size_t header_bytes_received_;
+  SpdyStreamId header_stream_id_;
+  SpdyFrameType header_control_type_;
+  bool header_buffer_valid_;
+  std::unique_ptr<TestHeadersHandler> headers_handler_;
+  SpdyHeaderBlock headers_;
+  bool header_has_priority_;
+  SpdyStreamId header_parent_stream_id_;
+  bool header_exclusive_;
+};
+
+class TestExtension : public ExtensionVisitorInterface {
+ public:
+  void OnSetting(SpdySettingsId id, uint32_t value) override {
+    settings_received_.push_back({id, value});
+  }
+
+  // Called when non-standard frames are received.
+  bool OnFrameHeader(SpdyStreamId stream_id,
+                     size_t length,
+                     uint8_t type,
+                     uint8_t flags) override {
+    stream_id_ = stream_id;
+    length_ = length;
+    type_ = type;
+    flags_ = flags;
+    return true;
+  }
+
+  // The payload for a single frame may be delivered as multiple calls to
+  // OnFramePayload.
+  void OnFramePayload(const char* data, size_t len) override {
+    payload_.append(data, len);
+  }
+
+  std::vector<std::pair<SpdySettingsId, uint32_t>> settings_received_;
+  SpdyStreamId stream_id_ = 0;
+  size_t length_ = 0;
+  uint8_t type_ = 0;
+  uint8_t flags_ = 0;
+  SpdyString payload_;
+};
+
+// Exposes SpdyUnknownIR::set_length() for testing purposes.
+class TestSpdyUnknownIR : public SpdyUnknownIR {
+ public:
+  using SpdyUnknownIR::set_length;
+  using SpdyUnknownIR::SpdyUnknownIR;
+};
+
+enum Output { USE, NOT_USE };
+
+class SpdyFramerTest : public ::testing::TestWithParam<Output> {
+ public:
+  SpdyFramerTest()
+      : output_(output_buffer, kSize),
+        framer_(SpdyFramer::ENABLE_COMPRESSION) {}
+
+ protected:
+  void SetUp() override {
+    switch (GetParam()) {
+      case USE:
+        use_output_ = true;
+        break;
+      case NOT_USE:
+        // TODO(yasong): remove this case after
+        // gfe2_reloadable_flag_write_queue_zero_copy_buffer deprecates.
+        use_output_ = false;
+        break;
+    }
+  }
+
+  void CompareFrame(const SpdyString& description,
+                    const SpdySerializedFrame& actual_frame,
+                    const unsigned char* expected,
+                    const int expected_len) {
+    const unsigned char* actual =
+        reinterpret_cast<const unsigned char*>(actual_frame.data());
+    CompareCharArraysWithHexError(description, actual, actual_frame.size(),
+                                  expected, expected_len);
+  }
+
+  bool use_output_ = false;
+  ArrayOutputBuffer output_;
+  SpdyFramer framer_;
+  Http2DecoderAdapter deframer_;
+};
+
+INSTANTIATE_TEST_CASE_P(SpdyFramerTests,
+                        SpdyFramerTest,
+                        ::testing::Values(USE, NOT_USE));
+
+// Test that we can encode and decode a SpdyHeaderBlock in serialized form.
+TEST_P(SpdyFramerTest, HeaderBlockInBuffer) {
+  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+
+  // Encode the header block into a Headers frame.
+  SpdyHeadersIR headers(/* stream_id = */ 1);
+  headers.SetHeader("alpha", "beta");
+  headers.SetHeader("gamma", "charlie");
+  headers.SetHeader("cookie", "key1=value1; key2=value2");
+  SpdySerializedFrame frame(
+      SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_));
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()),
+                           frame.size());
+
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+  EXPECT_EQ(headers.header_block(), visitor.headers_);
+}
+
+// Test that if there's not a full frame, we fail to parse it.
+TEST_P(SpdyFramerTest, UndersizedHeaderBlockInBuffer) {
+  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+
+  // Encode the header block into a Headers frame.
+  SpdyHeadersIR headers(/* stream_id = */ 1);
+  headers.SetHeader("alpha", "beta");
+  headers.SetHeader("gamma", "charlie");
+  SpdySerializedFrame frame(
+      SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_));
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()),
+                           frame.size() - 2);
+
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+  EXPECT_THAT(visitor.headers_, testing::IsEmpty());
+}
+
+// Test that we can encode and decode stream dependency values in a header
+// frame.
+TEST_P(SpdyFramerTest, HeaderStreamDependencyValues) {
+  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+
+  const SpdyStreamId parent_stream_id_test_array[] = {0, 3};
+  for (SpdyStreamId parent_stream_id : parent_stream_id_test_array) {
+    const bool exclusive_test_array[] = {true, false};
+    for (bool exclusive : exclusive_test_array) {
+      SpdyHeadersIR headers(1);
+      headers.set_has_priority(true);
+      headers.set_parent_stream_id(parent_stream_id);
+      headers.set_exclusive(exclusive);
+      SpdySerializedFrame frame(
+          SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_));
+
+      TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+      visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()),
+                               frame.size());
+
+      EXPECT_TRUE(visitor.header_has_priority_);
+      EXPECT_EQ(parent_stream_id, visitor.header_parent_stream_id_);
+      EXPECT_EQ(exclusive, visitor.header_exclusive_);
+    }
+  }
+}
+
+// Test that if we receive a frame with payload length field at the
+// advertised max size, we do not set an error in ProcessInput.
+TEST_P(SpdyFramerTest, AcceptMaxFrameSizeSetting) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  // DATA frame with maximum allowed payload length.
+  unsigned char kH2FrameData[] = {
+      0x00, 0x40, 0x00,        // Length: 2^14
+      0x00,                    //   Type: HEADERS
+      0x00,                    //  Flags: None
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x00, 0x00, 0x00,  // Junk payload
+  };
+
+  SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
+                            sizeof(kH2FrameData), false);
+
+  EXPECT_CALL(visitor, OnDataFrameHeader(1, 1 << 14, false));
+  EXPECT_CALL(visitor, OnStreamFrameData(1, _, 4));
+  deframer_.ProcessInput(frame.data(), frame.size());
+  EXPECT_FALSE(deframer_.HasError());
+}
+
+// Test that if we receive a frame with payload length larger than the
+// advertised max size, we set an error of SPDY_INVALID_CONTROL_FRAME_SIZE.
+TEST_P(SpdyFramerTest, ExceedMaxFrameSizeSetting) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  // DATA frame with too large payload length.
+  unsigned char kH2FrameData[] = {
+      0x00, 0x40, 0x01,        // Length: 2^14 + 1
+      0x00,                    //   Type: HEADERS
+      0x00,                    //  Flags: None
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x00, 0x00, 0x00,  // Junk payload
+  };
+
+  SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
+                            sizeof(kH2FrameData), false);
+
+  EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD));
+  deframer_.ProcessInput(frame.data(), frame.size());
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a DATA frame with padding length larger than the
+// payload length, we set an error of SPDY_INVALID_PADDING
+TEST_P(SpdyFramerTest, OversizedDataPaddingError) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  // DATA frame with invalid padding length.
+  // |kH2FrameData| has to be |unsigned char|, because Chromium on Windows uses
+  // MSVC, where |char| is signed by default, which would not compile because of
+  // the element exceeding 127.
+  unsigned char kH2FrameData[] = {
+      0x00, 0x00, 0x05,        // Length: 5
+      0x00,                    //   Type: DATA
+      0x09,                    //  Flags: END_STREAM|PADDED
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0xff,                    // PadLen: 255 trailing bytes (Too Long)
+      0x00, 0x00, 0x00, 0x00,  // Padding
+  };
+
+  SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
+                            sizeof(kH2FrameData), false);
+
+  {
+    testing::InSequence seq;
+    EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, 1));
+    EXPECT_CALL(visitor, OnStreamPadding(1, 1));
+    EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_PADDING));
+  }
+  EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a DATA frame with padding length not larger than the
+// payload length, we do not set an error of SPDY_INVALID_PADDING
+TEST_P(SpdyFramerTest, CorrectlySizedDataPaddingNoError) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  // DATA frame with valid Padding length
+  char kH2FrameData[] = {
+      0x00, 0x00, 0x05,        // Length: 5
+      0x00,                    //   Type: DATA
+      0x08,                    //  Flags: PADDED
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x04,                    // PadLen: 4 trailing bytes
+      0x00, 0x00, 0x00, 0x00,  // Padding
+  };
+
+  SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
+
+  {
+    testing::InSequence seq;
+    EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, false));
+    EXPECT_CALL(visitor, OnStreamPadLength(1, 4));
+    EXPECT_CALL(visitor, OnError(_)).Times(0);
+    // Note that OnStreamFrameData(1, _, 1)) is never called
+    // since there is no data, only padding
+    EXPECT_CALL(visitor, OnStreamPadding(1, 4));
+  }
+
+  EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_FALSE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a HEADERS frame with padding length larger than the
+// payload length, we set an error of SPDY_INVALID_PADDING
+TEST_P(SpdyFramerTest, OversizedHeadersPaddingError) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  // HEADERS frame with invalid padding length.
+  // |kH2FrameData| has to be |unsigned char|, because Chromium on Windows uses
+  // MSVC, where |char| is signed by default, which would not compile because of
+  // the element exceeding 127.
+  unsigned char kH2FrameData[] = {
+      0x00, 0x00, 0x05,        // Length: 5
+      0x01,                    //   Type: HEADERS
+      0x08,                    //  Flags: PADDED
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0xff,                    // PadLen: 255 trailing bytes (Too Long)
+      0x00, 0x00, 0x00, 0x00,  // Padding
+  };
+
+  SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
+                            sizeof(kH2FrameData), false);
+
+  EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, false));
+  EXPECT_CALL(visitor, OnHeaderFrameStart(1)).Times(1);
+  EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_PADDING));
+  EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a HEADERS frame with padding length not larger
+// than the payload length, we do not set an error of SPDY_INVALID_PADDING
+TEST_P(SpdyFramerTest, CorrectlySizedHeadersPaddingNoError) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  // HEADERS frame with invalid Padding length
+  char kH2FrameData[] = {
+      0x00, 0x00, 0x05,        // Length: 5
+      0x01,                    //   Type: HEADERS
+      0x08,                    //  Flags: PADDED
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x04,                    // PadLen: 4 trailing bytes
+      0x00, 0x00, 0x00, 0x00,  // Padding
+  };
+
+  SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
+
+  EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, false));
+  EXPECT_CALL(visitor, OnHeaderFrameStart(1)).Times(1);
+
+  EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_FALSE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a DATA with stream ID zero, we signal an error
+// (but don't crash).
+TEST_P(SpdyFramerTest, DataWithStreamIdZero) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  const char bytes[] = "hello";
+  SpdyDataIR data_ir(/* stream_id = */ 0, bytes);
+  SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+
+  // We shouldn't have to read the whole frame before we signal an error.
+  EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+  EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a HEADERS with stream ID zero, we signal an error
+// (but don't crash).
+TEST_P(SpdyFramerTest, HeadersWithStreamIdZero) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  SpdyHeadersIR headers(/* stream_id = */ 0);
+  headers.SetHeader("alpha", "beta");
+  SpdySerializedFrame frame(
+      SpdyFramerPeer::SerializeHeaders(&framer_, headers, &output_));
+
+  // We shouldn't have to read the whole frame before we signal an error.
+  EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+  EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a PRIORITY with stream ID zero, we signal an error
+// (but don't crash).
+TEST_P(SpdyFramerTest, PriorityWithStreamIdZero) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  SpdyPriorityIR priority_ir(/* stream_id = */ 0,
+                             /* parent_stream_id = */ 1,
+                             /* weight = */ 16,
+                             /* exclusive = */ true);
+  SpdySerializedFrame frame(framer_.SerializeFrame(priority_ir));
+  if (use_output_) {
+    EXPECT_EQ(framer_.SerializeFrame(priority_ir, &output_), frame.size());
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+
+  // We shouldn't have to read the whole frame before we signal an error.
+  EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+  EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a RST_STREAM with stream ID zero, we signal an error
+// (but don't crash).
+TEST_P(SpdyFramerTest, RstStreamWithStreamIdZero) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  SpdyRstStreamIR rst_stream_ir(/* stream_id = */ 0, ERROR_CODE_PROTOCOL_ERROR);
+  SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream_ir));
+  if (use_output_) {
+    EXPECT_TRUE(framer_.SerializeRstStream(rst_stream_ir, &output_));
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+
+  // We shouldn't have to read the whole frame before we signal an error.
+  EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+  EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a SETTINGS with stream ID other than zero,
+// we signal an error (but don't crash).
+TEST_P(SpdyFramerTest, SettingsWithStreamIdNotZero) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  // Settings frame with invalid StreamID of 0x01
+  char kH2FrameData[] = {
+      0x00, 0x00, 0x06,        // Length: 6
+      0x04,                    //   Type: SETTINGS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x04,              //  Param: INITIAL_WINDOW_SIZE
+      0x0a, 0x0b, 0x0c, 0x0d,  //  Value: 168496141
+  };
+
+  SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
+
+  // We shouldn't have to read the whole frame before we signal an error.
+  EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+  EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a GOAWAY with stream ID other than zero,
+// we signal an error (but don't crash).
+TEST_P(SpdyFramerTest, GoawayWithStreamIdNotZero) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  // GOAWAY frame with invalid StreamID of 0x01
+  char kH2FrameData[] = {
+      0x00, 0x00, 0x0a,        // Length: 10
+      0x07,                    //   Type: GOAWAY
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x00, 0x00, 0x00,  //   Last: 0
+      0x00, 0x00, 0x00, 0x00,  //  Error: NO_ERROR
+      0x47, 0x41,              // Description
+  };
+
+  SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
+
+  // We shouldn't have to read the whole frame before we signal an error.
+  EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+  EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a CONTINUATION with stream ID zero, we signal
+// SPDY_INVALID_STREAM_ID.
+TEST_P(SpdyFramerTest, ContinuationWithStreamIdZero) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  SpdyContinuationIR continuation(/* stream_id = */ 0);
+  auto some_nonsense_encoding =
+      SpdyMakeUnique<SpdyString>("some nonsense encoding");
+  continuation.take_encoding(std::move(some_nonsense_encoding));
+  continuation.set_end_headers(true);
+  SpdySerializedFrame frame(framer_.SerializeContinuation(continuation));
+  if (use_output_) {
+    ASSERT_TRUE(framer_.SerializeContinuation(continuation, &output_));
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+
+  // We shouldn't have to read the whole frame before we signal an error.
+  EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+  EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a PUSH_PROMISE with stream ID zero, we signal
+// SPDY_INVALID_STREAM_ID.
+TEST_P(SpdyFramerTest, PushPromiseWithStreamIdZero) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  SpdyPushPromiseIR push_promise(/* stream_id = */ 0,
+                                 /* promised_stream_id = */ 4);
+  push_promise.SetHeader("alpha", "beta");
+  SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+      &framer_, push_promise, use_output_ ? &output_ : nullptr));
+
+  // We shouldn't have to read the whole frame before we signal an error.
+  EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+  EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a PUSH_PROMISE with promised stream ID zero, we
+// signal SPDY_INVALID_CONTROL_FRAME.
+TEST_P(SpdyFramerTest, PushPromiseWithPromisedStreamIdZero) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  SpdyPushPromiseIR push_promise(/* stream_id = */ 3,
+                                 /* promised_stream_id = */ 0);
+  push_promise.SetHeader("alpha", "beta");
+  SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+      &framer_, push_promise, use_output_ ? &output_ : nullptr));
+
+  EXPECT_CALL(visitor,
+              OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME));
+  deframer_.ProcessInput(frame.data(), frame.size());
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, MultiValueHeader) {
+  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+  SpdyString value("value1\0value2", 13);
+  // TODO(jgraettinger): If this pattern appears again, move to test class.
+  SpdyHeaderBlock header_set;
+  header_set["name"] = value;
+  SpdyString buffer;
+  HpackEncoder encoder(ObtainHpackHuffmanTable());
+  encoder.DisableCompression();
+  encoder.EncodeHeaderSet(header_set, &buffer);
+  // Frame builder with plentiful buffer size.
+  SpdyFrameBuilder frame(1024);
+  frame.BeginNewFrame(SpdyFrameType::HEADERS,
+                      HEADERS_FLAG_PRIORITY | HEADERS_FLAG_END_HEADERS, 3,
+                      buffer.size() + 5 /* priority */);
+  frame.WriteUInt32(0);   // Priority exclusivity and dependent stream.
+  frame.WriteUInt8(255);  // Priority weight.
+  frame.WriteBytes(&buffer[0], buffer.size());
+
+  SpdySerializedFrame control_frame(frame.take());
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      control_frame.size());
+
+  EXPECT_THAT(visitor.headers_, testing::ElementsAre(testing::Pair(
+                                    "name", SpdyStringPiece(value))));
+}
+
+TEST_P(SpdyFramerTest, CompressEmptyHeaders) {
+  // See https://crbug.com/172383/
+  SpdyHeadersIR headers(1);
+  headers.SetHeader("server", "SpdyServer 1.0");
+  headers.SetHeader("date", "Mon 12 Jan 2009 12:12:12 PST");
+  headers.SetHeader("status", "200");
+  headers.SetHeader("version", "HTTP/1.1");
+  headers.SetHeader("content-type", "text/html");
+  headers.SetHeader("content-length", "12");
+  headers.SetHeader("x-empty-header", "");
+
+  SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION);
+  SpdySerializedFrame frame1(
+      SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_));
+}
+
+TEST_P(SpdyFramerTest, Basic) {
+  // Send HEADERS frames with PRIORITY and END_HEADERS set.
+  // frame-format off
+  const unsigned char kH2Input[] = {
+      0x00, 0x00, 0x05,        // Length: 5
+      0x01,                    //   Type: HEADERS
+      0x24,                    //  Flags: END_HEADERS|PRIORITY
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x00, 0x00, 0x00,  // Parent: 0
+      0x82,                    // Weight: 131
+
+      0x00, 0x00, 0x01,        // Length: 1
+      0x01,                    //   Type: HEADERS
+      0x04,                    //  Flags: END_HEADERS
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x8c,                    // :status: 200
+
+      0x00, 0x00, 0x0c,        // Length: 12
+      0x00,                    //   Type: DATA
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0xde, 0xad, 0xbe, 0xef,  // Payload
+      0xde, 0xad, 0xbe, 0xef,  //
+      0xde, 0xad, 0xbe, 0xef,  //
+
+      0x00, 0x00, 0x05,        // Length: 5
+      0x01,                    //   Type: HEADERS
+      0x24,                    //  Flags: END_HEADERS|PRIORITY
+      0x00, 0x00, 0x00, 0x03,  // Stream: 3
+      0x00, 0x00, 0x00, 0x00,  // Parent: 0
+      0x82,                    // Weight: 131
+
+      0x00, 0x00, 0x08,        // Length: 8
+      0x00,                    //   Type: DATA
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x03,  // Stream: 3
+      0xde, 0xad, 0xbe, 0xef,  // Payload
+      0xde, 0xad, 0xbe, 0xef,  //
+
+      0x00, 0x00, 0x04,        // Length: 4
+      0x00,                    //   Type: DATA
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0xde, 0xad, 0xbe, 0xef,  // Payload
+
+      0x00, 0x00, 0x04,        // Length: 4
+      0x03,                    //   Type: RST_STREAM
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x00, 0x00, 0x08,  //  Error: CANCEL
+
+      0x00, 0x00, 0x00,        // Length: 0
+      0x00,                    //   Type: DATA
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x03,  // Stream: 3
+
+      0x00, 0x00, 0x04,        // Length: 4
+      0x03,                    //   Type: RST_STREAM
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x03,  // Stream: 3
+      0x00, 0x00, 0x00, 0x08,  //  Error: CANCEL
+  };
+  // frame-format on
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kH2Input, sizeof(kH2Input));
+
+  EXPECT_EQ(24, visitor.data_bytes_);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(2, visitor.fin_frame_count_);
+
+  EXPECT_EQ(3, visitor.headers_frame_count_);
+
+  EXPECT_EQ(0, visitor.fin_flag_count_);
+  EXPECT_EQ(0, visitor.end_of_stream_count_);
+  EXPECT_EQ(4, visitor.data_frame_count_);
+}
+
+// Test that the FIN flag on a data frame signifies EOF.
+TEST_P(SpdyFramerTest, FinOnDataFrame) {
+  // Send HEADERS frames with END_HEADERS set.
+  // frame-format off
+  const unsigned char kH2Input[] = {
+      0x00, 0x00, 0x05,        // Length: 5
+      0x01,                    //   Type: HEADERS
+      0x24,                    //  Flags: END_HEADERS|PRIORITY
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x00, 0x00, 0x00,  // Parent: 0
+      0x82,                    // Weight: 131
+
+      0x00, 0x00, 0x01,        // Length: 1
+      0x01,                    //   Type: HEADERS
+      0x04,                    //  Flags: END_HEADERS
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x8c,                    // :status: 200
+
+      0x00, 0x00, 0x0c,        // Length: 12
+      0x00,                    //   Type: DATA
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0xde, 0xad, 0xbe, 0xef,  // Payload
+      0xde, 0xad, 0xbe, 0xef,  //
+      0xde, 0xad, 0xbe, 0xef,  //
+
+      0x00, 0x00, 0x04,        // Length: 4
+      0x00,                    //   Type: DATA
+      0x01,                    //  Flags: END_STREAM
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0xde, 0xad, 0xbe, 0xef,  // Payload
+  };
+  // frame-format on
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kH2Input, sizeof(kH2Input));
+
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(2, visitor.headers_frame_count_);
+  EXPECT_EQ(16, visitor.data_bytes_);
+  EXPECT_EQ(0, visitor.fin_frame_count_);
+  EXPECT_EQ(0, visitor.fin_flag_count_);
+  EXPECT_EQ(1, visitor.end_of_stream_count_);
+  EXPECT_EQ(2, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, FinOnHeadersFrame) {
+  // Send HEADERS frames with END_HEADERS set.
+  // frame-format off
+  const unsigned char kH2Input[] = {
+      0x00, 0x00, 0x05,        // Length: 5
+      0x01,                    //   Type: HEADERS
+      0x24,                    //  Flags: END_HEADERS|PRIORITY
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x00, 0x00, 0x00,  // Parent: 0
+      0x82,                    // Weight: 131
+
+      0x00, 0x00, 0x01,        // Length: 1
+      0x01,                    //   Type: HEADERS
+      0x05,                    //  Flags: END_STREAM|END_HEADERS
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x8c,                    // :status: 200
+  };
+  // frame-format on
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kH2Input, sizeof(kH2Input));
+
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(2, visitor.headers_frame_count_);
+  EXPECT_EQ(0, visitor.data_bytes_);
+  EXPECT_EQ(0, visitor.fin_frame_count_);
+  EXPECT_EQ(1, visitor.fin_flag_count_);
+  EXPECT_EQ(1, visitor.end_of_stream_count_);
+  EXPECT_EQ(0, visitor.data_frame_count_);
+}
+
+// Verify we can decompress the stream even if handed over to the
+// framer 1 byte at a time.
+TEST_P(SpdyFramerTest, UnclosedStreamDataCompressorsOneByteAtATime) {
+  const char kHeader1[] = "header1";
+  const char kHeader2[] = "header2";
+  const char kValue1[] = "value1";
+  const char kValue2[] = "value2";
+
+  SpdyHeadersIR headers(/* stream_id = */ 1);
+  headers.SetHeader(kHeader1, kValue1);
+  headers.SetHeader(kHeader2, kValue2);
+  SpdySerializedFrame headers_frame(SpdyFramerPeer::SerializeHeaders(
+      &framer_, headers, use_output_ ? &output_ : nullptr));
+
+  const char bytes[] = "this is a test test test test test!";
+  SpdyDataIR data_ir(/* stream_id = */ 1,
+                     SpdyStringPiece(bytes, SPDY_ARRAYSIZE(bytes)));
+  data_ir.set_fin(true);
+  SpdySerializedFrame send_frame(framer_.SerializeData(data_ir));
+
+  // Run the inputs through the framer.
+  TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+  const unsigned char* data;
+  data = reinterpret_cast<const unsigned char*>(headers_frame.data());
+  for (size_t idx = 0; idx < headers_frame.size(); ++idx) {
+    visitor.SimulateInFramer(data + idx, 1);
+    ASSERT_EQ(0, visitor.error_count_);
+  }
+  data = reinterpret_cast<const unsigned char*>(send_frame.data());
+  for (size_t idx = 0; idx < send_frame.size(); ++idx) {
+    visitor.SimulateInFramer(data + idx, 1);
+    ASSERT_EQ(0, visitor.error_count_);
+  }
+
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(SPDY_ARRAYSIZE(bytes), static_cast<unsigned>(visitor.data_bytes_));
+  EXPECT_EQ(0, visitor.fin_frame_count_);
+  EXPECT_EQ(0, visitor.fin_flag_count_);
+  EXPECT_EQ(1, visitor.end_of_stream_count_);
+  EXPECT_EQ(1, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, WindowUpdateFrame) {
+  SpdyWindowUpdateIR window_update(/* stream_id = */ 1,
+                                   /* delta = */ 0x12345678);
+  SpdySerializedFrame frame(framer_.SerializeWindowUpdate(window_update));
+  if (use_output_) {
+    ASSERT_TRUE(framer_.SerializeWindowUpdate(window_update, &output_));
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+
+  const char kDescription[] = "WINDOW_UPDATE frame, stream 1, delta 0x12345678";
+  const unsigned char kH2FrameData[] = {
+      0x00, 0x00, 0x04,        // Length: 4
+      0x08,                    //   Type: WINDOW_UPDATE
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x12, 0x34, 0x56, 0x78,  // Increment: 305419896
+  };
+
+  CompareFrame(kDescription, frame, kH2FrameData, SPDY_ARRAYSIZE(kH2FrameData));
+}
+
+TEST_P(SpdyFramerTest, CreateDataFrame) {
+  {
+    const char kDescription[] = "'hello' data frame, no FIN";
+    // frame-format off
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x05,        // Length: 5
+        0x00,                    //   Type: DATA
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x01,  // Stream: 1
+        'h',  'e',  'l',  'l',   // Payload
+        'o',                     //
+    };
+    // frame-format on
+    const char bytes[] = "hello";
+
+    SpdyDataIR data_ir(/* stream_id = */ 1, bytes);
+    SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+
+    SpdyDataIR data_header_ir(/* stream_id = */ 1);
+    data_header_ir.SetDataShallow(bytes);
+    frame =
+        framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_header_ir);
+    CompareCharArraysWithHexError(
+        kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
+        kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize);
+  }
+
+  {
+    const char kDescription[] = "'hello' data frame with more padding, no FIN";
+    // clang-format off
+    // frame-format off
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0xfd,        // Length: 253
+        0x00,                    //   Type: DATA
+        0x08,                    //  Flags: PADDED
+        0x00, 0x00, 0x00, 0x01,  // Stream: 1
+        0xf7,                    // PadLen: 247 trailing bytes
+        'h', 'e', 'l', 'l',      // Payload
+        'o',                     //
+        // Padding of 247 0x00(s).
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    };
+    // frame-format on
+    // clang-format on
+    const char bytes[] = "hello";
+
+    SpdyDataIR data_ir(/* stream_id = */ 1, bytes);
+    // 247 zeros and the pad length field make the overall padding to be 248
+    // bytes.
+    data_ir.set_padding_len(248);
+    SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+
+    frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir);
+    CompareCharArraysWithHexError(
+        kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
+        kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize);
+  }
+
+  {
+    const char kDescription[] = "'hello' data frame with few padding, no FIN";
+    // frame-format off
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x0d,        // Length: 13
+        0x00,                    //   Type: DATA
+        0x08,                    //  Flags: PADDED
+        0x00, 0x00, 0x00, 0x01,  // Stream: 1
+        0x07,                    // PadLen: 7 trailing bytes
+        'h',  'e',  'l',  'l',   // Payload
+        'o',                     //
+        0x00, 0x00, 0x00, 0x00,  // Padding
+        0x00, 0x00, 0x00,        // Padding
+    };
+    // frame-format on
+    const char bytes[] = "hello";
+
+    SpdyDataIR data_ir(/* stream_id = */ 1, bytes);
+    // 7 zeros and the pad length field make the overall padding to be 8 bytes.
+    data_ir.set_padding_len(8);
+    SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+
+    frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir);
+    CompareCharArraysWithHexError(
+        kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
+        kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize);
+  }
+
+  {
+    const char kDescription[] =
+        "'hello' data frame with 1 byte padding, no FIN";
+    // frame-format off
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x06,        // Length: 6
+        0x00,                    //   Type: DATA
+        0x08,                    //  Flags: PADDED
+        0x00, 0x00, 0x00, 0x01,  // Stream: 1
+        0x00,                    // PadLen: 0 trailing bytes
+        'h',  'e',  'l',  'l',   // Payload
+        'o',                     //
+    };
+    // frame-format on
+    const char bytes[] = "hello";
+
+    SpdyDataIR data_ir(/* stream_id = */ 1, bytes);
+    // The pad length field itself is used for the 1-byte padding and no padding
+    // payload is needed.
+    data_ir.set_padding_len(1);
+    SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+
+    frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir);
+    CompareCharArraysWithHexError(
+        kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
+        kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize);
+  }
+
+  {
+    const char kDescription[] = "Data frame with negative data byte, no FIN";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x01,        // Length: 1
+        0x00,                    //   Type: DATA
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x01,  // Stream: 1
+        0xff,                    // Payload
+    };
+    SpdyDataIR data_ir(/* stream_id = */ 1, "\xff");
+    SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] = "'hello' data frame, with FIN";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x05,        // Length: 5
+        0x00,                    //   Type: DATA
+        0x01,                    //  Flags: END_STREAM
+        0x00, 0x00, 0x00, 0x01,  // Stream: 1
+        0x68, 0x65, 0x6c, 0x6c,  // Payload
+        0x6f,                    //
+    };
+    SpdyDataIR data_ir(/* stream_id = */ 1, "hello");
+    data_ir.set_fin(true);
+    SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] = "Empty data frame";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x00,        // Length: 0
+        0x00,                    //   Type: DATA
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x01,  // Stream: 1
+    };
+    SpdyDataIR data_ir(/* stream_id = */ 1, "");
+    SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+
+    frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir);
+    CompareCharArraysWithHexError(
+        kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
+        kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize);
+  }
+
+  {
+    const char kDescription[] = "Data frame with max stream ID";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x05,        // Length: 5
+        0x00,                    //   Type: DATA
+        0x01,                    //  Flags: END_STREAM
+        0x7f, 0xff, 0xff, 0xff,  // Stream: 0x7fffffff
+        0x68, 0x65, 0x6c, 0x6c,  // Payload
+        0x6f,                    //
+    };
+    SpdyDataIR data_ir(/* stream_id = */ 0x7fffffff, "hello");
+    data_ir.set_fin(true);
+    SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+}
+
+TEST_P(SpdyFramerTest, CreateRstStream) {
+  {
+    const char kDescription[] = "RST_STREAM frame";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x04,        // Length: 4
+        0x03,                    //   Type: RST_STREAM
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x01,  // Stream: 1
+        0x00, 0x00, 0x00, 0x01,  //  Error: PROTOCOL_ERROR
+    };
+    SpdyRstStreamIR rst_stream(/* stream_id = */ 1, ERROR_CODE_PROTOCOL_ERROR);
+    SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream));
+    if (use_output_) {
+      ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] = "RST_STREAM frame with max stream ID";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x04,        // Length: 4
+        0x03,                    //   Type: RST_STREAM
+        0x00,                    //  Flags: none
+        0x7f, 0xff, 0xff, 0xff,  // Stream: 0x7fffffff
+        0x00, 0x00, 0x00, 0x01,  //  Error: PROTOCOL_ERROR
+    };
+    SpdyRstStreamIR rst_stream(/* stream_id = */ 0x7FFFFFFF,
+                               ERROR_CODE_PROTOCOL_ERROR);
+    SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream));
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] = "RST_STREAM frame with max status code";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x04,        // Length: 4
+        0x03,                    //   Type: RST_STREAM
+        0x00,                    //  Flags: none
+        0x7f, 0xff, 0xff, 0xff,  // Stream: 0x7fffffff
+        0x00, 0x00, 0x00, 0x02,  //  Error: INTERNAL_ERROR
+    };
+    SpdyRstStreamIR rst_stream(/* stream_id = */ 0x7FFFFFFF,
+                               ERROR_CODE_INTERNAL_ERROR);
+    SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream));
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+}
+
+TEST_P(SpdyFramerTest, CreateSettings) {
+  {
+    const char kDescription[] = "Network byte order SETTINGS frame";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x06,        // Length: 6
+        0x04,                    //   Type: SETTINGS
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x00,  // Stream: 0
+        0x00, 0x04,              //  Param: INITIAL_WINDOW_SIZE
+        0x0a, 0x0b, 0x0c, 0x0d,  //  Value: 168496141
+    };
+
+    uint32_t kValue = 0x0a0b0c0d;
+    SpdySettingsIR settings_ir;
+
+    SpdyKnownSettingsId kId = SETTINGS_INITIAL_WINDOW_SIZE;
+    settings_ir.AddSetting(kId, kValue);
+
+    SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir));
+    if (use_output_) {
+      ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] = "Basic SETTINGS frame";
+    // These end up seemingly out of order because of the way that our internal
+    // ordering for settings_ir works. HTTP2 has no requirement on ordering on
+    // the wire.
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x18,        // Length: 24
+        0x04,                    //   Type: SETTINGS
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x00,  // Stream: 0
+        0x00, 0x01,              //  Param: HEADER_TABLE_SIZE
+        0x00, 0x00, 0x00, 0x05,  //  Value: 5
+        0x00, 0x02,              //  Param: ENABLE_PUSH
+        0x00, 0x00, 0x00, 0x06,  //  Value: 6
+        0x00, 0x03,              //  Param: MAX_CONCURRENT_STREAMS
+        0x00, 0x00, 0x00, 0x07,  //  Value: 7
+        0x00, 0x04,              //  Param: INITIAL_WINDOW_SIZE
+        0x00, 0x00, 0x00, 0x08,  //  Value: 8
+    };
+
+    SpdySettingsIR settings_ir;
+    settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5);
+    settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6);
+    settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7);
+    settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 8);
+    SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir));
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] = "Empty SETTINGS frame";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x00,        // Length: 0
+        0x04,                    //   Type: SETTINGS
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x00,  // Stream: 0
+    };
+    SpdySettingsIR settings_ir;
+    SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir));
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+}
+
+TEST_P(SpdyFramerTest, CreatePingFrame) {
+  {
+    const char kDescription[] = "PING frame";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x08,        // Length: 8
+        0x06,                    //   Type: PING
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x00,  // Stream: 0
+        0x12, 0x34, 0x56, 0x78,  // Opaque
+        0x9a, 0xbc, 0xde, 0xff,  //     Data
+    };
+    const unsigned char kH2FrameDataWithAck[] = {
+        0x00, 0x00, 0x08,        // Length: 8
+        0x06,                    //   Type: PING
+        0x01,                    //  Flags: ACK
+        0x00, 0x00, 0x00, 0x00,  // Stream: 0
+        0x12, 0x34, 0x56, 0x78,  // Opaque
+        0x9a, 0xbc, 0xde, 0xff,  //     Data
+    };
+    const SpdyPingId kPingId = 0x123456789abcdeffULL;
+    SpdyPingIR ping_ir(kPingId);
+    // Tests SpdyPingIR when the ping is not an ack.
+    ASSERT_FALSE(ping_ir.is_ack());
+    SpdySerializedFrame frame(framer_.SerializePing(ping_ir));
+    if (use_output_) {
+      ASSERT_TRUE(framer_.SerializePing(ping_ir, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+
+    // Tests SpdyPingIR when the ping is an ack.
+    ping_ir.set_is_ack(true);
+    frame = framer_.SerializePing(ping_ir);
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializePing(ping_ir, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    CompareFrame(kDescription, frame, kH2FrameDataWithAck,
+                 SPDY_ARRAYSIZE(kH2FrameDataWithAck));
+  }
+}
+
+TEST_P(SpdyFramerTest, CreateGoAway) {
+  {
+    const char kDescription[] = "GOAWAY frame";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x0a,        // Length: 10
+        0x07,                    //   Type: GOAWAY
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x00,  // Stream: 0
+        0x00, 0x00, 0x00, 0x00,  //   Last: 0
+        0x00, 0x00, 0x00, 0x00,  //  Error: NO_ERROR
+        0x47, 0x41,              // Description
+    };
+    SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 0, ERROR_CODE_NO_ERROR,
+                           "GA");
+    SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir));
+    if (use_output_) {
+      ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] = "GOAWAY frame with max stream ID, status";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x0a,        // Length: 10
+        0x07,                    //   Type: GOAWAY
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x00,  // Stream: 0
+        0x7f, 0xff, 0xff, 0xff,  //   Last: 0x7fffffff
+        0x00, 0x00, 0x00, 0x02,  //  Error: INTERNAL_ERROR
+        0x47, 0x41,              // Description
+    };
+    SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 0x7FFFFFFF,
+                           ERROR_CODE_INTERNAL_ERROR, "GA");
+    SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir));
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+}
+
+TEST_P(SpdyFramerTest, CreateHeadersUncompressed) {
+  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+
+  {
+    const char kDescription[] = "HEADERS frame, no FIN";
+    // frame-format off
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x12,        // Length: 18
+        0x01,                    //   Type: HEADERS
+        0x04,                    //  Flags: END_HEADERS
+        0x00, 0x00, 0x00, 0x01,  // Stream: 1
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x62, 0x61, 0x72,  // bar
+        0x03,              // Value Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+        0x03,              // Value Len: 3
+        0x62, 0x61, 0x72,  // bar
+    };
+    // frame-format on
+    SpdyHeadersIR headers(/* stream_id = */ 1);
+    headers.SetHeader("bar", "foo");
+    headers.SetHeader("foo", "bar");
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+        &framer, headers, use_output_ ? &output_ : nullptr));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] =
+        "HEADERS frame with a 0-length header name, FIN, max stream ID";
+    // frame-format off
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x0f,        // Length: 15
+        0x01,                    //   Type: HEADERS
+        0x05,                    //  Flags: END_STREAM|END_HEADERS
+        0x7f, 0xff, 0xff, 0xff,  // Stream: 2147483647
+
+        0x00,              // Unindexed Entry
+        0x00,              // Name Len: 0
+        0x03,              // Value Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+        0x03,              // Value Len: 3
+        0x62, 0x61, 0x72,  // bar
+    };
+    // frame-format on
+    SpdyHeadersIR headers(/* stream_id = */ 0x7fffffff);
+    headers.set_fin(true);
+    headers.SetHeader("", "foo");
+    headers.SetHeader("foo", "bar");
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+        &framer, headers, use_output_ ? &output_ : nullptr));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] =
+        "HEADERS frame with a 0-length header val, FIN, max stream ID";
+    // frame-format off
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x0f,        // Length: 15
+        0x01,                    //   Type: HEADERS
+        0x05,                    //  Flags: END_STREAM|END_HEADERS
+        0x7f, 0xff, 0xff, 0xff,  // Stream: 2147483647
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x62, 0x61, 0x72,  // bar
+        0x03,              // Value Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+        0x00,              // Value Len: 0
+    };
+    // frame-format on
+    SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff);
+    headers_ir.set_fin(true);
+    headers_ir.SetHeader("bar", "foo");
+    headers_ir.SetHeader("foo", "");
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+        &framer, headers_ir, use_output_ ? &output_ : nullptr));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] =
+        "HEADERS frame with a 0-length header val, FIN, max stream ID, pri";
+
+    // frame-format off
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x14,        // Length: 20
+        0x01,                    //   Type: HEADERS
+        0x25,                    //  Flags: END_STREAM|END_HEADERS|PRIORITY
+        0x7f, 0xff, 0xff, 0xff,  // Stream: 2147483647
+        0x00, 0x00, 0x00, 0x00,  // Parent: 0
+        0xdb,                    // Weight: 220
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x62, 0x61, 0x72,  // bar
+        0x03,              // Value Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+        0x00,              // Value Len: 0
+    };
+    // frame-format on
+    SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff);
+    headers_ir.set_fin(true);
+    headers_ir.set_has_priority(true);
+    headers_ir.set_weight(220);
+    headers_ir.SetHeader("bar", "foo");
+    headers_ir.SetHeader("foo", "");
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+        &framer, headers_ir, use_output_ ? &output_ : nullptr));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] =
+        "HEADERS frame with a 0-length header val, FIN, max stream ID, pri, "
+        "exclusive=true, parent_stream=0";
+
+    // frame-format off
+    const unsigned char kV4FrameData[] = {
+        0x00, 0x00, 0x14,        // Length: 20
+        0x01,                    //   Type: HEADERS
+        0x25,                    //  Flags: END_STREAM|END_HEADERS|PRIORITY
+        0x7f, 0xff, 0xff, 0xff,  // Stream: 2147483647
+        0x80, 0x00, 0x00, 0x00,  // Parent: 0 (Exclusive)
+        0xdb,                    // Weight: 220
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x62, 0x61, 0x72,  // bar
+        0x03,              // Value Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+        0x00,              // Value Len: 0
+    };
+    // frame-format on
+    SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff);
+    headers_ir.set_fin(true);
+    headers_ir.set_has_priority(true);
+    headers_ir.set_weight(220);
+    headers_ir.set_exclusive(true);
+    headers_ir.set_parent_stream_id(0);
+    headers_ir.SetHeader("bar", "foo");
+    headers_ir.SetHeader("foo", "");
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+        &framer, headers_ir, use_output_ ? &output_ : nullptr));
+    CompareFrame(kDescription, frame, kV4FrameData,
+                 SPDY_ARRAYSIZE(kV4FrameData));
+  }
+
+  {
+    const char kDescription[] =
+        "HEADERS frame with a 0-length header val, FIN, max stream ID, pri, "
+        "exclusive=false, parent_stream=max stream ID";
+
+    // frame-format off
+    const unsigned char kV4FrameData[] = {
+        0x00, 0x00, 0x14,        // Length: 20
+        0x01,                    //   Type: HEADERS
+        0x25,                    //  Flags: END_STREAM|END_HEADERS|PRIORITY
+        0x7f, 0xff, 0xff, 0xff,  // Stream: 2147483647
+        0x7f, 0xff, 0xff, 0xff,  // Parent: 2147483647
+        0xdb,                    // Weight: 220
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x62, 0x61, 0x72,  // bar
+        0x03,              // Value Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+        0x00,              // Value Len: 0
+    };
+    // frame-format on
+    SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff);
+    headers_ir.set_fin(true);
+    headers_ir.set_has_priority(true);
+    headers_ir.set_weight(220);
+    headers_ir.set_exclusive(false);
+    headers_ir.set_parent_stream_id(0x7fffffff);
+    headers_ir.SetHeader("bar", "foo");
+    headers_ir.SetHeader("foo", "");
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+        &framer, headers_ir, use_output_ ? &output_ : nullptr));
+    CompareFrame(kDescription, frame, kV4FrameData,
+                 SPDY_ARRAYSIZE(kV4FrameData));
+  }
+
+  {
+    const char kDescription[] =
+        "HEADERS frame with a 0-length header name, FIN, max stream ID, padded";
+
+    // frame-format off
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x15,        // Length: 21
+        0x01,                    //   Type: HEADERS
+        0x0d,                    //  Flags: END_STREAM|END_HEADERS|PADDED
+        0x7f, 0xff, 0xff, 0xff,  // Stream: 2147483647
+        0x05,                    // PadLen: 5 trailing bytes
+
+        0x00,              // Unindexed Entry
+        0x00,              // Name Len: 0
+        0x03,              // Value Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+        0x03,              // Value Len: 3
+        0x62, 0x61, 0x72,  // bar
+
+        0x00, 0x00, 0x00, 0x00,  // Padding
+        0x00,                    // Padding
+    };
+    // frame-format on
+    SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff);
+    headers_ir.set_fin(true);
+    headers_ir.SetHeader("", "foo");
+    headers_ir.SetHeader("foo", "bar");
+    headers_ir.set_padding_len(6);
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+        &framer, headers_ir, use_output_ ? &output_ : nullptr));
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+}
+
+TEST_P(SpdyFramerTest, CreateWindowUpdate) {
+  {
+    const char kDescription[] = "WINDOW_UPDATE frame";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x04,        // Length: 4
+        0x08,                    //   Type: WINDOW_UPDATE
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x01,  // Stream: 1
+        0x00, 0x00, 0x00, 0x01,  // Increment: 1
+    };
+    SpdySerializedFrame frame(framer_.SerializeWindowUpdate(
+        SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 1)));
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializeWindowUpdate(
+          SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 1), &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] = "WINDOW_UPDATE frame with max stream ID";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x04,        // Length: 4
+        0x08,                    //   Type: WINDOW_UPDATE
+        0x00,                    //  Flags: none
+        0x7f, 0xff, 0xff, 0xff,  // Stream: 0x7fffffff
+        0x00, 0x00, 0x00, 0x01,  // Increment: 1
+    };
+    SpdySerializedFrame frame(framer_.SerializeWindowUpdate(
+        SpdyWindowUpdateIR(/* stream_id = */ 0x7FFFFFFF, /* delta = */ 1)));
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializeWindowUpdate(
+          SpdyWindowUpdateIR(/* stream_id = */ 0x7FFFFFFF, /* delta = */ 1),
+          &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+
+  {
+    const char kDescription[] = "WINDOW_UPDATE frame with max window delta";
+    const unsigned char kH2FrameData[] = {
+        0x00, 0x00, 0x04,        // Length: 4
+        0x08,                    //   Type: WINDOW_UPDATE
+        0x00,                    //  Flags: none
+        0x00, 0x00, 0x00, 0x01,  // Stream: 1
+        0x7f, 0xff, 0xff, 0xff,  // Increment: 0x7fffffff
+    };
+    SpdySerializedFrame frame(framer_.SerializeWindowUpdate(
+        SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 0x7FFFFFFF)));
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializeWindowUpdate(
+          SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 0x7FFFFFFF),
+          &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    CompareFrame(kDescription, frame, kH2FrameData,
+                 SPDY_ARRAYSIZE(kH2FrameData));
+  }
+}
+
+TEST_P(SpdyFramerTest, CreatePushPromiseUncompressed) {
+  {
+    // Test framing PUSH_PROMISE without padding.
+    SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+    const char kDescription[] = "PUSH_PROMISE frame without padding";
+
+    // frame-format off
+    const unsigned char kFrameData[] = {
+        0x00, 0x00, 0x16,        // Length: 22
+        0x05,                    //   Type: PUSH_PROMISE
+        0x04,                    //  Flags: END_HEADERS
+        0x00, 0x00, 0x00, 0x29,  // Stream: 41
+        0x00, 0x00, 0x00, 0x3a,  // Promise: 58
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x62, 0x61, 0x72,  // bar
+        0x03,              // Value Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+        0x03,              // Value Len: 3
+        0x62, 0x61, 0x72,  // bar
+    };
+    // frame-format on
+
+    SpdyPushPromiseIR push_promise(/* stream_id = */ 41,
+                                   /* promised_stream_id = */ 58);
+    push_promise.SetHeader("bar", "foo");
+    push_promise.SetHeader("foo", "bar");
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+        &framer, push_promise, use_output_ ? &output_ : nullptr));
+    CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  }
+
+  {
+    // Test framing PUSH_PROMISE with one byte of padding.
+    SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+    const char kDescription[] = "PUSH_PROMISE frame with one byte of padding";
+
+    // frame-format off
+    const unsigned char kFrameData[] = {
+        0x00, 0x00, 0x17,        // Length: 23
+        0x05,                    //   Type: PUSH_PROMISE
+        0x0c,                    //  Flags: END_HEADERS|PADDED
+        0x00, 0x00, 0x00, 0x29,  // Stream: 41
+        0x00,                    // PadLen: 0 trailing bytes
+        0x00, 0x00, 0x00, 0x3a,  // Promise: 58
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x62, 0x61, 0x72,  // bar
+        0x03,              // Value Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+
+        0x00,              // Unindexed Entry
+        0x03,              // Name Len: 3
+        0x66, 0x6f, 0x6f,  // foo
+        0x03,              // Value Len: 3
+        0x62, 0x61, 0x72,  // bar
+    };
+    // frame-format on
+
+    SpdyPushPromiseIR push_promise(/* stream_id = */ 41,
+                                   /* promised_stream_id = */ 58);
+    push_promise.set_padding_len(1);
+    push_promise.SetHeader("bar", "foo");
+    push_promise.SetHeader("foo", "bar");
+    output_.Reset();
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+        &framer, push_promise, use_output_ ? &output_ : nullptr));
+
+    CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  }
+
+  {
+    // Test framing PUSH_PROMISE with 177 bytes of padding.
+    SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+    const char kDescription[] = "PUSH_PROMISE frame with 177 bytes of padding";
+
+    // frame-format off
+    // clang-format off
+    const unsigned char kFrameData[] = {
+        0x00, 0x00, 0xc7,        // Length: 199
+        0x05,                    //   Type: PUSH_PROMISE
+        0x0c,                    //  Flags: END_HEADERS|PADDED
+        0x00, 0x00, 0x00, 0x2a,  // Stream: 42
+        0xb0,                    // PadLen: 176 trailing bytes
+        0x00, 0x00, 0x00, 0x39,  // Promise: 57
+
+        0x00,                    // Unindexed Entry
+        0x03,                    // Name Len: 3
+        0x62, 0x61, 0x72,        // bar
+        0x03,                    // Value Len: 3
+        0x66, 0x6f, 0x6f,        // foo
+
+        0x00,                    // Unindexed Entry
+        0x03,                    // Name Len: 3
+        0x66, 0x6f, 0x6f,        // foo
+        0x03,                    // Value Len: 3
+        0x62, 0x61, 0x72,        // bar
+
+      // Padding of 176 0x00(s).
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+      0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, 0x00,  0x00,  0x00,
+    };
+    // clang-format on
+    // frame-format on
+
+    SpdyPushPromiseIR push_promise(/* stream_id = */ 42,
+                                   /* promised_stream_id = */ 57);
+    push_promise.set_padding_len(177);
+    push_promise.SetHeader("bar", "foo");
+    push_promise.SetHeader("foo", "bar");
+    output_.Reset();
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+        &framer, push_promise, use_output_ ? &output_ : nullptr));
+
+    CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  }
+}
+
+// Regression test for https://crbug.com/464748.
+TEST_P(SpdyFramerTest, GetNumberRequiredContinuationFrames) {
+  EXPECT_EQ(1u, GetNumberRequiredContinuationFrames(16383 + 16374));
+  EXPECT_EQ(2u, GetNumberRequiredContinuationFrames(16383 + 16374 + 1));
+  EXPECT_EQ(2u, GetNumberRequiredContinuationFrames(16383 + 2 * 16374));
+  EXPECT_EQ(3u, GetNumberRequiredContinuationFrames(16383 + 2 * 16374 + 1));
+}
+
+TEST_P(SpdyFramerTest, CreateContinuationUncompressed) {
+  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+  const char kDescription[] = "CONTINUATION frame";
+
+  // frame-format off
+  const unsigned char kFrameData[] = {
+      0x00, 0x00, 0x12,        // Length: 18
+      0x09,                    //   Type: CONTINUATION
+      0x04,                    //  Flags: END_HEADERS
+      0x00, 0x00, 0x00, 0x2a,  // Stream: 42
+
+      0x00,              // Unindexed Entry
+      0x03,              // Name Len: 3
+      0x62, 0x61, 0x72,  // bar
+      0x03,              // Value Len: 3
+      0x66, 0x6f, 0x6f,  // foo
+
+      0x00,              // Unindexed Entry
+      0x03,              // Name Len: 3
+      0x66, 0x6f, 0x6f,  // foo
+      0x03,              // Value Len: 3
+      0x62, 0x61, 0x72,  // bar
+  };
+  // frame-format on
+
+  SpdyHeaderBlock header_block;
+  header_block["bar"] = "foo";
+  header_block["foo"] = "bar";
+  auto buffer = SpdyMakeUnique<SpdyString>();
+  HpackEncoder encoder(ObtainHpackHuffmanTable());
+  encoder.DisableCompression();
+  encoder.EncodeHeaderSet(header_block, buffer.get());
+
+  SpdyContinuationIR continuation(/* stream_id = */ 42);
+  continuation.take_encoding(std::move(buffer));
+  continuation.set_end_headers(true);
+
+  SpdySerializedFrame frame(framer.SerializeContinuation(continuation));
+  if (use_output_) {
+    ASSERT_TRUE(framer.SerializeContinuation(continuation, &output_));
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+}
+
+// Test that if we send an unexpected CONTINUATION
+// we signal an error (but don't crash).
+TEST_P(SpdyFramerTest, SendUnexpectedContinuation) {
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  // frame-format off
+  char kH2FrameData[] = {
+      0x00, 0x00, 0x12,        // Length: 18
+      0x09,                    //   Type: CONTINUATION
+      0x04,                    //  Flags: END_HEADERS
+      0x00, 0x00, 0x00, 0x2a,  // Stream: 42
+
+      0x00,              // Unindexed Entry
+      0x03,              // Name Len: 3
+      0x62, 0x61, 0x72,  // bar
+      0x03,              // Value Len: 3
+      0x66, 0x6f, 0x6f,  // foo
+
+      0x00,              // Unindexed Entry
+      0x03,              // Name Len: 3
+      0x66, 0x6f, 0x6f,  // foo
+      0x03,              // Value Len: 3
+      0x62, 0x61, 0x72,  // bar
+  };
+  // frame-format on
+
+  SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
+
+  // We shouldn't have to read the whole frame before we signal an error.
+  EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME));
+  EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+  EXPECT_TRUE(deframer_.HasError());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, CreatePushPromiseThenContinuationUncompressed) {
+  {
+    // Test framing in a case such that a PUSH_PROMISE frame, with one byte of
+    // padding, cannot hold all the data payload, which is overflowed to the
+    // consecutive CONTINUATION frame.
+    SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+    const char kDescription[] =
+        "PUSH_PROMISE and CONTINUATION frames with one byte of padding";
+
+    // frame-format off
+    const unsigned char kPartialPushPromiseFrameData[] = {
+        0x00, 0x3f, 0xf6,        // Length: 16374
+        0x05,                    //   Type: PUSH_PROMISE
+        0x08,                    //  Flags: PADDED
+        0x00, 0x00, 0x00, 0x2a,  // Stream: 42
+        0x00,                    // PadLen: 0 trailing bytes
+        0x00, 0x00, 0x00, 0x39,  // Promise: 57
+
+        0x00,                    // Unindexed Entry
+        0x03,                    // Name Len: 3
+        0x78, 0x78, 0x78,        // xxx
+        0x7f, 0x80, 0x7f,        // Value Len: 16361
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+    };
+    const unsigned char kContinuationFrameData[] = {
+        0x00, 0x00, 0x16,        // Length: 22
+        0x09,                    //   Type: CONTINUATION
+        0x04,                    //  Flags: END_HEADERS
+        0x00, 0x00, 0x00, 0x2a,  // Stream: 42
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78, 0x78, 0x78, 0x78,  // xxxx
+        0x78,                    // x
+    };
+    // frame-format on
+
+    SpdyPushPromiseIR push_promise(/* stream_id = */ 42,
+                                   /* promised_stream_id = */ 57);
+    push_promise.set_padding_len(1);
+    SpdyString big_value(kHttp2MaxControlFrameSendSize, 'x');
+    push_promise.SetHeader("xxx", big_value);
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+        &framer, push_promise, use_output_ ? &output_ : nullptr));
+
+    // The entire frame should look like below:
+    // Name                     Length in Byte
+    // ------------------------------------------- Begin of PUSH_PROMISE frame
+    // PUSH_PROMISE header      9
+    // Pad length field         1
+    // Promised stream          4
+    // Length field of key      2
+    // Content of key           3
+    // Length field of value    3
+    // Part of big_value        16361
+    // ------------------------------------------- Begin of CONTINUATION frame
+    // CONTINUATION header      9
+    // Remaining of big_value   22
+    // ------------------------------------------- End
+
+    // Length of everything listed above except big_value.
+    int len_non_data_payload = 31;
+    EXPECT_EQ(kHttp2MaxControlFrameSendSize + len_non_data_payload,
+              frame.size());
+
+    // Partially compare the PUSH_PROMISE frame against the template.
+    const unsigned char* frame_data =
+        reinterpret_cast<const unsigned char*>(frame.data());
+    CompareCharArraysWithHexError(kDescription, frame_data,
+                                  SPDY_ARRAYSIZE(kPartialPushPromiseFrameData),
+                                  kPartialPushPromiseFrameData,
+                                  SPDY_ARRAYSIZE(kPartialPushPromiseFrameData));
+
+    // Compare the CONTINUATION frame against the template.
+    frame_data += kHttp2MaxControlFrameSendSize;
+    CompareCharArraysWithHexError(
+        kDescription, frame_data, SPDY_ARRAYSIZE(kContinuationFrameData),
+        kContinuationFrameData, SPDY_ARRAYSIZE(kContinuationFrameData));
+  }
+}
+
+TEST_P(SpdyFramerTest, CreateAltSvc) {
+  const char kDescription[] = "ALTSVC frame";
+  const unsigned char kType = SerializeFrameType(SpdyFrameType::ALTSVC);
+  const unsigned char kFrameData[] = {
+      0x00, 0x00, 0x49, kType, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 'o',
+      'r',  'i',  'g',  'i',   'n',  'p',  'i',  'd',  '1',  '=',  '"',  'h',
+      'o',  's',  't',  ':',   '4',  '4',  '3',  '"',  ';',  ' ',  'm',  'a',
+      '=',  '5',  ',',  'p',   '%',  '2',  '2',  '%',  '3',  'D',  'i',  '%',
+      '3',  'A',  'd',  '=',   '"',  'h',  '_',  '\\', '\\', 'o',  '\\', '"',
+      's',  't',  ':',  '1',   '2',  '3',  '"',  ';',  ' ',  'm',  'a',  '=',
+      '4',  '2',  ';',  ' ',   'v',  '=',  '"',  '2',  '4',  '"'};
+  SpdyAltSvcIR altsvc_ir(/* stream_id = */ 3);
+  altsvc_ir.set_origin("origin");
+  altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+      "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()));
+  altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+      "p\"=i:d", "h_\\o\"st", 123, 42,
+      SpdyAltSvcWireFormat::VersionVector{24}));
+  SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir));
+  if (use_output_) {
+    EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size());
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+}
+
+TEST_P(SpdyFramerTest, CreatePriority) {
+  const char kDescription[] = "PRIORITY frame";
+  const unsigned char kFrameData[] = {
+      0x00, 0x00, 0x05,        // Length: 5
+      0x02,                    //   Type: PRIORITY
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x02,  // Stream: 2
+      0x80, 0x00, 0x00, 0x01,  // Parent: 1 (Exclusive)
+      0x10,                    // Weight: 17
+  };
+  SpdyPriorityIR priority_ir(/* stream_id = */ 2,
+                             /* parent_stream_id = */ 1,
+                             /* weight = */ 17,
+                             /* exclusive = */ true);
+  SpdySerializedFrame frame(framer_.SerializeFrame(priority_ir));
+  if (use_output_) {
+    EXPECT_EQ(framer_.SerializeFrame(priority_ir, &output_), frame.size());
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+}
+
+TEST_P(SpdyFramerTest, CreateUnknown) {
+  const char kDescription[] = "Unknown frame";
+  const uint8_t kType = 0xaf;
+  const uint8_t kFlags = 0x11;
+  const uint8_t kLength = strlen(kDescription);
+  const unsigned char kFrameData[] = {
+      0x00,   0x00, kLength,        // Length: 13
+      kType,                        //   Type: undefined
+      kFlags,                       //  Flags: arbitrary, undefined
+      0x00,   0x00, 0x00,    0x02,  // Stream: 2
+      0x55,   0x6e, 0x6b,    0x6e,  // "Unkn"
+      0x6f,   0x77, 0x6e,    0x20,  // "own "
+      0x66,   0x72, 0x61,    0x6d,  // "fram"
+      0x65,                         // "e"
+  };
+  SpdyUnknownIR unknown_ir(/* stream_id = */ 2,
+                           /* type = */ kType,
+                           /* flags = */ kFlags,
+                           /* payload = */ kDescription);
+  SpdySerializedFrame frame(framer_.SerializeFrame(unknown_ir));
+  if (use_output_) {
+    EXPECT_EQ(framer_.SerializeFrame(unknown_ir, &output_), frame.size());
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+}
+
+// Test serialization of a SpdyUnknownIR with a defined type, a length field
+// that does not match the payload size and in fact exceeds framer limits, and a
+// stream ID that effectively flips the reserved bit.
+TEST_P(SpdyFramerTest, CreateUnknownUnchecked) {
+  const char kDescription[] = "Unknown frame";
+  const uint8_t kType = 0x00;
+  const uint8_t kFlags = 0x11;
+  const uint8_t kLength = std::numeric_limits<uint8_t>::max();
+  const unsigned int kStreamId = kStreamIdMask + 42;
+  const unsigned char kFrameData[] = {
+      0x00,   0x00, kLength,        // Length: 16426
+      kType,                        //   Type: DATA, defined
+      kFlags,                       //  Flags: arbitrary, undefined
+      0x80,   0x00, 0x00,    0x29,  // Stream: 2147483689
+      0x55,   0x6e, 0x6b,    0x6e,  // "Unkn"
+      0x6f,   0x77, 0x6e,    0x20,  // "own "
+      0x66,   0x72, 0x61,    0x6d,  // "fram"
+      0x65,                         // "e"
+  };
+  TestSpdyUnknownIR unknown_ir(/* stream_id = */ kStreamId,
+                               /* type = */ kType,
+                               /* flags = */ kFlags,
+                               /* payload = */ kDescription);
+  unknown_ir.set_length(kLength);
+  SpdySerializedFrame frame(framer_.SerializeFrame(unknown_ir));
+  if (use_output_) {
+    EXPECT_EQ(framer_.SerializeFrame(unknown_ir, &output_), frame.size());
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlock) {
+  SpdyHeadersIR headers_ir(/* stream_id = */ 1);
+  headers_ir.SetHeader("alpha", "beta");
+  headers_ir.SetHeader("gamma", "delta");
+  SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders(
+      &framer_, headers_ir, use_output_ ? &output_ : nullptr));
+  TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      control_frame.size());
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(0, visitor.control_frame_header_data_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+  EXPECT_EQ(0, visitor.end_of_stream_count_);
+  EXPECT_EQ(headers_ir.header_block(), visitor.headers_);
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlockWithHalfClose) {
+  SpdyHeadersIR headers_ir(/* stream_id = */ 1);
+  headers_ir.set_fin(true);
+  headers_ir.SetHeader("alpha", "beta");
+  headers_ir.SetHeader("gamma", "delta");
+  SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders(
+      &framer_, headers_ir, use_output_ ? &output_ : nullptr));
+  TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      control_frame.size());
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(0, visitor.control_frame_header_data_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+  EXPECT_EQ(1, visitor.end_of_stream_count_);
+  EXPECT_EQ(headers_ir.header_block(), visitor.headers_);
+}
+
+TEST_P(SpdyFramerTest, TooLargeHeadersFrameUsesContinuation) {
+  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+  SpdyHeadersIR headers(/* stream_id = */ 1);
+  headers.set_padding_len(256);
+
+  // Exact payload length will change with HPACK, but this should be long
+  // enough to cause an overflow.
+  const size_t kBigValueSize = kHttp2MaxControlFrameSendSize;
+  SpdyString big_value(kBigValueSize, 'x');
+  headers.SetHeader("aa", big_value);
+  SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders(
+      &framer, headers, use_output_ ? &output_ : nullptr));
+  EXPECT_GT(control_frame.size(), kHttp2MaxControlFrameSendSize);
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      control_frame.size());
+  EXPECT_TRUE(visitor.header_buffer_valid_);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(1, visitor.continuation_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+}
+
+TEST_P(SpdyFramerTest, MultipleContinuationFramesWithIterator) {
+  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+  auto headers = SpdyMakeUnique<SpdyHeadersIR>(/* stream_id = */ 1);
+  headers->set_padding_len(256);
+
+  // Exact payload length will change with HPACK, but this should be long
+  // enough to cause an overflow.
+  const size_t kBigValueSize = kHttp2MaxControlFrameSendSize;
+  SpdyString big_valuex(kBigValueSize, 'x');
+  headers->SetHeader("aa", big_valuex);
+  SpdyString big_valuez(kBigValueSize, 'z');
+  headers->SetHeader("bb", big_valuez);
+
+  SpdyFramer::SpdyHeaderFrameIterator frame_it(&framer, std::move(headers));
+
+  EXPECT_TRUE(frame_it.HasNextFrame());
+  EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+  SpdySerializedFrame headers_frame(output_.Begin(), output_.Size(), false);
+  EXPECT_EQ(headers_frame.size(), kHttp2MaxControlFrameSendSize);
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(headers_frame.data()),
+      headers_frame.size());
+  EXPECT_TRUE(visitor.header_buffer_valid_);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(0, visitor.continuation_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+  output_.Reset();
+  EXPECT_TRUE(frame_it.HasNextFrame());
+  EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+  SpdySerializedFrame first_cont_frame(output_.Begin(), output_.Size(), false);
+  EXPECT_EQ(first_cont_frame.size(), kHttp2MaxControlFrameSendSize);
+
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(first_cont_frame.data()),
+      first_cont_frame.size());
+  EXPECT_TRUE(visitor.header_buffer_valid_);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(1, visitor.continuation_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+  output_.Reset();
+  EXPECT_TRUE(frame_it.HasNextFrame());
+  EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+  SpdySerializedFrame second_cont_frame(output_.Begin(), output_.Size(), false);
+  EXPECT_LT(second_cont_frame.size(), kHttp2MaxControlFrameSendSize);
+
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(second_cont_frame.data()),
+      second_cont_frame.size());
+  EXPECT_TRUE(visitor.header_buffer_valid_);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(2, visitor.continuation_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+  EXPECT_FALSE(frame_it.HasNextFrame());
+}
+
+TEST_P(SpdyFramerTest, PushPromiseFramesWithIterator) {
+  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+  auto push_promise =
+      SpdyMakeUnique<SpdyPushPromiseIR>(/* stream_id = */ 1,
+                                        /* promised_stream_id = */ 2);
+  push_promise->set_padding_len(256);
+
+  // Exact payload length will change with HPACK, but this should be long
+  // enough to cause an overflow.
+  const size_t kBigValueSize = kHttp2MaxControlFrameSendSize;
+  SpdyString big_valuex(kBigValueSize, 'x');
+  push_promise->SetHeader("aa", big_valuex);
+  SpdyString big_valuez(kBigValueSize, 'z');
+  push_promise->SetHeader("bb", big_valuez);
+
+  SpdyFramer::SpdyPushPromiseFrameIterator frame_it(&framer,
+                                                    std::move(push_promise));
+
+  EXPECT_TRUE(frame_it.HasNextFrame());
+  EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+  SpdySerializedFrame push_promise_frame(output_.Begin(), output_.Size(),
+                                         false);
+  EXPECT_EQ(push_promise_frame.size(), kHttp2MaxControlFrameSendSize);
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(push_promise_frame.data()),
+      push_promise_frame.size());
+  EXPECT_TRUE(visitor.header_buffer_valid_);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.push_promise_frame_count_);
+  EXPECT_EQ(0, visitor.continuation_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+  EXPECT_TRUE(frame_it.HasNextFrame());
+  output_.Reset();
+  EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+  SpdySerializedFrame first_cont_frame(output_.Begin(), output_.Size(), false);
+
+  EXPECT_EQ(first_cont_frame.size(), kHttp2MaxControlFrameSendSize);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(first_cont_frame.data()),
+      first_cont_frame.size());
+  EXPECT_TRUE(visitor.header_buffer_valid_);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.push_promise_frame_count_);
+  EXPECT_EQ(1, visitor.continuation_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+  EXPECT_TRUE(frame_it.HasNextFrame());
+  output_.Reset();
+  EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+  SpdySerializedFrame second_cont_frame(output_.Begin(), output_.Size(), false);
+  EXPECT_LT(second_cont_frame.size(), kHttp2MaxControlFrameSendSize);
+
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(second_cont_frame.data()),
+      second_cont_frame.size());
+  EXPECT_TRUE(visitor.header_buffer_valid_);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.push_promise_frame_count_);
+  EXPECT_EQ(2, visitor.continuation_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+  EXPECT_FALSE(frame_it.HasNextFrame());
+}
+
+class SpdyControlFrameIteratorTest : public ::testing::Test {
+ public:
+  SpdyControlFrameIteratorTest() : output_(output_buffer, kSize) {}
+
+  void RunTest(std::unique_ptr<SpdyFrameIR> ir) {
+    SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+    SpdySerializedFrame frame(framer.SerializeFrame(*ir));
+    std::unique_ptr<SpdyFrameSequence> it =
+        SpdyFramer::CreateIterator(&framer, std::move(ir));
+    EXPECT_TRUE(it->HasNextFrame());
+    EXPECT_EQ(it->NextFrame(&output_), frame.size());
+    EXPECT_FALSE(it->HasNextFrame());
+  }
+
+ private:
+  ArrayOutputBuffer output_;
+};
+
+TEST_F(SpdyControlFrameIteratorTest, RstStreamFrameWithIterator) {
+  auto ir = SpdyMakeUnique<SpdyRstStreamIR>(0, ERROR_CODE_PROTOCOL_ERROR);
+  RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, SettingsFrameWithIterator) {
+  auto ir = SpdyMakeUnique<SpdySettingsIR>();
+  uint32_t kValue = 0x0a0b0c0d;
+  SpdyKnownSettingsId kId = SETTINGS_INITIAL_WINDOW_SIZE;
+  ir->AddSetting(kId, kValue);
+  RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, PingFrameWithIterator) {
+  const SpdyPingId kPingId = 0x123456789abcdeffULL;
+  auto ir = SpdyMakeUnique<SpdyPingIR>(kPingId);
+  RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, GoAwayFrameWithIterator) {
+  auto ir = SpdyMakeUnique<SpdyGoAwayIR>(0, ERROR_CODE_NO_ERROR, "GA");
+  RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, WindowUpdateFrameWithIterator) {
+  auto ir = SpdyMakeUnique<SpdyWindowUpdateIR>(1, 1);
+  RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, AtlSvcFrameWithIterator) {
+  auto ir = SpdyMakeUnique<SpdyAltSvcIR>(3);
+  ir->set_origin("origin");
+  ir->add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+      "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()));
+  ir->add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+      "p\"=i:d", "h_\\o\"st", 123, 42,
+      SpdyAltSvcWireFormat::VersionVector{24}));
+  RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, PriorityFrameWithIterator) {
+  auto ir = SpdyMakeUnique<SpdyPriorityIR>(2, 1, 17, true);
+  RunTest(std::move(ir));
+}
+
+TEST_P(SpdyFramerTest, TooLargePushPromiseFrameUsesContinuation) {
+  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+  SpdyPushPromiseIR push_promise(/* stream_id = */ 1,
+                                 /* promised_stream_id = */ 2);
+  push_promise.set_padding_len(256);
+
+  // Exact payload length will change with HPACK, but this should be long
+  // enough to cause an overflow.
+  const size_t kBigValueSize = kHttp2MaxControlFrameSendSize;
+  SpdyString big_value(kBigValueSize, 'x');
+  push_promise.SetHeader("aa", big_value);
+  SpdySerializedFrame control_frame(SpdyFramerPeer::SerializePushPromise(
+      &framer, push_promise, use_output_ ? &output_ : nullptr));
+  EXPECT_GT(control_frame.size(), kHttp2MaxControlFrameSendSize);
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      control_frame.size());
+  EXPECT_TRUE(visitor.header_buffer_valid_);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.push_promise_frame_count_);
+  EXPECT_EQ(1, visitor.continuation_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+}
+
+// Check that the framer stops delivering header data chunks once the visitor
+// declares it doesn't want any more. This is important to guard against
+// "zip bomb" types of attacks.
+TEST_P(SpdyFramerTest, ControlFrameMuchTooLarge) {
+  const size_t kHeaderBufferChunks = 4;
+  const size_t kHeaderBufferSize =
+      kHttp2DefaultFramePayloadLimit / kHeaderBufferChunks;
+  const size_t kBigValueSize = kHeaderBufferSize * 2;
+  SpdyString big_value(kBigValueSize, 'x');
+  SpdyHeadersIR headers(/* stream_id = */ 1);
+  headers.set_fin(true);
+  headers.SetHeader("aa", big_value);
+  SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders(
+      &framer_, headers, use_output_ ? &output_ : nullptr));
+  TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+  visitor.set_header_buffer_size(kHeaderBufferSize);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      control_frame.size());
+  // It's up to the visitor to ignore extraneous header data; the framer
+  // won't throw an error.
+  EXPECT_GT(visitor.header_bytes_received_, visitor.header_buffer_size_);
+  EXPECT_EQ(1, visitor.end_of_stream_count_);
+}
+
+TEST_P(SpdyFramerTest, ControlFrameSizesAreValidated) {
+  // Create a GoAway frame that has a few extra bytes at the end.
+  const size_t length = 20;
+
+  // HTTP/2 GOAWAY frames are only bound by a minimal length, since they may
+  // carry opaque data. Verify that minimal length is tested.
+  ASSERT_GT(kGoawayFrameMinimumSize, kFrameHeaderSize);
+  const size_t less_than_min_length =
+      kGoawayFrameMinimumSize - kFrameHeaderSize - 1;
+  ASSERT_LE(less_than_min_length, std::numeric_limits<unsigned char>::max());
+  const unsigned char kH2Len = static_cast<unsigned char>(less_than_min_length);
+  const unsigned char kH2FrameData[] = {
+      0x00, 0x00, kH2Len,        // Length: min length - 1
+      0x07,                      //   Type: GOAWAY
+      0x00,                      //  Flags: none
+      0x00, 0x00, 0x00,   0x00,  // Stream: 0
+      0x00, 0x00, 0x00,   0x00,  //   Last: 0
+      0x00, 0x00, 0x00,          // Truncated Status Field
+  };
+  const size_t pad_length = length + kFrameHeaderSize - sizeof(kH2FrameData);
+  SpdyString pad(pad_length, 'A');
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+
+  visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData));
+  visitor.SimulateInFramer(reinterpret_cast<const unsigned char*>(pad.c_str()),
+                           pad.length());
+
+  EXPECT_EQ(1, visitor.error_count_);  // This generated an error.
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+  EXPECT_EQ(0, visitor.goaway_count_);  // Frame not parsed.
+}
+
+TEST_P(SpdyFramerTest, ReadZeroLenSettingsFrame) {
+  SpdySettingsIR settings_ir;
+  SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir));
+  if (use_output_) {
+    ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+    control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  SetFrameLength(&control_frame, 0);
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()), kFrameHeaderSize);
+  // Zero-len settings frames are permitted as of HTTP/2.
+  EXPECT_EQ(0, visitor.error_count_);
+}
+
+// Tests handling of SETTINGS frames with invalid length.
+TEST_P(SpdyFramerTest, ReadBogusLenSettingsFrame) {
+  SpdySettingsIR settings_ir;
+
+  // Add settings to more than fill the frame so that we don't get a buffer
+  // overflow when calling SimulateInFramer() below. These settings must be
+  // distinct parameters because SpdySettingsIR has a map for settings, and
+  // will collapse multiple copies of the same parameter.
+  settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 0x00000002);
+  settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 0x00000002);
+  SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir));
+  if (use_output_) {
+    ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+    control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  const size_t kNewLength = 8;
+  SetFrameLength(&control_frame, kNewLength);
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      kFrameHeaderSize + kNewLength);
+  // Should generate an error, since its not possible to have a
+  // settings frame of length kNewLength.
+  EXPECT_EQ(1, visitor.error_count_);
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+}
+
+// Tests handling of larger SETTINGS frames.
+TEST_P(SpdyFramerTest, ReadLargeSettingsFrame) {
+  SpdySettingsIR settings_ir;
+  settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5);
+  settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6);
+  settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7);
+
+  SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir));
+  if (use_output_) {
+    ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+    control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+
+  // Read all at once.
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      control_frame.size());
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(3, visitor.setting_count_);
+  EXPECT_EQ(1, visitor.settings_ack_sent_);
+
+  // Read data in small chunks.
+  size_t framed_data = 0;
+  size_t unframed_data = control_frame.size();
+  size_t kReadChunkSize = 5;  // Read five bytes at a time.
+  while (unframed_data > 0) {
+    size_t to_read = std::min(kReadChunkSize, unframed_data);
+    visitor.SimulateInFramer(
+        reinterpret_cast<unsigned char*>(control_frame.data() + framed_data),
+        to_read);
+    unframed_data -= to_read;
+    framed_data += to_read;
+  }
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(3 * 2, visitor.setting_count_);
+  EXPECT_EQ(2, visitor.settings_ack_sent_);
+}
+
+// Tests handling of SETTINGS frame with duplicate entries.
+TEST_P(SpdyFramerTest, ReadDuplicateSettings) {
+  const unsigned char kH2FrameData[] = {
+      0x00, 0x00, 0x12,        // Length: 18
+      0x04,                    //   Type: SETTINGS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x00,  // Stream: 0
+      0x00, 0x01,              //  Param: HEADER_TABLE_SIZE
+      0x00, 0x00, 0x00, 0x02,  //  Value: 2
+      0x00, 0x01,              //  Param: HEADER_TABLE_SIZE
+      0x00, 0x00, 0x00, 0x03,  //  Value: 3
+      0x00, 0x03,              //  Param: MAX_CONCURRENT_STREAMS
+      0x00, 0x00, 0x00, 0x03,  //  Value: 3
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData));
+
+  // In HTTP/2, duplicate settings are allowed;
+  // each setting replaces the previous value for that setting.
+  EXPECT_EQ(3, visitor.setting_count_);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.settings_ack_sent_);
+}
+
+// Tests handling of SETTINGS frame with a setting we don't recognize.
+TEST_P(SpdyFramerTest, ReadUnknownSettingsId) {
+  const unsigned char kH2FrameData[] = {
+      0x00, 0x00, 0x06,        // Length: 6
+      0x04,                    //   Type: SETTINGS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x00,  // Stream: 0
+      0x00, 0x10,              //  Param: 16
+      0x00, 0x00, 0x00, 0x02,  //  Value: 2
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData));
+
+  // In HTTP/2, we ignore unknown settings because of extensions. However, we
+  // pass the SETTINGS to the visitor, which can decide how to handle them.
+  EXPECT_EQ(1, visitor.setting_count_);
+  EXPECT_EQ(0, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadKnownAndUnknownSettingsWithExtension) {
+  const unsigned char kH2FrameData[] = {
+      0x00, 0x00, 0x12,        // Length: 18
+      0x04,                    //   Type: SETTINGS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x00,  // Stream: 0
+      0x00, 0x10,              //  Param: 16
+      0x00, 0x00, 0x00, 0x02,  //  Value: 2
+      0x00, 0x5f,              //  Param: 95
+      0x00, 0x01, 0x00, 0x02,  //  Value: 65538
+      0x00, 0x02,              //  Param: ENABLE_PUSH
+      0x00, 0x00, 0x00, 0x01,  //  Value: 1
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  TestExtension extension;
+  visitor.set_extension_visitor(&extension);
+  visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData));
+
+  // In HTTP/2, we ignore unknown settings because of extensions. However, we
+  // pass the SETTINGS to the visitor, which can decide how to handle them.
+  EXPECT_EQ(3, visitor.setting_count_);
+  EXPECT_EQ(0, visitor.error_count_);
+
+  // The extension receives all SETTINGS, including the non-standard SETTINGS.
+  EXPECT_THAT(
+      extension.settings_received_,
+      testing::ElementsAre(testing::Pair(16, 2), testing::Pair(95, 65538),
+                           testing::Pair(2, 1)));
+}
+
+// Tests handling of SETTINGS frame with entries out of order.
+TEST_P(SpdyFramerTest, ReadOutOfOrderSettings) {
+  const unsigned char kH2FrameData[] = {
+      0x00, 0x00, 0x12,        // Length: 18
+      0x04,                    //   Type: SETTINGS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x00,  // Stream: 0
+      0x00, 0x02,              //  Param: ENABLE_PUSH
+      0x00, 0x00, 0x00, 0x02,  //  Value: 2
+      0x00, 0x01,              //  Param: HEADER_TABLE_SIZE
+      0x00, 0x00, 0x00, 0x03,  //  Value: 3
+      0x00, 0x03,              //  Param: MAX_CONCURRENT_STREAMS
+      0x00, 0x00, 0x00, 0x03,  //  Value: 3
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData));
+
+  // In HTTP/2, settings are allowed in any order.
+  EXPECT_EQ(3, visitor.setting_count_);
+  EXPECT_EQ(0, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ProcessSettingsAckFrame) {
+  const unsigned char kFrameData[] = {
+      0x00, 0x00, 0x00,        // Length: 0
+      0x04,                    //   Type: SETTINGS
+      0x01,                    //  Flags: ACK
+      0x00, 0x00, 0x00, 0x00,  // Stream: 0
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(0, visitor.setting_count_);
+  EXPECT_EQ(1, visitor.settings_ack_received_);
+}
+
+TEST_P(SpdyFramerTest, ProcessDataFrameWithPadding) {
+  const int kPaddingLen = 119;
+  const char data_payload[] = "hello";
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  SpdyDataIR data_ir(/* stream_id = */ 1, data_payload);
+  data_ir.set_padding_len(kPaddingLen);
+  SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+
+  int bytes_consumed = 0;
+
+  // Send the frame header.
+  EXPECT_CALL(visitor,
+              OnDataFrameHeader(1, kPaddingLen + strlen(data_payload), false));
+  CHECK_EQ(kDataFrameMinimumSize,
+           deframer_.ProcessInput(frame.data(), kDataFrameMinimumSize));
+  CHECK_EQ(deframer_.state(),
+           Http2DecoderAdapter::SPDY_READ_DATA_FRAME_PADDING_LENGTH);
+  CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+  bytes_consumed += kDataFrameMinimumSize;
+
+  // Send the padding length field.
+  EXPECT_CALL(visitor, OnStreamPadLength(1, kPaddingLen - 1));
+  CHECK_EQ(1u, deframer_.ProcessInput(frame.data() + bytes_consumed, 1));
+  CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME);
+  CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+  bytes_consumed += 1;
+
+  // Send the first two bytes of the data payload, i.e., "he".
+  EXPECT_CALL(visitor, OnStreamFrameData(1, _, 2));
+  CHECK_EQ(2u, deframer_.ProcessInput(frame.data() + bytes_consumed, 2));
+  CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME);
+  CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+  bytes_consumed += 2;
+
+  // Send the rest three bytes of the data payload, i.e., "llo".
+  EXPECT_CALL(visitor, OnStreamFrameData(1, _, 3));
+  CHECK_EQ(3u, deframer_.ProcessInput(frame.data() + bytes_consumed, 3));
+  CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_CONSUME_PADDING);
+  CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+  bytes_consumed += 3;
+
+  // Send the first 100 bytes of the padding payload.
+  EXPECT_CALL(visitor, OnStreamPadding(1, 100));
+  CHECK_EQ(100u, deframer_.ProcessInput(frame.data() + bytes_consumed, 100));
+  CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_CONSUME_PADDING);
+  CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+  bytes_consumed += 100;
+
+  // Send rest of the padding payload.
+  EXPECT_CALL(visitor, OnStreamPadding(1, 18));
+  CHECK_EQ(18u, deframer_.ProcessInput(frame.data() + bytes_consumed, 18));
+  CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_READY_FOR_FRAME);
+  CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+}
+
+TEST_P(SpdyFramerTest, ReadWindowUpdate) {
+  SpdySerializedFrame control_frame(framer_.SerializeWindowUpdate(
+      SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 2)));
+  if (use_output_) {
+    ASSERT_TRUE(framer_.SerializeWindowUpdate(
+        SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 2), &output_));
+    control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      control_frame.size());
+  EXPECT_EQ(1u, visitor.last_window_update_stream_);
+  EXPECT_EQ(2, visitor.last_window_update_delta_);
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedPushPromise) {
+  SpdyPushPromiseIR push_promise(/* stream_id = */ 42,
+                                 /* promised_stream_id = */ 57);
+  push_promise.SetHeader("foo", "bar");
+  push_promise.SetHeader("bar", "foofoo");
+  SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+      &framer_, push_promise, use_output_ ? &output_ : nullptr));
+  TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+  visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()),
+                           frame.size());
+  EXPECT_EQ(42u, visitor.last_push_promise_stream_);
+  EXPECT_EQ(57u, visitor.last_push_promise_promised_stream_);
+  EXPECT_EQ(push_promise.header_block(), visitor.headers_);
+}
+
+TEST_P(SpdyFramerTest, ReadHeadersWithContinuation) {
+  // frame-format off
+  const unsigned char kInput[] = {
+      0x00, 0x00, 0x14,                       // Length: 20
+      0x01,                                   //   Type: HEADERS
+      0x08,                                   //  Flags: PADDED
+      0x00, 0x00, 0x00, 0x01,                 // Stream: 1
+      0x03,                                   // PadLen: 3 trailing bytes
+      0x00,                                   // Unindexed Entry
+      0x06,                                   // Name Len: 6
+      'c',  'o',  'o',  'k',  'i', 'e',       // Name
+      0x07,                                   // Value Len: 7
+      'f',  'o',  'o',  '=',  'b', 'a', 'r',  // Value
+      0x00, 0x00, 0x00,                       // Padding
+
+      0x00, 0x00, 0x14,                            // Length: 20
+      0x09,                                        //   Type: CONTINUATION
+      0x00,                                        //  Flags: none
+      0x00, 0x00, 0x00, 0x01,                      // Stream: 1
+      0x00,                                        // Unindexed Entry
+      0x06,                                        // Name Len: 6
+      'c',  'o',  'o',  'k',  'i', 'e',            // Name
+      0x08,                                        // Value Len: 7
+      'b',  'a',  'z',  '=',  'b', 'i', 'n', 'g',  // Value
+      0x00,                                        // Unindexed Entry
+      0x06,                                        // Name Len: 6
+      'c',                                         // Name (split)
+
+      0x00, 0x00, 0x12,             // Length: 18
+      0x09,                         //   Type: CONTINUATION
+      0x04,                         //  Flags: END_HEADERS
+      0x00, 0x00, 0x00, 0x01,       // Stream: 1
+      'o',  'o',  'k',  'i',  'e',  // Name (continued)
+      0x00,                         // Value Len: 0
+      0x00,                         // Unindexed Entry
+      0x04,                         // Name Len: 4
+      'n',  'a',  'm',  'e',        // Name
+      0x05,                         // Value Len: 5
+      'v',  'a',  'l',  'u',  'e',  // Value
+  };
+  // frame-format on
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(2, visitor.continuation_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+  EXPECT_EQ(0, visitor.end_of_stream_count_);
+
+  EXPECT_THAT(
+      visitor.headers_,
+      testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "),
+                           testing::Pair("name", "value")));
+}
+
+TEST_P(SpdyFramerTest, ReadHeadersWithContinuationAndFin) {
+  // frame-format off
+  const unsigned char kInput[] = {
+      0x00, 0x00, 0x10,                       // Length: 20
+      0x01,                                   //   Type: HEADERS
+      0x01,                                   //  Flags: END_STREAM
+      0x00, 0x00, 0x00, 0x01,                 // Stream: 1
+      0x00,                                   // Unindexed Entry
+      0x06,                                   // Name Len: 6
+      'c',  'o',  'o',  'k',  'i', 'e',       // Name
+      0x07,                                   // Value Len: 7
+      'f',  'o',  'o',  '=',  'b', 'a', 'r',  // Value
+
+      0x00, 0x00, 0x14,                            // Length: 20
+      0x09,                                        //   Type: CONTINUATION
+      0x00,                                        //  Flags: none
+      0x00, 0x00, 0x00, 0x01,                      // Stream: 1
+      0x00,                                        // Unindexed Entry
+      0x06,                                        // Name Len: 6
+      'c',  'o',  'o',  'k',  'i', 'e',            // Name
+      0x08,                                        // Value Len: 7
+      'b',  'a',  'z',  '=',  'b', 'i', 'n', 'g',  // Value
+      0x00,                                        // Unindexed Entry
+      0x06,                                        // Name Len: 6
+      'c',                                         // Name (split)
+
+      0x00, 0x00, 0x12,             // Length: 18
+      0x09,                         //   Type: CONTINUATION
+      0x04,                         //  Flags: END_HEADERS
+      0x00, 0x00, 0x00, 0x01,       // Stream: 1
+      'o',  'o',  'k',  'i',  'e',  // Name (continued)
+      0x00,                         // Value Len: 0
+      0x00,                         // Unindexed Entry
+      0x04,                         // Name Len: 4
+      'n',  'a',  'm',  'e',        // Name
+      0x05,                         // Value Len: 5
+      'v',  'a',  'l',  'u',  'e',  // Value
+  };
+  // frame-format on
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(2, visitor.continuation_count_);
+  EXPECT_EQ(1, visitor.fin_flag_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+  EXPECT_EQ(1, visitor.end_of_stream_count_);
+
+  EXPECT_THAT(
+      visitor.headers_,
+      testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "),
+                           testing::Pair("name", "value")));
+}
+
+TEST_P(SpdyFramerTest, ReadPushPromiseWithContinuation) {
+  // frame-format off
+  const unsigned char kInput[] = {
+      0x00, 0x00, 0x17,                       // Length: 23
+      0x05,                                   //   Type: PUSH_PROMISE
+      0x08,                                   //  Flags: PADDED
+      0x00, 0x00, 0x00, 0x01,                 // Stream: 1
+      0x02,                                   // PadLen: 2 trailing bytes
+      0x00, 0x00, 0x00, 0x2a,                 // Promise: 42
+      0x00,                                   // Unindexed Entry
+      0x06,                                   // Name Len: 6
+      'c',  'o',  'o',  'k',  'i', 'e',       // Name
+      0x07,                                   // Value Len: 7
+      'f',  'o',  'o',  '=',  'b', 'a', 'r',  // Value
+      0x00, 0x00,                             // Padding
+
+      0x00, 0x00, 0x14,                            // Length: 20
+      0x09,                                        //   Type: CONTINUATION
+      0x00,                                        //  Flags: none
+      0x00, 0x00, 0x00, 0x01,                      // Stream: 1
+      0x00,                                        // Unindexed Entry
+      0x06,                                        // Name Len: 6
+      'c',  'o',  'o',  'k',  'i', 'e',            // Name
+      0x08,                                        // Value Len: 7
+      'b',  'a',  'z',  '=',  'b', 'i', 'n', 'g',  // Value
+      0x00,                                        // Unindexed Entry
+      0x06,                                        // Name Len: 6
+      'c',                                         // Name (split)
+
+      0x00, 0x00, 0x12,             // Length: 18
+      0x09,                         //   Type: CONTINUATION
+      0x04,                         //  Flags: END_HEADERS
+      0x00, 0x00, 0x00, 0x01,       // Stream: 1
+      'o',  'o',  'k',  'i',  'e',  // Name (continued)
+      0x00,                         // Value Len: 0
+      0x00,                         // Unindexed Entry
+      0x04,                         // Name Len: 4
+      'n',  'a',  'm',  'e',        // Name
+      0x05,                         // Value Len: 5
+      'v',  'a',  'l',  'u',  'e',  // Value
+  };
+  // frame-format on
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1u, visitor.last_push_promise_stream_);
+  EXPECT_EQ(42u, visitor.last_push_promise_promised_stream_);
+  EXPECT_EQ(2, visitor.continuation_count_);
+  EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+  EXPECT_EQ(0, visitor.end_of_stream_count_);
+
+  EXPECT_THAT(
+      visitor.headers_,
+      testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "),
+                           testing::Pair("name", "value")));
+}
+
+// Receiving an unknown frame when a continuation is expected should
+// result in a SPDY_UNEXPECTED_FRAME error
+TEST_P(SpdyFramerTest, ReceiveUnknownMidContinuation) {
+  const unsigned char kInput[] = {
+      0x00, 0x00, 0x10,        // Length: 16
+      0x01,                    //   Type: HEADERS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x06, 0x63, 0x6f,  // HPACK
+      0x6f, 0x6b, 0x69, 0x65,  //
+      0x07, 0x66, 0x6f, 0x6f,  //
+      0x3d, 0x62, 0x61, 0x72,  //
+
+      0x00, 0x00, 0x14,        // Length: 20
+      0xa9,                    //   Type: UnknownFrameType(169)
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x06, 0x63, 0x6f,  // Payload
+      0x6f, 0x6b, 0x69, 0x65,  //
+      0x08, 0x62, 0x61, 0x7a,  //
+      0x3d, 0x62, 0x69, 0x6e,  //
+      0x67, 0x00, 0x06, 0x63,  //
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  // Assume the unknown frame is allowed
+  visitor.on_unknown_frame_result_ = true;
+  deframer_.set_visitor(&visitor);
+  visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+  EXPECT_EQ(1, visitor.error_count_);
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(0, visitor.continuation_count_);
+  EXPECT_EQ(0u, visitor.header_buffer_length_);
+}
+
+// Receiving an unknown frame when a continuation is expected should
+// result in a SPDY_UNEXPECTED_FRAME error
+TEST_P(SpdyFramerTest, ReceiveUnknownMidContinuationWithExtension) {
+  const unsigned char kInput[] = {
+      0x00, 0x00, 0x10,        // Length: 16
+      0x01,                    //   Type: HEADERS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x06, 0x63, 0x6f,  // HPACK
+      0x6f, 0x6b, 0x69, 0x65,  //
+      0x07, 0x66, 0x6f, 0x6f,  //
+      0x3d, 0x62, 0x61, 0x72,  //
+
+      0x00, 0x00, 0x14,        // Length: 20
+      0xa9,                    //   Type: UnknownFrameType(169)
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x06, 0x63, 0x6f,  // Payload
+      0x6f, 0x6b, 0x69, 0x65,  //
+      0x08, 0x62, 0x61, 0x7a,  //
+      0x3d, 0x62, 0x69, 0x6e,  //
+      0x67, 0x00, 0x06, 0x63,  //
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  TestExtension extension;
+  visitor.set_extension_visitor(&extension);
+  deframer_.set_visitor(&visitor);
+  visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+  EXPECT_EQ(1, visitor.error_count_);
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(0, visitor.continuation_count_);
+  EXPECT_EQ(0u, visitor.header_buffer_length_);
+}
+
+TEST_P(SpdyFramerTest, ReceiveContinuationOnWrongStream) {
+  const unsigned char kInput[] = {
+      0x00, 0x00, 0x10,        // Length: 16
+      0x01,                    //   Type: HEADERS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x06, 0x63, 0x6f,  // HPACK
+      0x6f, 0x6b, 0x69, 0x65,  //
+      0x07, 0x66, 0x6f, 0x6f,  //
+      0x3d, 0x62, 0x61, 0x72,  //
+
+      0x00, 0x00, 0x14,        // Length: 20
+      0x09,                    //   Type: CONTINUATION
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x02,  // Stream: 2
+      0x00, 0x06, 0x63, 0x6f,  // HPACK
+      0x6f, 0x6b, 0x69, 0x65,  //
+      0x08, 0x62, 0x61, 0x7a,  //
+      0x3d, 0x62, 0x69, 0x6e,  //
+      0x67, 0x00, 0x06, 0x63,  //
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  deframer_.set_visitor(&visitor);
+  visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+  EXPECT_EQ(1, visitor.error_count_);
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(0, visitor.continuation_count_);
+  EXPECT_EQ(0u, visitor.header_buffer_length_);
+}
+
+TEST_P(SpdyFramerTest, ReadContinuationOutOfOrder) {
+  const unsigned char kInput[] = {
+      0x00, 0x00, 0x18,        // Length: 24
+      0x09,                    //   Type: CONTINUATION
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x06, 0x63, 0x6f,  // HPACK
+      0x6f, 0x6b, 0x69, 0x65,  //
+      0x07, 0x66, 0x6f, 0x6f,  //
+      0x3d, 0x62, 0x61, 0x72,  //
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  deframer_.set_visitor(&visitor);
+  visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+  EXPECT_EQ(1, visitor.error_count_);
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+  EXPECT_EQ(0, visitor.continuation_count_);
+  EXPECT_EQ(0u, visitor.header_buffer_length_);
+}
+
+TEST_P(SpdyFramerTest, ExpectContinuationReceiveData) {
+  const unsigned char kInput[] = {
+      0x00, 0x00, 0x10,        // Length: 16
+      0x01,                    //   Type: HEADERS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x06, 0x63, 0x6f,  // HPACK
+      0x6f, 0x6b, 0x69, 0x65,  //
+      0x07, 0x66, 0x6f, 0x6f,  //
+      0x3d, 0x62, 0x61, 0x72,  //
+
+      0x00, 0x00, 0x00,        // Length: 0
+      0x00,                    //   Type: DATA
+      0x01,                    //  Flags: END_STREAM
+      0x00, 0x00, 0x00, 0x04,  // Stream: 4
+
+      0xde, 0xad, 0xbe, 0xef,  // Truncated Frame Header
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  deframer_.set_visitor(&visitor);
+  visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+  EXPECT_EQ(1, visitor.error_count_);
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(0, visitor.continuation_count_);
+  EXPECT_EQ(0u, visitor.header_buffer_length_);
+  EXPECT_EQ(0, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, ExpectContinuationReceiveControlFrame) {
+  const unsigned char kInput[] = {
+      0x00, 0x00, 0x10,        // Length: 16
+      0x01,                    //   Type: HEADERS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x06, 0x63, 0x6f,  // HPACK
+      0x6f, 0x6b, 0x69, 0x65,  //
+      0x07, 0x66, 0x6f, 0x6f,  //
+      0x3d, 0x62, 0x61, 0x72,  //
+
+      0x00, 0x00, 0x10,        // Length: 16
+      0x01,                    //   Type: HEADERS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x06, 0x63, 0x6f,  // HPACK
+      0x6f, 0x6b, 0x69, 0x65,  //
+      0x07, 0x66, 0x6f, 0x6f,  //
+      0x3d, 0x62, 0x61, 0x72,  //
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  deframer_.set_visitor(&visitor);
+  visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+  EXPECT_EQ(1, visitor.error_count_);
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_EQ(0, visitor.continuation_count_);
+  EXPECT_EQ(0u, visitor.header_buffer_length_);
+  EXPECT_EQ(0, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadGarbage) {
+  unsigned char garbage_frame[256];
+  memset(garbage_frame, ~0, sizeof(garbage_frame));
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(garbage_frame, sizeof(garbage_frame));
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadUnknownExtensionFrame) {
+  // The unrecognized frame type should still have a valid length.
+  const unsigned char unknown_frame[] = {
+      0x00, 0x00, 0x08,        // Length: 8
+      0xff,                    //   Type: UnknownFrameType(255)
+      0xff,                    //  Flags: 0xff
+      0xff, 0xff, 0xff, 0xff,  // Stream: 0x7fffffff (R-bit set)
+      0xff, 0xff, 0xff, 0xff,  // Payload
+      0xff, 0xff, 0xff, 0xff,  //
+  };
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+
+  // Simulate the case where the stream id validation checks out.
+  visitor.on_unknown_frame_result_ = true;
+  visitor.SimulateInFramer(unknown_frame, SPDY_ARRAYSIZE(unknown_frame));
+  EXPECT_EQ(0, visitor.error_count_);
+
+  // Follow it up with a valid control frame to make sure we handle
+  // subsequent frames correctly.
+  SpdySettingsIR settings_ir;
+  settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 10);
+  SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir));
+  if (use_output_) {
+    ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+    control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      control_frame.size());
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.setting_count_);
+  EXPECT_EQ(1, visitor.settings_ack_sent_);
+}
+
+TEST_P(SpdyFramerTest, ReadUnknownExtensionFrameWithExtension) {
+  // The unrecognized frame type should still have a valid length.
+  const unsigned char unknown_frame[] = {
+      0x00, 0x00, 0x14,        // Length: 20
+      0xff,                    //   Type: UnknownFrameType(255)
+      0xff,                    //  Flags: 0xff
+      0xff, 0xff, 0xff, 0xff,  // Stream: 0x7fffffff (R-bit set)
+      0xff, 0xff, 0xff, 0xff,  // Payload
+      0xff, 0xff, 0xff, 0xff,  //
+      0xff, 0xff, 0xff, 0xff,  //
+      0xff, 0xff, 0xff, 0xff,  //
+      0xff, 0xff, 0xff, 0xff,  //
+  };
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  TestExtension extension;
+  visitor.set_extension_visitor(&extension);
+  visitor.SimulateInFramer(unknown_frame, SPDY_ARRAYSIZE(unknown_frame));
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(0x7fffffffu, extension.stream_id_);
+  EXPECT_EQ(20u, extension.length_);
+  EXPECT_EQ(255, extension.type_);
+  EXPECT_EQ(0xff, extension.flags_);
+  EXPECT_EQ(SpdyString(20, '\xff'), extension.payload_);
+
+  // Follow it up with a valid control frame to make sure we handle
+  // subsequent frames correctly.
+  SpdySettingsIR settings_ir;
+  settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 10);
+  SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir));
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      control_frame.size());
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.setting_count_);
+  EXPECT_EQ(1, visitor.settings_ack_sent_);
+}
+
+TEST_P(SpdyFramerTest, ReadGarbageWithValidLength) {
+  const unsigned char kFrameData[] = {
+      0x00, 0x00, 0x08,        // Length: 8
+      0xff,                    //   Type: UnknownFrameType(255)
+      0xff,                    //  Flags: 0xff
+      0xff, 0xff, 0xff, 0xff,  // Stream: 0x7fffffff (R-bit set)
+      0xff, 0xff, 0xff, 0xff,  // Payload
+      0xff, 0xff, 0xff, 0xff,  //
+  };
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadGarbageHPACKEncoding) {
+  const unsigned char kInput[] = {
+      0x00, 0x12, 0x01,        // Length: 4609
+      0x04,                    //   Type: SETTINGS
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x01, 0xef,  // Stream: 495
+      0xef, 0xff,              //  Param: 61439
+      0xff, 0xff, 0xff, 0xff,  //  Value: 4294967295
+      0xff, 0xff,              //  Param: 0xffff
+      0xff, 0xff, 0xff, 0xff,  //  Value: 4294967295
+      0xff, 0xff, 0xff, 0xff,  // Settings (Truncated)
+      0xff,                    //
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kInput, SPDY_ARRAYSIZE(kInput));
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, SizesTest) {
+  EXPECT_EQ(9u, kFrameHeaderSize);
+  EXPECT_EQ(9u, kDataFrameMinimumSize);
+  EXPECT_EQ(9u, kHeadersFrameMinimumSize);
+  EXPECT_EQ(14u, kPriorityFrameSize);
+  EXPECT_EQ(13u, kRstStreamFrameSize);
+  EXPECT_EQ(9u, kSettingsFrameMinimumSize);
+  EXPECT_EQ(13u, kPushPromiseFrameMinimumSize);
+  EXPECT_EQ(17u, kPingFrameSize);
+  EXPECT_EQ(17u, kGoawayFrameMinimumSize);
+  EXPECT_EQ(13u, kWindowUpdateFrameSize);
+  EXPECT_EQ(9u, kContinuationFrameMinimumSize);
+  EXPECT_EQ(11u, kGetAltSvcFrameMinimumSize);
+  EXPECT_EQ(9u, kFrameMinimumSize);
+
+  EXPECT_EQ(16384u, kHttp2DefaultFramePayloadLimit);
+  EXPECT_EQ(16393u, kHttp2DefaultFrameSizeLimit);
+}
+
+TEST_P(SpdyFramerTest, StateToStringTest) {
+  EXPECT_STREQ("ERROR", Http2DecoderAdapter::StateToString(
+                            Http2DecoderAdapter::SPDY_ERROR));
+  EXPECT_STREQ("FRAME_COMPLETE", Http2DecoderAdapter::StateToString(
+                                     Http2DecoderAdapter::SPDY_FRAME_COMPLETE));
+  EXPECT_STREQ("READY_FOR_FRAME",
+               Http2DecoderAdapter::StateToString(
+                   Http2DecoderAdapter::SPDY_READY_FOR_FRAME));
+  EXPECT_STREQ("READING_COMMON_HEADER",
+               Http2DecoderAdapter::StateToString(
+                   Http2DecoderAdapter::SPDY_READING_COMMON_HEADER));
+  EXPECT_STREQ("CONTROL_FRAME_PAYLOAD",
+               Http2DecoderAdapter::StateToString(
+                   Http2DecoderAdapter::SPDY_CONTROL_FRAME_PAYLOAD));
+  EXPECT_STREQ("IGNORE_REMAINING_PAYLOAD",
+               Http2DecoderAdapter::StateToString(
+                   Http2DecoderAdapter::SPDY_IGNORE_REMAINING_PAYLOAD));
+  EXPECT_STREQ("FORWARD_STREAM_FRAME",
+               Http2DecoderAdapter::StateToString(
+                   Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME));
+  EXPECT_STREQ(
+      "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK",
+      Http2DecoderAdapter::StateToString(
+          Http2DecoderAdapter::SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK));
+  EXPECT_STREQ("SPDY_CONTROL_FRAME_HEADER_BLOCK",
+               Http2DecoderAdapter::StateToString(
+                   Http2DecoderAdapter::SPDY_CONTROL_FRAME_HEADER_BLOCK));
+  EXPECT_STREQ("SPDY_SETTINGS_FRAME_PAYLOAD",
+               Http2DecoderAdapter::StateToString(
+                   Http2DecoderAdapter::SPDY_SETTINGS_FRAME_PAYLOAD));
+  EXPECT_STREQ("SPDY_ALTSVC_FRAME_PAYLOAD",
+               Http2DecoderAdapter::StateToString(
+                   Http2DecoderAdapter::SPDY_ALTSVC_FRAME_PAYLOAD));
+  EXPECT_STREQ("UNKNOWN_STATE",
+               Http2DecoderAdapter::StateToString(
+                   Http2DecoderAdapter::SPDY_ALTSVC_FRAME_PAYLOAD + 1));
+}
+
+TEST_P(SpdyFramerTest, SpdyFramerErrorToStringTest) {
+  EXPECT_STREQ("NO_ERROR", Http2DecoderAdapter::SpdyFramerErrorToString(
+                               Http2DecoderAdapter::SPDY_NO_ERROR));
+  EXPECT_STREQ("INVALID_STREAM_ID",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+  EXPECT_STREQ("INVALID_CONTROL_FRAME",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME));
+  EXPECT_STREQ("CONTROL_PAYLOAD_TOO_LARGE",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_CONTROL_PAYLOAD_TOO_LARGE));
+  EXPECT_STREQ("ZLIB_INIT_FAILURE",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_ZLIB_INIT_FAILURE));
+  EXPECT_STREQ("UNSUPPORTED_VERSION",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_UNSUPPORTED_VERSION));
+  EXPECT_STREQ("DECOMPRESS_FAILURE",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_DECOMPRESS_FAILURE));
+  EXPECT_STREQ("COMPRESS_FAILURE",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_COMPRESS_FAILURE));
+  EXPECT_STREQ("GOAWAY_FRAME_CORRUPT",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_GOAWAY_FRAME_CORRUPT));
+  EXPECT_STREQ("RST_STREAM_FRAME_CORRUPT",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_RST_STREAM_FRAME_CORRUPT));
+  EXPECT_STREQ("INVALID_PADDING",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_INVALID_PADDING));
+  EXPECT_STREQ("INVALID_DATA_FRAME_FLAGS",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_INVALID_DATA_FRAME_FLAGS));
+  EXPECT_STREQ("INVALID_CONTROL_FRAME_FLAGS",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_FLAGS));
+  EXPECT_STREQ("UNEXPECTED_FRAME",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME));
+  EXPECT_STREQ("INTERNAL_FRAMER_ERROR",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_INTERNAL_FRAMER_ERROR));
+  EXPECT_STREQ("INVALID_CONTROL_FRAME_SIZE",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE));
+  EXPECT_STREQ("OVERSIZED_PAYLOAD",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD));
+  EXPECT_STREQ("UNKNOWN_ERROR", Http2DecoderAdapter::SpdyFramerErrorToString(
+                                    Http2DecoderAdapter::LAST_ERROR));
+  EXPECT_STREQ("UNKNOWN_ERROR",
+               Http2DecoderAdapter::SpdyFramerErrorToString(
+                   static_cast<Http2DecoderAdapter::SpdyFramerError>(
+                       Http2DecoderAdapter::LAST_ERROR + 1)));
+}
+
+TEST_P(SpdyFramerTest, DataFrameFlagsV4) {
+  uint8_t valid_data_flags = DATA_FLAG_FIN | DATA_FLAG_PADDED;
+
+  uint8_t flags = 0;
+  do {
+    SCOPED_TRACE(testing::Message()
+                 << "Flags " << std::hex << static_cast<int>(flags));
+
+    testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+    deframer_.set_visitor(&visitor);
+
+    SpdyDataIR data_ir(/* stream_id = */ 1, "hello");
+    SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+    SetFrameFlags(&frame, flags);
+
+    if (flags & ~valid_data_flags) {
+      EXPECT_CALL(visitor, OnError(_));
+    } else {
+      EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, flags & DATA_FLAG_FIN));
+      if (flags & DATA_FLAG_PADDED) {
+        // The first byte of payload is parsed as padding length, but 'h'
+        // (0x68) is too large a padding length for a 5 byte payload.
+        EXPECT_CALL(visitor, OnStreamPadding(_, 1));
+        // Expect Error since the frame ends prematurely.
+        EXPECT_CALL(visitor, OnError(_));
+      } else {
+        EXPECT_CALL(visitor, OnStreamFrameData(_, _, 5));
+        if (flags & DATA_FLAG_FIN) {
+          EXPECT_CALL(visitor, OnStreamEnd(_));
+        }
+      }
+    }
+
+    deframer_.ProcessInput(frame.data(), frame.size());
+    if (flags & ~valid_data_flags) {
+      EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state());
+      EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_DATA_FRAME_FLAGS,
+                deframer_.spdy_framer_error())
+          << Http2DecoderAdapter::SpdyFramerErrorToString(
+                 deframer_.spdy_framer_error());
+    } else if (flags & DATA_FLAG_PADDED) {
+      EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state());
+      EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING,
+                deframer_.spdy_framer_error())
+          << Http2DecoderAdapter::SpdyFramerErrorToString(
+                 deframer_.spdy_framer_error());
+    } else {
+      EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+      EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR,
+                deframer_.spdy_framer_error())
+          << Http2DecoderAdapter::SpdyFramerErrorToString(
+                 deframer_.spdy_framer_error());
+    }
+    deframer_.Reset();
+  } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, RstStreamFrameFlags) {
+  uint8_t flags = 0;
+  do {
+    SCOPED_TRACE(testing::Message()
+                 << "Flags " << std::hex << static_cast<int>(flags));
+
+    testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+    deframer_.set_visitor(&visitor);
+
+    SpdyRstStreamIR rst_stream(/* stream_id = */ 13, ERROR_CODE_CANCEL);
+    SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream));
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    SetFrameFlags(&frame, flags);
+
+    EXPECT_CALL(visitor, OnRstStream(13, ERROR_CODE_CANCEL));
+
+    deframer_.ProcessInput(frame.data(), frame.size());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+        << Http2DecoderAdapter::SpdyFramerErrorToString(
+               deframer_.spdy_framer_error());
+    deframer_.Reset();
+  } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, SettingsFrameFlags) {
+  uint8_t flags = 0;
+  do {
+    SCOPED_TRACE(testing::Message()
+                 << "Flags " << std::hex << static_cast<int>(flags));
+
+    testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+    deframer_.set_visitor(&visitor);
+
+    SpdySettingsIR settings_ir;
+    settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 16);
+    SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir));
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    SetFrameFlags(&frame, flags);
+
+    if (flags & SETTINGS_FLAG_ACK) {
+      EXPECT_CALL(visitor, OnError(_));
+    } else {
+      EXPECT_CALL(visitor, OnSettings());
+      EXPECT_CALL(visitor, OnSetting(SETTINGS_INITIAL_WINDOW_SIZE, 16));
+      EXPECT_CALL(visitor, OnSettingsEnd());
+    }
+
+    deframer_.ProcessInput(frame.data(), frame.size());
+    if (flags & SETTINGS_FLAG_ACK) {
+      // The frame is invalid because ACK frames should have no payload.
+      EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state());
+      EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+                deframer_.spdy_framer_error())
+          << Http2DecoderAdapter::SpdyFramerErrorToString(
+                 deframer_.spdy_framer_error());
+    } else {
+      EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+      EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR,
+                deframer_.spdy_framer_error())
+          << Http2DecoderAdapter::SpdyFramerErrorToString(
+                 deframer_.spdy_framer_error());
+    }
+    deframer_.Reset();
+  } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, GoawayFrameFlags) {
+  uint8_t flags = 0;
+  do {
+    SCOPED_TRACE(testing::Message()
+                 << "Flags " << std::hex << static_cast<int>(flags));
+
+    testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+    deframer_.set_visitor(&visitor);
+
+    SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 97, ERROR_CODE_NO_ERROR,
+                           "test");
+    SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir));
+    if (use_output_) {
+      output_.Reset();
+      ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_));
+      frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    }
+    SetFrameFlags(&frame, flags);
+
+    EXPECT_CALL(visitor, OnGoAway(97, ERROR_CODE_NO_ERROR));
+
+    deframer_.ProcessInput(frame.data(), frame.size());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+        << Http2DecoderAdapter::SpdyFramerErrorToString(
+               deframer_.spdy_framer_error());
+    deframer_.Reset();
+  } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, HeadersFrameFlags) {
+  uint8_t flags = 0;
+  do {
+    SCOPED_TRACE(testing::Message()
+                 << "Flags " << std::hex << static_cast<int>(flags));
+
+    testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+    SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION);
+    Http2DecoderAdapter deframer;
+    deframer.set_visitor(&visitor);
+
+    SpdyHeadersIR headers_ir(/* stream_id = */ 57);
+    if (flags & HEADERS_FLAG_PRIORITY) {
+      headers_ir.set_weight(3);
+      headers_ir.set_has_priority(true);
+      headers_ir.set_parent_stream_id(5);
+      headers_ir.set_exclusive(true);
+    }
+    headers_ir.SetHeader("foo", "bar");
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+        &framer, headers_ir, use_output_ ? &output_ : nullptr));
+    uint8_t set_flags = flags & ~HEADERS_FLAG_PADDED;
+    SetFrameFlags(&frame, set_flags);
+
+    // Expected callback values
+    SpdyStreamId stream_id = 57;
+    bool has_priority = false;
+    int weight = 0;
+    SpdyStreamId parent_stream_id = 0;
+    bool exclusive = false;
+    bool fin = flags & CONTROL_FLAG_FIN;
+    bool end = flags & HEADERS_FLAG_END_HEADERS;
+    if (flags & HEADERS_FLAG_PRIORITY) {
+      has_priority = true;
+      weight = 3;
+      parent_stream_id = 5;
+      exclusive = true;
+    }
+    EXPECT_CALL(visitor, OnHeaders(stream_id, has_priority, weight,
+                                   parent_stream_id, exclusive, fin, end));
+    EXPECT_CALL(visitor, OnHeaderFrameStart(57)).Times(1);
+    if (end) {
+      EXPECT_CALL(visitor, OnHeaderFrameEnd(57)).Times(1);
+    }
+    if (flags & DATA_FLAG_FIN && end) {
+      EXPECT_CALL(visitor, OnStreamEnd(_));
+    } else {
+      // Do not close the stream if we are expecting a CONTINUATION frame.
+      EXPECT_CALL(visitor, OnStreamEnd(_)).Times(0);
+    }
+
+    deframer.ProcessInput(frame.data(), frame.size());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error())
+        << Http2DecoderAdapter::SpdyFramerErrorToString(
+               deframer.spdy_framer_error());
+    deframer.Reset();
+  } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, PingFrameFlags) {
+  uint8_t flags = 0;
+  do {
+    SCOPED_TRACE(testing::Message()
+                 << "Flags " << std::hex << static_cast<int>(flags));
+
+    testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+    deframer_.set_visitor(&visitor);
+
+    SpdySerializedFrame frame(framer_.SerializePing(SpdyPingIR(42)));
+    SetFrameFlags(&frame, flags);
+
+    EXPECT_CALL(visitor, OnPing(42, flags & PING_FLAG_ACK));
+
+    deframer_.ProcessInput(frame.data(), frame.size());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+        << Http2DecoderAdapter::SpdyFramerErrorToString(
+               deframer_.spdy_framer_error());
+    deframer_.Reset();
+  } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, WindowUpdateFrameFlags) {
+  uint8_t flags = 0;
+  do {
+    SCOPED_TRACE(testing::Message()
+                 << "Flags " << std::hex << static_cast<int>(flags));
+
+    testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+    deframer_.set_visitor(&visitor);
+
+    SpdySerializedFrame frame(framer_.SerializeWindowUpdate(
+        SpdyWindowUpdateIR(/* stream_id = */ 4, /* delta = */ 1024)));
+    SetFrameFlags(&frame, flags);
+
+    EXPECT_CALL(visitor, OnWindowUpdate(4, 1024));
+
+    deframer_.ProcessInput(frame.data(), frame.size());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+        << Http2DecoderAdapter::SpdyFramerErrorToString(
+               deframer_.spdy_framer_error());
+    deframer_.Reset();
+  } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, PushPromiseFrameFlags) {
+  const SpdyStreamId client_id = 123;   // Must be odd.
+  const SpdyStreamId promised_id = 22;  // Must be even.
+  uint8_t flags = 0;
+  do {
+    SCOPED_TRACE(testing::Message()
+                 << "Flags " << std::hex << static_cast<int>(flags));
+
+    testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+    testing::StrictMock<test::MockDebugVisitor> debug_visitor;
+    SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION);
+    Http2DecoderAdapter deframer;
+    deframer.set_visitor(&visitor);
+    deframer.set_debug_visitor(&debug_visitor);
+    framer.set_debug_visitor(&debug_visitor);
+
+    EXPECT_CALL(
+        debug_visitor,
+        OnSendCompressedFrame(client_id, SpdyFrameType::PUSH_PROMISE, _, _));
+
+    SpdyPushPromiseIR push_promise(client_id, promised_id);
+    push_promise.SetHeader("foo", "bar");
+    SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+        &framer, push_promise, use_output_ ? &output_ : nullptr));
+    // TODO(jgraettinger): Add padding to SpdyPushPromiseIR,
+    // and implement framing.
+    SetFrameFlags(&frame, flags & ~HEADERS_FLAG_PADDED);
+
+    bool end = flags & PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+    EXPECT_CALL(debug_visitor, OnReceiveCompressedFrame(
+                                   client_id, SpdyFrameType::PUSH_PROMISE, _));
+    EXPECT_CALL(visitor, OnPushPromise(client_id, promised_id, end));
+    EXPECT_CALL(visitor, OnHeaderFrameStart(client_id)).Times(1);
+    if (end) {
+      EXPECT_CALL(visitor, OnHeaderFrameEnd(client_id)).Times(1);
+    }
+
+    deframer.ProcessInput(frame.data(), frame.size());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error())
+        << Http2DecoderAdapter::SpdyFramerErrorToString(
+               deframer.spdy_framer_error());
+  } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, ContinuationFrameFlags) {
+  uint8_t flags = 0;
+  do {
+    if (use_output_) {
+      output_.Reset();
+    }
+    SCOPED_TRACE(testing::Message()
+                 << "Flags " << std::hex << static_cast<int>(flags));
+
+    testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+    testing::StrictMock<test::MockDebugVisitor> debug_visitor;
+    SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION);
+    Http2DecoderAdapter deframer;
+    deframer.set_visitor(&visitor);
+    deframer.set_debug_visitor(&debug_visitor);
+    framer.set_debug_visitor(&debug_visitor);
+
+    EXPECT_CALL(debug_visitor,
+                OnSendCompressedFrame(42, SpdyFrameType::HEADERS, _, _));
+    EXPECT_CALL(debug_visitor,
+                OnReceiveCompressedFrame(42, SpdyFrameType::HEADERS, _));
+    EXPECT_CALL(visitor, OnHeaders(42, false, 0, 0, false, false, false));
+    EXPECT_CALL(visitor, OnHeaderFrameStart(42)).Times(1);
+
+    SpdyHeadersIR headers_ir(/* stream_id = */ 42);
+    headers_ir.SetHeader("foo", "bar");
+    SpdySerializedFrame frame0;
+    if (use_output_) {
+      EXPECT_TRUE(framer.SerializeHeaders(headers_ir, &output_));
+      frame0 = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+    } else {
+      frame0 = framer.SerializeHeaders(headers_ir);
+    }
+    SetFrameFlags(&frame0, 0);
+
+    SpdyContinuationIR continuation(/* stream_id = */ 42);
+    SpdySerializedFrame frame1;
+    if (use_output_) {
+      char* begin = output_.Begin() + output_.Size();
+      ASSERT_TRUE(framer.SerializeContinuation(continuation, &output_));
+      frame1 =
+          SpdySerializedFrame(begin, output_.Size() - frame0.size(), false);
+    } else {
+      frame1 = framer.SerializeContinuation(continuation);
+    }
+    SetFrameFlags(&frame1, flags);
+
+    EXPECT_CALL(debug_visitor,
+                OnReceiveCompressedFrame(42, SpdyFrameType::CONTINUATION, _));
+    EXPECT_CALL(visitor, OnContinuation(42, flags & HEADERS_FLAG_END_HEADERS));
+    bool end = flags & HEADERS_FLAG_END_HEADERS;
+    if (end) {
+      EXPECT_CALL(visitor, OnHeaderFrameEnd(42)).Times(1);
+    }
+
+    deframer.ProcessInput(frame0.data(), frame0.size());
+    deframer.ProcessInput(frame1.data(), frame1.size());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error())
+        << Http2DecoderAdapter::SpdyFramerErrorToString(
+               deframer.spdy_framer_error());
+  } while (++flags != 0);
+}
+
+// TODO(mlavan): Add TEST_P(SpdyFramerTest, AltSvcFrameFlags)
+
+// Test handling of a RST_STREAM with out-of-bounds status codes.
+TEST_P(SpdyFramerTest, RstStreamStatusBounds) {
+  const unsigned char kH2RstStreamInvalid[] = {
+      0x00, 0x00, 0x04,        // Length: 4
+      0x03,                    //   Type: RST_STREAM
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x00, 0x00, 0x00,  //  Error: NO_ERROR
+  };
+  const unsigned char kH2RstStreamNumStatusCodes[] = {
+      0x00, 0x00, 0x04,        // Length: 4
+      0x03,                    //   Type: RST_STREAM
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x01,  // Stream: 1
+      0x00, 0x00, 0x00, 0xff,  //  Error: 255
+  };
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  EXPECT_CALL(visitor, OnRstStream(1, ERROR_CODE_NO_ERROR));
+  deframer_.ProcessInput(reinterpret_cast<const char*>(kH2RstStreamInvalid),
+                         SPDY_ARRAYSIZE(kH2RstStreamInvalid));
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+  deframer_.Reset();
+
+  EXPECT_CALL(visitor, OnRstStream(1, ERROR_CODE_INTERNAL_ERROR));
+  deframer_.ProcessInput(
+      reinterpret_cast<const char*>(kH2RstStreamNumStatusCodes),
+      SPDY_ARRAYSIZE(kH2RstStreamNumStatusCodes));
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Test handling of GOAWAY frames with out-of-bounds status code.
+TEST_P(SpdyFramerTest, GoAwayStatusBounds) {
+  const unsigned char kH2FrameData[] = {
+      0x00, 0x00, 0x0a,        // Length: 10
+      0x07,                    //   Type: GOAWAY
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x00,  // Stream: 0
+      0x00, 0x00, 0x00, 0x01,  //   Last: 1
+      0xff, 0xff, 0xff, 0xff,  //  Error: 0xffffffff
+      0x47, 0x41,              // Description
+  };
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  EXPECT_CALL(visitor, OnGoAway(1, ERROR_CODE_INTERNAL_ERROR));
+  deframer_.ProcessInput(reinterpret_cast<const char*>(kH2FrameData),
+                         SPDY_ARRAYSIZE(kH2FrameData));
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Tests handling of a GOAWAY frame with out-of-bounds stream ID.
+TEST_P(SpdyFramerTest, GoAwayStreamIdBounds) {
+  const unsigned char kH2FrameData[] = {
+      0x00, 0x00, 0x08,        // Length: 8
+      0x07,                    //   Type: GOAWAY
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x00,  // Stream: 0
+      0xff, 0xff, 0xff, 0xff,  //   Last: 0x7fffffff (R-bit set)
+      0x00, 0x00, 0x00, 0x00,  //  Error: NO_ERROR
+  };
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  EXPECT_CALL(visitor, OnGoAway(0x7fffffff, ERROR_CODE_NO_ERROR));
+  deframer_.ProcessInput(reinterpret_cast<const char*>(kH2FrameData),
+                         SPDY_ARRAYSIZE(kH2FrameData));
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, OnAltSvcWithOrigin) {
+  const SpdyStreamId kStreamId = 0;  // Stream id must be zero if origin given.
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  SpdyAltSvcWireFormat::AlternativeService altsvc1(
+      "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector());
+  SpdyAltSvcWireFormat::AlternativeService altsvc2(
+      "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24});
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+  altsvc_vector.push_back(altsvc1);
+  altsvc_vector.push_back(altsvc2);
+  EXPECT_CALL(visitor,
+              OnAltSvc(kStreamId, SpdyStringPiece("o_r|g!n"), altsvc_vector));
+
+  SpdyAltSvcIR altsvc_ir(kStreamId);
+  altsvc_ir.set_origin("o_r|g!n");
+  altsvc_ir.add_altsvc(altsvc1);
+  altsvc_ir.add_altsvc(altsvc2);
+  SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir));
+  if (use_output_) {
+    output_.Reset();
+    EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size());
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  deframer_.ProcessInput(frame.data(), frame.size());
+
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, OnAltSvcNoOrigin) {
+  const SpdyStreamId kStreamId = 1;
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  SpdyAltSvcWireFormat::AlternativeService altsvc1(
+      "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector());
+  SpdyAltSvcWireFormat::AlternativeService altsvc2(
+      "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24});
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+  altsvc_vector.push_back(altsvc1);
+  altsvc_vector.push_back(altsvc2);
+  EXPECT_CALL(visitor, OnAltSvc(kStreamId, SpdyStringPiece(""), altsvc_vector));
+
+  SpdyAltSvcIR altsvc_ir(kStreamId);
+  altsvc_ir.add_altsvc(altsvc1);
+  altsvc_ir.add_altsvc(altsvc2);
+  SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir));
+  deframer_.ProcessInput(frame.data(), frame.size());
+
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, OnAltSvcEmptyProtocolId) {
+  const SpdyStreamId kStreamId = 0;  // Stream id must be zero if origin given.
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+  deframer_.set_visitor(&visitor);
+
+  EXPECT_CALL(visitor,
+              OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME));
+
+  SpdyAltSvcIR altsvc_ir(kStreamId);
+  altsvc_ir.set_origin("o1");
+  altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+      "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()));
+  altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+      "", "h1", 443, 10, SpdyAltSvcWireFormat::VersionVector()));
+  SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir));
+  if (use_output_) {
+    output_.Reset();
+    EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size());
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  deframer_.ProcessInput(frame.data(), frame.size());
+
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, OnAltSvcBadLengths) {
+  const unsigned char kType = SerializeFrameType(SpdyFrameType::ALTSVC);
+  const unsigned char kFrameDataOriginLenLargerThanFrame[] = {
+      0x00, 0x00, 0x05, kType, 0x00, 0x00, 0x00,
+      0x00, 0x03, 0x42, 0x42,  'f',  'o',  'o',
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+
+  deframer_.set_visitor(&visitor);
+  visitor.SimulateInFramer(kFrameDataOriginLenLargerThanFrame,
+                           sizeof(kFrameDataOriginLenLargerThanFrame));
+
+  EXPECT_EQ(1, visitor.error_count_);
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME,
+            visitor.deframer_.spdy_framer_error());
+}
+
+// Tests handling of ALTSVC frames delivered in small chunks.
+TEST_P(SpdyFramerTest, ReadChunkedAltSvcFrame) {
+  SpdyAltSvcIR altsvc_ir(/* stream_id = */ 1);
+  SpdyAltSvcWireFormat::AlternativeService altsvc1(
+      "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector());
+  SpdyAltSvcWireFormat::AlternativeService altsvc2(
+      "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24});
+  altsvc_ir.add_altsvc(altsvc1);
+  altsvc_ir.add_altsvc(altsvc2);
+
+  SpdySerializedFrame control_frame(framer_.SerializeAltSvc(altsvc_ir));
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+
+  // Read data in small chunks.
+  size_t framed_data = 0;
+  size_t unframed_data = control_frame.size();
+  size_t kReadChunkSize = 5;  // Read five bytes at a time.
+  while (unframed_data > 0) {
+    size_t to_read = std::min(kReadChunkSize, unframed_data);
+    visitor.SimulateInFramer(
+        reinterpret_cast<unsigned char*>(control_frame.data() + framed_data),
+        to_read);
+    unframed_data -= to_read;
+    framed_data += to_read;
+  }
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(1, visitor.altsvc_count_);
+  ASSERT_NE(nullptr, visitor.test_altsvc_ir_);
+  ASSERT_EQ(2u, visitor.test_altsvc_ir_->altsvc_vector().size());
+  EXPECT_TRUE(visitor.test_altsvc_ir_->altsvc_vector()[0] == altsvc1);
+  EXPECT_TRUE(visitor.test_altsvc_ir_->altsvc_vector()[1] == altsvc2);
+}
+
+// While RFC7838 Section 4 says that an ALTSVC frame on stream 0 with empty
+// origin MUST be ignored, it is not implemented at the framer level: instead,
+// such frames are passed on to the consumer.
+TEST_P(SpdyFramerTest, ReadAltSvcFrame) {
+  constexpr struct {
+    uint32_t stream_id;
+    const char* origin;
+  } test_cases[] = {{0, ""},
+                    {1, ""},
+                    {0, "https://www.example.com"},
+                    {1, "https://www.example.com"}};
+  for (const auto& test_case : test_cases) {
+    SpdyAltSvcIR altsvc_ir(test_case.stream_id);
+    SpdyAltSvcWireFormat::AlternativeService altsvc(
+        "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector());
+    altsvc_ir.add_altsvc(altsvc);
+    altsvc_ir.set_origin(test_case.origin);
+    SpdySerializedFrame frame(framer_.SerializeAltSvc(altsvc_ir));
+
+    TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+    deframer_.set_visitor(&visitor);
+    deframer_.ProcessInput(frame.data(), frame.size());
+
+    EXPECT_EQ(0, visitor.error_count_);
+    EXPECT_EQ(1, visitor.altsvc_count_);
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+        << Http2DecoderAdapter::SpdyFramerErrorToString(
+               deframer_.spdy_framer_error());
+  }
+}
+
+// An ALTSVC frame with invalid Alt-Svc-Field-Value results in an error.
+TEST_P(SpdyFramerTest, ErrorOnAltSvcFrameWithInvalidValue) {
+  // Alt-Svc-Field-Value must be "clear" or must contain an "=" character
+  // per RFC7838 Section 3.
+  const char kFrameData[] = {
+      0x00, 0x00, 0x16,        //     Length: 22
+      0x0a,                    //       Type: ALTSVC
+      0x00,                    //      Flags: none
+      0x00, 0x00, 0x00, 0x01,  //     Stream: 1
+      0x00, 0x00,              // Origin-Len: 0
+      0x74, 0x68, 0x69, 0x73,  // thisisnotavalidvalue
+      0x69, 0x73, 0x6e, 0x6f, 0x74, 0x61, 0x76, 0x61,
+      0x6c, 0x69, 0x64, 0x76, 0x61, 0x6c, 0x75, 0x65,
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+  deframer_.set_visitor(&visitor);
+  deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+
+  EXPECT_EQ(1, visitor.error_count_);
+  EXPECT_EQ(0, visitor.altsvc_count_);
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME,
+            deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Tests handling of PRIORITY frames.
+TEST_P(SpdyFramerTest, ReadPriority) {
+  SpdyPriorityIR priority(/* stream_id = */ 3,
+                          /* parent_stream_id = */ 1,
+                          /* weight = */ 256,
+                          /* exclusive = */ false);
+  SpdySerializedFrame frame(framer_.SerializePriority(priority));
+  if (use_output_) {
+    output_.Reset();
+    ASSERT_TRUE(framer_.SerializePriority(priority, &output_));
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+  EXPECT_CALL(visitor, OnPriority(3, 1, 256, false));
+  deframer_.ProcessInput(frame.data(), frame.size());
+
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             deframer_.spdy_framer_error());
+}
+
+// Tests handling of PRIORITY frame with incorrect size.
+TEST_P(SpdyFramerTest, ReadIncorrectlySizedPriority) {
+  // PRIORITY frame of size 4, which isn't correct.
+  const unsigned char kFrameData[] = {
+      0x00, 0x00, 0x04,        // Length: 4
+      0x02,                    //   Type: PRIORITY
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x03,  // Stream: 3
+      0x00, 0x00, 0x00, 0x01,  // Priority (Truncated)
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+}
+
+// Tests handling of PING frame with incorrect size.
+TEST_P(SpdyFramerTest, ReadIncorrectlySizedPing) {
+  // PING frame of size 4, which isn't correct.
+  const unsigned char kFrameData[] = {
+      0x00, 0x00, 0x04,        // Length: 4
+      0x06,                    //   Type: PING
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x00,  // Stream: 0
+      0x00, 0x00, 0x00, 0x01,  // Ping (Truncated)
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+}
+
+// Tests handling of WINDOW_UPDATE frame with incorrect size.
+TEST_P(SpdyFramerTest, ReadIncorrectlySizedWindowUpdate) {
+  // WINDOW_UPDATE frame of size 3, which isn't correct.
+  const unsigned char kFrameData[] = {
+      0x00, 0x00, 0x03,        // Length: 3
+      0x08,                    //   Type: WINDOW_UPDATE
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x03,  // Stream: 3
+      0x00, 0x00, 0x01,        // WindowUpdate (Truncated)
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+}
+
+// Tests handling of RST_STREAM frame with incorrect size.
+TEST_P(SpdyFramerTest, ReadIncorrectlySizedRstStream) {
+  // RST_STREAM frame of size 3, which isn't correct.
+  const unsigned char kFrameData[] = {
+      0x00, 0x00, 0x03,        // Length: 3
+      0x03,                    //   Type: RST_STREAM
+      0x00,                    //  Flags: none
+      0x00, 0x00, 0x00, 0x03,  // Stream: 3
+      0x00, 0x00, 0x01,        // RstStream (Truncated)
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+}
+
+// Regression test for https://crbug.com/548674:
+// RST_STREAM with payload must not be accepted.
+TEST_P(SpdyFramerTest, ReadInvalidRstStreamWithPayload) {
+  const unsigned char kFrameData[] = {
+      0x00, 0x00, 0x07,        //  Length: 7
+      0x03,                    //    Type: RST_STREAM
+      0x00,                    //   Flags: none
+      0x00, 0x00, 0x00, 0x01,  //  Stream: 1
+      0x00, 0x00, 0x00, 0x00,  //   Error: NO_ERROR
+      'f',  'o',  'o'          // Payload: "foo"
+  };
+
+  TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+  visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state());
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+            visitor.deframer_.spdy_framer_error())
+      << Http2DecoderAdapter::SpdyFramerErrorToString(
+             visitor.deframer_.spdy_framer_error());
+}
+
+// Test that SpdyFramer processes, by default, all passed input in one call
+// to ProcessInput (i.e. will not be calling set_process_single_input_frame()).
+TEST_P(SpdyFramerTest, ProcessAllInput) {
+  auto visitor =
+      SpdyMakeUnique<TestSpdyVisitor>(SpdyFramer::DISABLE_COMPRESSION);
+  deframer_.set_visitor(visitor.get());
+
+  // Create two input frames.
+  SpdyHeadersIR headers(/* stream_id = */ 1);
+  headers.SetHeader("alpha", "beta");
+  headers.SetHeader("gamma", "charlie");
+  headers.SetHeader("cookie", "key1=value1; key2=value2");
+  SpdySerializedFrame headers_frame(SpdyFramerPeer::SerializeHeaders(
+      &framer_, headers, use_output_ ? &output_ : nullptr));
+
+  const char four_score[] = "Four score and seven years ago";
+  SpdyDataIR four_score_ir(/* stream_id = */ 1, four_score);
+  SpdySerializedFrame four_score_frame(framer_.SerializeData(four_score_ir));
+
+  // Put them in a single buffer (new variables here to make it easy to
+  // change the order and type of frames).
+  SpdySerializedFrame frame1 = std::move(headers_frame);
+  SpdySerializedFrame frame2 = std::move(four_score_frame);
+
+  const size_t frame1_size = frame1.size();
+  const size_t frame2_size = frame2.size();
+
+  VLOG(1) << "frame1_size = " << frame1_size;
+  VLOG(1) << "frame2_size = " << frame2_size;
+
+  SpdyString input_buffer;
+  input_buffer.append(frame1.data(), frame1_size);
+  input_buffer.append(frame2.data(), frame2_size);
+
+  const char* buf = input_buffer.data();
+  const size_t buf_size = input_buffer.size();
+
+  VLOG(1) << "buf_size = " << buf_size;
+
+  size_t processed = deframer_.ProcessInput(buf, buf_size);
+  EXPECT_EQ(buf_size, processed);
+  EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+  EXPECT_EQ(1, visitor->headers_frame_count_);
+  EXPECT_EQ(1, visitor->data_frame_count_);
+  EXPECT_EQ(strlen(four_score), static_cast<unsigned>(visitor->data_bytes_));
+}
+
+// Test that SpdyFramer stops after processing a full frame if
+// process_single_input_frame is set. Input to ProcessInput has two frames, but
+// only processes the first when we give it the first frame split at any point,
+// or give it more than one frame in the input buffer.
+TEST_P(SpdyFramerTest, ProcessAtMostOneFrame) {
+  deframer_.set_process_single_input_frame(true);
+
+  // Create two input frames.
+  const char four_score[] = "Four score and ...";
+  SpdyDataIR four_score_ir(/* stream_id = */ 1, four_score);
+  SpdySerializedFrame four_score_frame(framer_.SerializeData(four_score_ir));
+
+  SpdyHeadersIR headers(/* stream_id = */ 2);
+  headers.SetHeader("alpha", "beta");
+  headers.SetHeader("gamma", "charlie");
+  headers.SetHeader("cookie", "key1=value1; key2=value2");
+  SpdySerializedFrame headers_frame(SpdyFramerPeer::SerializeHeaders(
+      &framer_, headers, use_output_ ? &output_ : nullptr));
+
+  // Put them in a single buffer (new variables here to make it easy to
+  // change the order and type of frames).
+  SpdySerializedFrame frame1 = std::move(four_score_frame);
+  SpdySerializedFrame frame2 = std::move(headers_frame);
+
+  const size_t frame1_size = frame1.size();
+  const size_t frame2_size = frame2.size();
+
+  VLOG(1) << "frame1_size = " << frame1_size;
+  VLOG(1) << "frame2_size = " << frame2_size;
+
+  SpdyString input_buffer;
+  input_buffer.append(frame1.data(), frame1_size);
+  input_buffer.append(frame2.data(), frame2_size);
+
+  const char* buf = input_buffer.data();
+  const size_t buf_size = input_buffer.size();
+
+  VLOG(1) << "buf_size = " << buf_size;
+
+  for (size_t first_size = 0; first_size <= buf_size; ++first_size) {
+    VLOG(1) << "first_size = " << first_size;
+    auto visitor =
+        SpdyMakeUnique<TestSpdyVisitor>(SpdyFramer::DISABLE_COMPRESSION);
+    deframer_.set_visitor(visitor.get());
+
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+
+    size_t processed_first = deframer_.ProcessInput(buf, first_size);
+    if (first_size < frame1_size) {
+      EXPECT_EQ(first_size, processed_first);
+
+      if (first_size == 0) {
+        EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+      } else {
+        EXPECT_NE(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+      }
+
+      const char* rest = buf + processed_first;
+      const size_t remaining = buf_size - processed_first;
+      VLOG(1) << "remaining = " << remaining;
+
+      size_t processed_second = deframer_.ProcessInput(rest, remaining);
+
+      // Redundant tests just to make it easier to think about.
+      EXPECT_EQ(frame1_size - processed_first, processed_second);
+      size_t processed_total = processed_first + processed_second;
+      EXPECT_EQ(frame1_size, processed_total);
+    } else {
+      EXPECT_EQ(frame1_size, processed_first);
+    }
+
+    EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+
+    // At this point should have processed the entirety of the first frame,
+    // and none of the second frame.
+
+    EXPECT_EQ(1, visitor->data_frame_count_);
+    EXPECT_EQ(strlen(four_score), static_cast<unsigned>(visitor->data_bytes_));
+    EXPECT_EQ(0, visitor->headers_frame_count_);
+  }
+}
+
+namespace {
+void CheckFrameAndIRSize(SpdyFrameIR* ir,
+                         SpdyFramer* framer,
+                         ArrayOutputBuffer* output_buffer) {
+  output_buffer->Reset();
+  SpdyFrameType type = ir->frame_type();
+  size_t ir_size = ir->size();
+  framer->SerializeFrame(*ir, output_buffer);
+  if (type == SpdyFrameType::HEADERS || type == SpdyFrameType::PUSH_PROMISE) {
+    // For HEADERS and PUSH_PROMISE, the size is an estimate.
+    EXPECT_GE(ir_size, output_buffer->Size() * 9 / 10);
+    EXPECT_LT(ir_size, output_buffer->Size() * 11 / 10);
+  } else {
+    EXPECT_EQ(ir_size, output_buffer->Size());
+  }
+}
+}  // namespace
+
+TEST_P(SpdyFramerTest, SpdyFrameIRSize) {
+  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+
+  const char bytes[] = "this is a very short data frame";
+  SpdyDataIR data_ir(1, SpdyStringPiece(bytes, SPDY_ARRAYSIZE(bytes)));
+  CheckFrameAndIRSize(&data_ir, &framer, &output_);
+
+  SpdyRstStreamIR rst_ir(/* stream_id = */ 1, ERROR_CODE_PROTOCOL_ERROR);
+  CheckFrameAndIRSize(&rst_ir, &framer, &output_);
+
+  SpdySettingsIR settings_ir;
+  settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5);
+  settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6);
+  settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7);
+  CheckFrameAndIRSize(&settings_ir, &framer, &output_);
+
+  SpdyPingIR ping_ir(42);
+  CheckFrameAndIRSize(&ping_ir, &framer, &output_);
+
+  SpdyGoAwayIR goaway_ir(97, ERROR_CODE_NO_ERROR, "Goaway description");
+  CheckFrameAndIRSize(&goaway_ir, &framer, &output_);
+
+  SpdyHeadersIR headers_ir(1);
+  headers_ir.SetHeader("alpha", "beta");
+  headers_ir.SetHeader("gamma", "charlie");
+  headers_ir.SetHeader("cookie", "key1=value1; key2=value2");
+  CheckFrameAndIRSize(&headers_ir, &framer, &output_);
+
+  SpdyHeadersIR headers_ir_with_continuation(1);
+  headers_ir_with_continuation.SetHeader("alpha", SpdyString(100000, 'x'));
+  headers_ir_with_continuation.SetHeader("beta", SpdyString(100000, 'x'));
+  headers_ir_with_continuation.SetHeader("cookie", "key1=value1; key2=value2");
+  CheckFrameAndIRSize(&headers_ir_with_continuation, &framer, &output_);
+
+  SpdyWindowUpdateIR window_update_ir(4, 1024);
+  CheckFrameAndIRSize(&window_update_ir, &framer, &output_);
+
+  SpdyPushPromiseIR push_promise_ir(3, 8);
+  push_promise_ir.SetHeader("alpha", SpdyString(100000, 'x'));
+  push_promise_ir.SetHeader("beta", SpdyString(100000, 'x'));
+  push_promise_ir.SetHeader("cookie", "key1=value1; key2=value2");
+  CheckFrameAndIRSize(&push_promise_ir, &framer, &output_);
+
+  SpdyAltSvcWireFormat::AlternativeService altsvc1(
+      "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector());
+  SpdyAltSvcWireFormat::AlternativeService altsvc2(
+      "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24});
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+  altsvc_vector.push_back(altsvc1);
+  altsvc_vector.push_back(altsvc2);
+  SpdyAltSvcIR altsvc_ir(0);
+  altsvc_ir.set_origin("o_r|g!n");
+  altsvc_ir.add_altsvc(altsvc1);
+  altsvc_ir.add_altsvc(altsvc2);
+  CheckFrameAndIRSize(&altsvc_ir, &framer, &output_);
+
+  SpdyPriorityIR priority_ir(3, 1, 256, false);
+  CheckFrameAndIRSize(&priority_ir, &framer, &output_);
+
+  const char kDescription[] = "Unknown frame";
+  const uint8_t kType = 0xaf;
+  const uint8_t kFlags = 0x11;
+  SpdyUnknownIR unknown_ir(2, kType, kFlags, kDescription);
+  CheckFrameAndIRSize(&unknown_ir, &framer, &output_);
+}
+
+}  // namespace test
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_header_block.cc b/spdy/core/spdy_header_block.cc
new file mode 100644
index 0000000..dcf84b9
--- /dev/null
+++ b/spdy/core/spdy_header_block.cc
@@ -0,0 +1,401 @@
+// Copyright (c) 2012 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/spdy/core/spdy_header_block.h"
+
+#include <string.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h"
+
+namespace spdy {
+namespace {
+
+// By default, linked_hash_map's internal map allocates space for 100 map
+// buckets on construction, which is larger than necessary.  Standard library
+// unordered map implementations use a list of prime numbers to set the bucket
+// count for a particular capacity.  |kInitialMapBuckets| is chosen to reduce
+// memory usage for small header blocks, at the cost of having to rehash for
+// large header blocks.
+const size_t kInitialMapBuckets = 11;
+
+// SpdyHeaderBlock::Storage allocates blocks of this size by default.
+const size_t kDefaultStorageBlockSize = 2048;
+
+const char kCookieKey[] = "cookie";
+const char kNullSeparator = 0;
+
+SpdyStringPiece SeparatorForKey(SpdyStringPiece key) {
+  if (key == kCookieKey) {
+    static SpdyStringPiece cookie_separator = "; ";
+    return cookie_separator;
+  } else {
+    return SpdyStringPiece(&kNullSeparator, 1);
+  }
+}
+
+}  // namespace
+
+// This class provides a backing store for SpdyStringPieces. It previously used
+// custom allocation logic, but now uses an UnsafeArena instead. It has the
+// property that SpdyStringPieces that refer to data in Storage are never
+// invalidated until the Storage is deleted or Clear() is called.
+//
+// Write operations always append to the last block. If there is not enough
+// space to perform the write, a new block is allocated, and any unused space
+// is wasted.
+class SpdyHeaderBlock::Storage {
+ public:
+  Storage() : arena_(kDefaultStorageBlockSize) {}
+  Storage(const Storage&) = delete;
+  Storage& operator=(const Storage&) = delete;
+
+  SpdyStringPiece Write(const SpdyStringPiece s) {
+    return SpdyStringPiece(arena_.Memdup(s.data(), s.size()), s.size());
+  }
+
+  // If |s| points to the most recent allocation from arena_, the arena will
+  // reclaim the memory. Otherwise, this method is a no-op.
+  void Rewind(const SpdyStringPiece s) {
+    arena_.Free(const_cast<char*>(s.data()), s.size());
+  }
+
+  void Clear() { arena_.Reset(); }
+
+  // Given a list of fragments and a separator, writes the fragments joined by
+  // the separator to a contiguous region of memory. Returns a SpdyStringPiece
+  // pointing to the region of memory.
+  SpdyStringPiece WriteFragments(const std::vector<SpdyStringPiece>& fragments,
+                                 SpdyStringPiece separator) {
+    if (fragments.empty()) {
+      return SpdyStringPiece();
+    }
+    size_t total_size = separator.size() * (fragments.size() - 1);
+    for (const auto fragment : fragments) {
+      total_size += fragment.size();
+    }
+    char* dst = arena_.Alloc(total_size);
+    size_t written = Join(dst, fragments, separator);
+    DCHECK_EQ(written, total_size);
+    return SpdyStringPiece(dst, total_size);
+  }
+
+  size_t bytes_allocated() const { return arena_.status().bytes_allocated(); }
+
+  // TODO(xunjieli): https://crbug.com/669108. Merge this with bytes_allocated()
+  size_t EstimateMemoryUsage() const {
+    return arena_.status().bytes_allocated();
+  }
+
+ private:
+  SpdyUnsafeArena arena_;
+};
+
+SpdyHeaderBlock::HeaderValue::HeaderValue(Storage* storage,
+                                          SpdyStringPiece key,
+                                          SpdyStringPiece initial_value)
+    : storage_(storage),
+      fragments_({initial_value}),
+      pair_({key, {}}),
+      size_(initial_value.size()),
+      separator_size_(SeparatorForKey(key).size()) {}
+
+SpdyHeaderBlock::HeaderValue::HeaderValue(HeaderValue&& other)
+    : storage_(other.storage_),
+      fragments_(std::move(other.fragments_)),
+      pair_(std::move(other.pair_)),
+      size_(other.size_),
+      separator_size_(other.separator_size_) {}
+
+SpdyHeaderBlock::HeaderValue& SpdyHeaderBlock::HeaderValue::operator=(
+    HeaderValue&& other) {
+  storage_ = other.storage_;
+  fragments_ = std::move(other.fragments_);
+  pair_ = std::move(other.pair_);
+  size_ = other.size_;
+  separator_size_ = other.separator_size_;
+  return *this;
+}
+
+SpdyHeaderBlock::HeaderValue::~HeaderValue() = default;
+
+SpdyStringPiece SpdyHeaderBlock::HeaderValue::ConsolidatedValue() const {
+  if (fragments_.empty()) {
+    return SpdyStringPiece();
+  }
+  if (fragments_.size() > 1) {
+    fragments_ = {
+        storage_->WriteFragments(fragments_, SeparatorForKey(pair_.first))};
+  }
+  return fragments_[0];
+}
+
+void SpdyHeaderBlock::HeaderValue::Append(SpdyStringPiece fragment) {
+  size_ += (fragment.size() + separator_size_);
+  fragments_.push_back(fragment);
+}
+
+const std::pair<SpdyStringPiece, SpdyStringPiece>&
+SpdyHeaderBlock::HeaderValue::as_pair() const {
+  pair_.second = ConsolidatedValue();
+  return pair_;
+}
+
+SpdyHeaderBlock::iterator::iterator(MapType::const_iterator it) : it_(it) {}
+
+SpdyHeaderBlock::iterator::iterator(const iterator& other) = default;
+
+SpdyHeaderBlock::iterator::~iterator() = default;
+
+SpdyHeaderBlock::ValueProxy::ValueProxy(
+    SpdyHeaderBlock::MapType* block,
+    SpdyHeaderBlock::Storage* storage,
+    SpdyHeaderBlock::MapType::iterator lookup_result,
+    const SpdyStringPiece key,
+    size_t* spdy_header_block_value_size)
+    : block_(block),
+      storage_(storage),
+      lookup_result_(lookup_result),
+      key_(key),
+      spdy_header_block_value_size_(spdy_header_block_value_size),
+      valid_(true) {}
+
+SpdyHeaderBlock::ValueProxy::ValueProxy(ValueProxy&& other)
+    : block_(other.block_),
+      storage_(other.storage_),
+      lookup_result_(other.lookup_result_),
+      key_(other.key_),
+      spdy_header_block_value_size_(other.spdy_header_block_value_size_),
+      valid_(true) {
+  other.valid_ = false;
+}
+
+SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=(
+    SpdyHeaderBlock::ValueProxy&& other) {
+  block_ = other.block_;
+  storage_ = other.storage_;
+  lookup_result_ = other.lookup_result_;
+  key_ = other.key_;
+  valid_ = true;
+  other.valid_ = false;
+  spdy_header_block_value_size_ = other.spdy_header_block_value_size_;
+  return *this;
+}
+
+SpdyHeaderBlock::ValueProxy::~ValueProxy() {
+  // If the ValueProxy is destroyed while lookup_result_ == block_->end(),
+  // the assignment operator was never used, and the block's Storage can
+  // reclaim the memory used by the key. This makes lookup-only access to
+  // SpdyHeaderBlock through operator[] memory-neutral.
+  if (valid_ && lookup_result_ == block_->end()) {
+    storage_->Rewind(key_);
+  }
+}
+
+SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=(
+    const SpdyStringPiece value) {
+  *spdy_header_block_value_size_ += value.size();
+  if (lookup_result_ == block_->end()) {
+    DVLOG(1) << "Inserting: (" << key_ << ", " << value << ")";
+    lookup_result_ =
+        block_
+            ->emplace(std::make_pair(
+                key_, HeaderValue(storage_, key_, storage_->Write(value))))
+            .first;
+  } else {
+    DVLOG(1) << "Updating key: " << key_ << " with value: " << value;
+    *spdy_header_block_value_size_ -= lookup_result_->second.SizeEstimate();
+    lookup_result_->second =
+        HeaderValue(storage_, key_, storage_->Write(value));
+  }
+  return *this;
+}
+
+SpdyString SpdyHeaderBlock::ValueProxy::as_string() const {
+  if (lookup_result_ == block_->end()) {
+    return "";
+  } else {
+    return SpdyString(lookup_result_->second.value());
+  }
+}
+
+SpdyHeaderBlock::SpdyHeaderBlock() : block_(kInitialMapBuckets) {}
+
+SpdyHeaderBlock::SpdyHeaderBlock(SpdyHeaderBlock&& other)
+    : block_(kInitialMapBuckets) {
+  block_.swap(other.block_);
+  storage_.swap(other.storage_);
+  key_size_ = other.key_size_;
+  value_size_ = other.value_size_;
+}
+
+SpdyHeaderBlock::~SpdyHeaderBlock() = default;
+
+SpdyHeaderBlock& SpdyHeaderBlock::operator=(SpdyHeaderBlock&& other) {
+  block_.swap(other.block_);
+  storage_.swap(other.storage_);
+  key_size_ = other.key_size_;
+  value_size_ = other.value_size_;
+  return *this;
+}
+
+SpdyHeaderBlock SpdyHeaderBlock::Clone() const {
+  SpdyHeaderBlock copy;
+  for (const auto& p : *this) {
+    copy.AppendHeader(p.first, p.second);
+  }
+  return copy;
+}
+
+bool SpdyHeaderBlock::operator==(const SpdyHeaderBlock& other) const {
+  return size() == other.size() && std::equal(begin(), end(), other.begin());
+}
+
+bool SpdyHeaderBlock::operator!=(const SpdyHeaderBlock& other) const {
+  return !(operator==(other));
+}
+
+SpdyString SpdyHeaderBlock::DebugString() const {
+  if (empty()) {
+    return "{}";
+  }
+
+  SpdyString output = "\n{\n";
+  for (auto it = begin(); it != end(); ++it) {
+    SpdyStrAppend(&output, "  ", it->first, " ", it->second, "\n");
+  }
+  SpdyStrAppend(&output, "}\n");
+  return output;
+}
+
+void SpdyHeaderBlock::erase(SpdyStringPiece key) {
+  auto iter = block_.find(key);
+  if (iter != block_.end()) {
+    DVLOG(1) << "Erasing header with name: " << key;
+    key_size_ -= key.size();
+    value_size_ -= iter->second.SizeEstimate();
+    block_.erase(iter);
+  }
+}
+
+void SpdyHeaderBlock::clear() {
+  key_size_ = 0;
+  value_size_ = 0;
+  block_.clear();
+  storage_.reset();
+}
+
+void SpdyHeaderBlock::insert(const SpdyHeaderBlock::value_type& value) {
+  // TODO(birenroy): Write new value in place of old value, if it fits.
+  value_size_ += value.second.size();
+
+  auto iter = block_.find(value.first);
+  if (iter == block_.end()) {
+    DVLOG(1) << "Inserting: (" << value.first << ", " << value.second << ")";
+    AppendHeader(value.first, value.second);
+  } else {
+    DVLOG(1) << "Updating key: " << iter->first
+             << " with value: " << value.second;
+    value_size_ -= iter->second.SizeEstimate();
+    auto* storage = GetStorage();
+    iter->second =
+        HeaderValue(storage, iter->first, storage->Write(value.second));
+  }
+}
+
+SpdyHeaderBlock::ValueProxy SpdyHeaderBlock::operator[](
+    const SpdyStringPiece key) {
+  DVLOG(2) << "Operator[] saw key: " << key;
+  SpdyStringPiece out_key;
+  auto iter = block_.find(key);
+  if (iter == block_.end()) {
+    // We write the key first, to assure that the ValueProxy has a
+    // reference to a valid SpdyStringPiece in its operator=.
+    out_key = WriteKey(key);
+    DVLOG(2) << "Key written as: " << std::hex
+             << static_cast<const void*>(key.data()) << ", " << std::dec
+             << key.size();
+  } else {
+    out_key = iter->first;
+  }
+  return ValueProxy(&block_, GetStorage(), iter, out_key, &value_size_);
+}
+
+void SpdyHeaderBlock::AppendValueOrAddHeader(const SpdyStringPiece key,
+                                             const SpdyStringPiece value) {
+  value_size_ += value.size();
+
+  auto iter = block_.find(key);
+  if (iter == block_.end()) {
+    DVLOG(1) << "Inserting: (" << key << ", " << value << ")";
+
+    AppendHeader(key, value);
+    return;
+  }
+  DVLOG(1) << "Updating key: " << iter->first << "; appending value: " << value;
+  value_size_ += SeparatorForKey(key).size();
+  iter->second.Append(GetStorage()->Write(value));
+}
+
+size_t SpdyHeaderBlock::EstimateMemoryUsage() const {
+  // TODO(xunjieli): https://crbug.com/669108. Also include |block_| when EMU()
+  // supports linked_hash_map.
+  return SpdyEstimateMemoryUsage(storage_);
+}
+
+void SpdyHeaderBlock::AppendHeader(const SpdyStringPiece key,
+                                   const SpdyStringPiece value) {
+  auto backed_key = WriteKey(key);
+  auto* storage = GetStorage();
+  block_.emplace(std::make_pair(
+      backed_key, HeaderValue(storage, backed_key, storage->Write(value))));
+}
+
+SpdyHeaderBlock::Storage* SpdyHeaderBlock::GetStorage() {
+  if (storage_ == nullptr) {
+    storage_ = SpdyMakeUnique<Storage>();
+  }
+  return storage_.get();
+}
+
+SpdyStringPiece SpdyHeaderBlock::WriteKey(const SpdyStringPiece key) {
+  key_size_ += key.size();
+  return GetStorage()->Write(key);
+}
+
+size_t SpdyHeaderBlock::bytes_allocated() const {
+  if (storage_ == nullptr) {
+    return 0;
+  } else {
+    return storage_->bytes_allocated();
+  }
+}
+
+size_t Join(char* dst,
+            const std::vector<SpdyStringPiece>& fragments,
+            SpdyStringPiece separator) {
+  if (fragments.empty()) {
+    return 0;
+  }
+  auto* original_dst = dst;
+  auto it = fragments.begin();
+  memcpy(dst, it->data(), it->size());
+  dst += it->size();
+  for (++it; it != fragments.end(); ++it) {
+    memcpy(dst, separator.data(), separator.size());
+    dst += separator.size();
+    memcpy(dst, it->data(), it->size());
+    dst += it->size();
+  }
+  return dst - original_dst;
+}
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_header_block.h b/spdy/core/spdy_header_block.h
new file mode 100644
index 0000000..f453246
--- /dev/null
+++ b/spdy/core/spdy_header_block.h
@@ -0,0 +1,254 @@
+// Copyright (c) 2012 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_SPDY_CORE_SPDY_HEADER_BLOCK_H_
+#define QUICHE_SPDY_CORE_SPDY_HEADER_BLOCK_H_
+
+#include <stddef.h>
+
+#include <list>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+class SpdyHeaderBlockPeer;
+class ValueProxyPeer;
+}  // namespace test
+
+// This class provides a key-value map that can be used to store SPDY header
+// names and values. This data structure preserves insertion order.
+//
+// Under the hood, this data structure uses large, contiguous blocks of memory
+// to store names and values. Lookups may be performed with SpdyStringPiece
+// keys, and values are returned as SpdyStringPieces (via ValueProxy, below).
+// Value SpdyStringPieces are valid as long as the SpdyHeaderBlock exists;
+// allocated memory is never freed until SpdyHeaderBlock's destruction.
+//
+// This implementation does not make much of an effort to minimize wasted space.
+// It's expected that keys are rarely deleted from a SpdyHeaderBlock.
+class SPDY_EXPORT_PRIVATE SpdyHeaderBlock {
+ private:
+  class Storage;
+
+  // Stores a list of value fragments that can be joined later with a
+  // key-dependent separator.
+  class SPDY_EXPORT_PRIVATE HeaderValue {
+   public:
+    HeaderValue(Storage* storage,
+                SpdyStringPiece key,
+                SpdyStringPiece initial_value);
+
+    // Moves are allowed.
+    HeaderValue(HeaderValue&& other);
+    HeaderValue& operator=(HeaderValue&& other);
+
+    // Copies are not.
+    HeaderValue(const HeaderValue& other) = delete;
+    HeaderValue& operator=(const HeaderValue& other) = delete;
+
+    ~HeaderValue();
+
+    // Consumes at most |fragment.size()| bytes of memory.
+    void Append(SpdyStringPiece fragment);
+
+    SpdyStringPiece value() const { return as_pair().second; }
+    const std::pair<SpdyStringPiece, SpdyStringPiece>& as_pair() const;
+
+    // Size estimate including separators. Used when keys are erased from
+    // SpdyHeaderBlock.
+    size_t SizeEstimate() const { return size_; }
+
+   private:
+    // May allocate a large contiguous region of memory to hold the concatenated
+    // fragments and separators.
+    SpdyStringPiece ConsolidatedValue() const;
+
+    mutable Storage* storage_;
+    mutable std::vector<SpdyStringPiece> fragments_;
+    // The first element is the key; the second is the consolidated value.
+    mutable std::pair<SpdyStringPiece, SpdyStringPiece> pair_;
+    size_t size_ = 0;
+    size_t separator_size_ = 0;
+  };
+
+  typedef SpdyLinkedHashMap<SpdyStringPiece, HeaderValue, SpdyStringPieceHash>
+      MapType;
+
+ public:
+  typedef std::pair<SpdyStringPiece, SpdyStringPiece> value_type;
+
+  // Provides iteration over a sequence of std::pair<SpdyStringPiece,
+  // SpdyStringPiece>, even though the underlying MapType::value_type is
+  // different. Dereferencing the iterator will result in memory allocation for
+  // multi-value headers.
+  class SPDY_EXPORT_PRIVATE iterator {
+   public:
+    // The following type definitions fulfill the requirements for iterator
+    // implementations.
+    typedef std::pair<SpdyStringPiece, SpdyStringPiece> value_type;
+    typedef value_type& reference;
+    typedef value_type* pointer;
+    typedef std::forward_iterator_tag iterator_category;
+    typedef MapType::iterator::difference_type difference_type;
+
+    // In practice, this iterator only offers access to const value_type.
+    typedef const value_type& const_reference;
+    typedef const value_type* const_pointer;
+
+    explicit iterator(MapType::const_iterator it);
+    iterator(const iterator& other);
+    ~iterator();
+
+    // This will result in memory allocation if the value consists of multiple
+    // fragments.
+    const_reference operator*() const { return it_->second.as_pair(); }
+
+    const_pointer operator->() const { return &(this->operator*()); }
+    bool operator==(const iterator& it) const { return it_ == it.it_; }
+    bool operator!=(const iterator& it) const { return !(*this == it); }
+
+    iterator& operator++() {
+      it_++;
+      return *this;
+    }
+
+    iterator operator++(int) {
+      auto ret = *this;
+      this->operator++();
+      return ret;
+    }
+
+   private:
+    MapType::const_iterator it_;
+  };
+  typedef iterator const_iterator;
+
+  class ValueProxy;
+
+  SpdyHeaderBlock();
+  SpdyHeaderBlock(const SpdyHeaderBlock& other) = delete;
+  SpdyHeaderBlock(SpdyHeaderBlock&& other);
+  ~SpdyHeaderBlock();
+
+  SpdyHeaderBlock& operator=(const SpdyHeaderBlock& other) = delete;
+  SpdyHeaderBlock& operator=(SpdyHeaderBlock&& other);
+  SpdyHeaderBlock Clone() const;
+
+  bool operator==(const SpdyHeaderBlock& other) const;
+  bool operator!=(const SpdyHeaderBlock& other) const;
+
+  // Provides a human readable multi-line representation of the stored header
+  // keys and values.
+  SpdyString DebugString() const;
+
+  iterator begin() { return iterator(block_.begin()); }
+  iterator end() { return iterator(block_.end()); }
+  const_iterator begin() const { return const_iterator(block_.begin()); }
+  const_iterator end() const { return const_iterator(block_.end()); }
+  bool empty() const { return block_.empty(); }
+  size_t size() const { return block_.size(); }
+  iterator find(SpdyStringPiece key) { return iterator(block_.find(key)); }
+  const_iterator find(SpdyStringPiece key) const {
+    return const_iterator(block_.find(key));
+  }
+  void erase(SpdyStringPiece key);
+
+  // Clears both our MapType member and the memory used to hold headers.
+  void clear();
+
+  // The next few methods copy data into our backing storage.
+
+  // If key already exists in the block, replaces the value of that key. Else
+  // adds a new header to the end of the block.
+  void insert(const value_type& value);
+
+  // If a header with the key is already present, then append the value to the
+  // existing header value, NUL ("\0") separated unless the key is cookie, in
+  // which case the separator is "; ".
+  // If there is no such key, a new header with the key and value is added.
+  void AppendValueOrAddHeader(const SpdyStringPiece key,
+                              const SpdyStringPiece value);
+
+  // Allows either lookup or mutation of the value associated with a key.
+  ValueProxy operator[](const SpdyStringPiece key) SPDY_MUST_USE_RESULT;
+
+  // This object provides automatic conversions that allow SpdyHeaderBlock to be
+  // nearly a drop-in replacement for SpdyLinkedHashMap<SpdyString, SpdyString>.
+  // It reads data from or writes data to a SpdyHeaderBlock::Storage.
+  class SPDY_EXPORT_PRIVATE ValueProxy {
+   public:
+    ~ValueProxy();
+
+    // Moves are allowed.
+    ValueProxy(ValueProxy&& other);
+    ValueProxy& operator=(ValueProxy&& other);
+
+    // Copies are not.
+    ValueProxy(const ValueProxy& other) = delete;
+    ValueProxy& operator=(const ValueProxy& other) = delete;
+
+    // Assignment modifies the underlying SpdyHeaderBlock.
+    ValueProxy& operator=(const SpdyStringPiece other);
+
+    SpdyString as_string() const;
+
+   private:
+    friend class SpdyHeaderBlock;
+    friend class test::ValueProxyPeer;
+
+    ValueProxy(SpdyHeaderBlock::MapType* block,
+               SpdyHeaderBlock::Storage* storage,
+               SpdyHeaderBlock::MapType::iterator lookup_result,
+               const SpdyStringPiece key,
+               size_t* spdy_header_block_value_size);
+
+    SpdyHeaderBlock::MapType* block_;
+    SpdyHeaderBlock::Storage* storage_;
+    SpdyHeaderBlock::MapType::iterator lookup_result_;
+    SpdyStringPiece key_;
+    size_t* spdy_header_block_value_size_;
+    bool valid_;
+  };
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+  size_t TotalBytesUsed() const { return key_size_ + value_size_; }
+
+ private:
+  friend class test::SpdyHeaderBlockPeer;
+
+  void AppendHeader(const SpdyStringPiece key, const SpdyStringPiece value);
+  Storage* GetStorage();
+  SpdyStringPiece WriteKey(const SpdyStringPiece key);
+  size_t bytes_allocated() const;
+
+  // SpdyStringPieces held by |block_| point to memory owned by |*storage_|.
+  // |storage_| might be nullptr as long as |block_| is empty.
+  MapType block_;
+  std::unique_ptr<Storage> storage_;
+
+  size_t key_size_ = 0;
+  size_t value_size_ = 0;
+};
+
+// Writes |fragments| to |dst|, joined by |separator|. |dst| must be large
+// enough to hold the result. Returns the number of bytes written.
+SPDY_EXPORT_PRIVATE size_t Join(char* dst,
+                                const std::vector<SpdyStringPiece>& fragments,
+                                SpdyStringPiece separator);
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_HEADER_BLOCK_H_
diff --git a/spdy/core/spdy_header_block_test.cc b/spdy/core/spdy_header_block_test.cc
new file mode 100644
index 0000000..3511a21
--- /dev/null
+++ b/spdy/core/spdy_header_block_test.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2012 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/spdy/core/spdy_header_block.h"
+
+#include <memory>
+#include <utility>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+
+using ::testing::ElementsAre;
+
+namespace spdy {
+namespace test {
+
+class ValueProxyPeer {
+ public:
+  static SpdyStringPiece key(SpdyHeaderBlock::ValueProxy* p) { return p->key_; }
+};
+
+std::pair<SpdyStringPiece, SpdyStringPiece> Pair(SpdyStringPiece k,
+                                                 SpdyStringPiece v) {
+  return std::make_pair(k, v);
+}
+
+// This test verifies that SpdyHeaderBlock behaves correctly when empty.
+TEST(SpdyHeaderBlockTest, EmptyBlock) {
+  SpdyHeaderBlock block;
+  EXPECT_TRUE(block.empty());
+  EXPECT_EQ(0u, block.size());
+  EXPECT_EQ(block.end(), block.find("foo"));
+  EXPECT_TRUE(block.end() == block.begin());
+
+  // Should have no effect.
+  block.erase("bar");
+}
+
+TEST(SpdyHeaderBlockTest, KeyMemoryReclaimedOnLookup) {
+  SpdyHeaderBlock block;
+  SpdyStringPiece copied_key1;
+  {
+    auto proxy1 = block["some key name"];
+    copied_key1 = ValueProxyPeer::key(&proxy1);
+  }
+  SpdyStringPiece copied_key2;
+  {
+    auto proxy2 = block["some other key name"];
+    copied_key2 = ValueProxyPeer::key(&proxy2);
+  }
+  // Because proxy1 was never used to modify the block, the memory used for the
+  // key could be reclaimed and used for the second call to operator[].
+  // Therefore, we expect the pointers of the two SpdyStringPieces to be equal.
+  EXPECT_EQ(copied_key1.data(), copied_key2.data());
+
+  {
+    auto proxy1 = block["some key name"];
+    block["some other key name"] = "some value";
+  }
+  // Nothing should blow up when proxy1 is destructed, and we should be able to
+  // modify and access the SpdyHeaderBlock.
+  block["key"] = "value";
+  EXPECT_EQ("value", block["key"]);
+  EXPECT_EQ("some value", block["some other key name"]);
+  EXPECT_TRUE(block.find("some key name") == block.end());
+}
+
+// This test verifies that headers can be set in a variety of ways.
+TEST(SpdyHeaderBlockTest, AddHeaders) {
+  SpdyHeaderBlock block;
+  block["foo"] = SpdyString(300, 'x');
+  block["bar"] = "baz";
+  block["qux"] = "qux1";
+  block["qux"] = "qux2";
+  block.insert(std::make_pair("key", "value"));
+
+  EXPECT_EQ(Pair("foo", SpdyString(300, 'x')), *block.find("foo"));
+  EXPECT_EQ("baz", block["bar"]);
+  SpdyString qux("qux");
+  EXPECT_EQ("qux2", block[qux]);
+  ASSERT_NE(block.end(), block.find("key"));
+  EXPECT_EQ(Pair("key", "value"), *block.find("key"));
+
+  block.erase("key");
+  EXPECT_EQ(block.end(), block.find("key"));
+}
+
+// This test verifies that SpdyHeaderBlock can be copied using Clone().
+TEST(SpdyHeaderBlockTest, CopyBlocks) {
+  SpdyHeaderBlock block1;
+  block1["foo"] = SpdyString(300, 'x');
+  block1["bar"] = "baz";
+  block1.insert(std::make_pair("qux", "qux1"));
+
+  SpdyHeaderBlock block2 = block1.Clone();
+  SpdyHeaderBlock block3(block1.Clone());
+
+  EXPECT_EQ(block1, block2);
+  EXPECT_EQ(block1, block3);
+}
+
+TEST(SpdyHeaderBlockTest, Equality) {
+  // Test equality and inequality operators.
+  SpdyHeaderBlock block1;
+  block1["foo"] = "bar";
+
+  SpdyHeaderBlock block2;
+  block2["foo"] = "bar";
+
+  SpdyHeaderBlock block3;
+  block3["baz"] = "qux";
+
+  EXPECT_EQ(block1, block2);
+  EXPECT_NE(block1, block3);
+
+  block2["baz"] = "qux";
+  EXPECT_NE(block1, block2);
+}
+
+// Test that certain methods do not crash on moved-from instances.
+TEST(SpdyHeaderBlockTest, MovedFromIsValid) {
+  SpdyHeaderBlock block1;
+  block1["foo"] = "bar";
+
+  SpdyHeaderBlock block2(std::move(block1));
+  EXPECT_THAT(block2, ElementsAre(Pair("foo", "bar")));
+
+  block1["baz"] = "qux";  // NOLINT  testing post-move behavior
+
+  SpdyHeaderBlock block3(std::move(block1));
+
+  block1["foo"] = "bar";  // NOLINT  testing post-move behavior
+
+  SpdyHeaderBlock block4(std::move(block1));
+
+  block1.clear();  // NOLINT  testing post-move behavior
+  EXPECT_TRUE(block1.empty());
+
+  block1["foo"] = "bar";
+  EXPECT_THAT(block1, ElementsAre(Pair("foo", "bar")));
+}
+
+// This test verifies that headers can be appended to no matter how they were
+// added originally.
+TEST(SpdyHeaderBlockTest, AppendHeaders) {
+  SpdyHeaderBlock block;
+  block["foo"] = "foo";
+  block.AppendValueOrAddHeader("foo", "bar");
+  EXPECT_EQ(Pair("foo", SpdyString("foo\0bar", 7)), *block.find("foo"));
+
+  block.insert(std::make_pair("foo", "baz"));
+  EXPECT_EQ("baz", block["foo"]);
+  EXPECT_EQ(Pair("foo", "baz"), *block.find("foo"));
+
+  // Try all four methods of adding an entry.
+  block["cookie"] = "key1=value1";
+  block.AppendValueOrAddHeader("h1", "h1v1");
+  block.insert(std::make_pair("h2", "h2v1"));
+
+  block.AppendValueOrAddHeader("h3", "h3v2");
+  block.AppendValueOrAddHeader("h2", "h2v2");
+  block.AppendValueOrAddHeader("h1", "h1v2");
+  block.AppendValueOrAddHeader("cookie", "key2=value2");
+
+  block.AppendValueOrAddHeader("cookie", "key3=value3");
+  block.AppendValueOrAddHeader("h1", "h1v3");
+  block.AppendValueOrAddHeader("h2", "h2v3");
+  block.AppendValueOrAddHeader("h3", "h3v3");
+  block.AppendValueOrAddHeader("h4", "singleton");
+
+  EXPECT_EQ("key1=value1; key2=value2; key3=value3", block["cookie"]);
+  EXPECT_EQ("baz", block["foo"]);
+  EXPECT_EQ(SpdyString("h1v1\0h1v2\0h1v3", 14), block["h1"]);
+  EXPECT_EQ(SpdyString("h2v1\0h2v2\0h2v3", 14), block["h2"]);
+  EXPECT_EQ(SpdyString("h3v2\0h3v3", 9), block["h3"]);
+  EXPECT_EQ("singleton", block["h4"]);
+}
+
+TEST(JoinTest, JoinEmpty) {
+  std::vector<SpdyStringPiece> empty;
+  SpdyStringPiece separator = ", ";
+  char buf[10] = "";
+  size_t written = Join(buf, empty, separator);
+  EXPECT_EQ(0u, written);
+}
+
+TEST(JoinTest, JoinOne) {
+  std::vector<SpdyStringPiece> v = {"one"};
+  SpdyStringPiece separator = ", ";
+  char buf[15];
+  size_t written = Join(buf, v, separator);
+  EXPECT_EQ(3u, written);
+  EXPECT_EQ("one", SpdyStringPiece(buf, written));
+}
+
+TEST(JoinTest, JoinMultiple) {
+  std::vector<SpdyStringPiece> v = {"one", "two", "three"};
+  SpdyStringPiece separator = ", ";
+  char buf[15];
+  size_t written = Join(buf, v, separator);
+  EXPECT_EQ(15u, written);
+  EXPECT_EQ("one, two, three", SpdyStringPiece(buf, written));
+}
+
+namespace {
+size_t SpdyHeaderBlockSize(const SpdyHeaderBlock& block) {
+  size_t size = 0;
+  for (const auto& pair : block) {
+    size += pair.first.size() + pair.second.size();
+  }
+  return size;
+}
+}  // namespace
+
+// Tests SpdyHeaderBlock SizeEstimate().
+TEST(SpdyHeaderBlockTest, TotalBytesUsed) {
+  SpdyHeaderBlock block;
+  const size_t value_size = 300;
+  block["foo"] = SpdyString(value_size, 'x');
+  EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+  block.insert(std::make_pair("key", SpdyString(value_size, 'x')));
+  EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+  block.AppendValueOrAddHeader("abc", SpdyString(value_size, 'x'));
+  EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+
+  // Replace value for existing key.
+  block["foo"] = SpdyString(value_size, 'x');
+  EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+  block.insert(std::make_pair("key", SpdyString(value_size, 'x')));
+  EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+  // Add value for existing key.
+  block.AppendValueOrAddHeader("abc", SpdyString(value_size, 'x'));
+  EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+
+  // Copies/clones SpdyHeaderBlock.
+  size_t block_size = block.TotalBytesUsed();
+  SpdyHeaderBlock block_copy = std::move(block);
+  EXPECT_EQ(block_size, block_copy.TotalBytesUsed());
+
+  // Erases key.
+  block_copy.erase("foo");
+  EXPECT_EQ(block_copy.TotalBytesUsed(), SpdyHeaderBlockSize(block_copy));
+  block_copy.erase("key");
+  EXPECT_EQ(block_copy.TotalBytesUsed(), SpdyHeaderBlockSize(block_copy));
+  block_copy.erase("abc");
+  EXPECT_EQ(block_copy.TotalBytesUsed(), SpdyHeaderBlockSize(block_copy));
+}
+
+}  // namespace test
+}  // namespace spdy
diff --git a/spdy/core/spdy_headers_handler_interface.h b/spdy/core/spdy_headers_handler_interface.h
new file mode 100644
index 0000000..ce6c1b6
--- /dev/null
+++ b/spdy/core/spdy_headers_handler_interface.h
@@ -0,0 +1,39 @@
+// Copyright 2015 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_SPDY_CORE_SPDY_HEADERS_HANDLER_INTERFACE_H_
+#define QUICHE_SPDY_CORE_SPDY_HEADERS_HANDLER_INTERFACE_H_
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+// This interface defines how an object that accepts header data should behave.
+// It is used by both SpdyHeadersBlockParser and HpackDecoder.
+class SPDY_EXPORT_PRIVATE SpdyHeadersHandlerInterface {
+ public:
+  virtual ~SpdyHeadersHandlerInterface() {}
+
+  // A callback method which notifies when the parser starts handling a new
+  // header block. Will only be called once per block, even if it extends into
+  // CONTINUATION frames.
+  virtual void OnHeaderBlockStart() = 0;
+
+  // A callback method which notifies on a header key value pair. Multiple
+  // values for a given key will be emitted as multiple calls to OnHeader.
+  virtual void OnHeader(SpdyStringPiece key, SpdyStringPiece value) = 0;
+
+  // A callback method which notifies when the parser finishes handling a
+  // header block (i.e. the containing frame has the END_HEADERS flag set).
+  // Also indicates the total number of bytes in this block.
+  virtual void OnHeaderBlockEnd(size_t uncompressed_header_bytes,
+                                size_t compressed_header_bytes) = 0;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_HEADERS_HANDLER_INTERFACE_H_
diff --git a/spdy/core/spdy_no_op_visitor.cc b/spdy/core/spdy_no_op_visitor.cc
new file mode 100644
index 0000000..5dcc15c
--- /dev/null
+++ b/spdy/core/spdy_no_op_visitor.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 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/spdy/core/spdy_no_op_visitor.h"
+
+#include <type_traits>
+
+namespace spdy {
+namespace test {
+
+SpdyNoOpVisitor::SpdyNoOpVisitor() {
+  static_assert(std::is_abstract<SpdyNoOpVisitor>::value == false,
+                "Need to update SpdyNoOpVisitor.");
+}
+SpdyNoOpVisitor::~SpdyNoOpVisitor() = default;
+
+SpdyHeadersHandlerInterface* SpdyNoOpVisitor::OnHeaderFrameStart(
+    SpdyStreamId stream_id) {
+  return this;
+}
+
+bool SpdyNoOpVisitor::OnUnknownFrame(SpdyStreamId stream_id,
+                                     uint8_t frame_type) {
+  return true;
+}
+
+}  // namespace test
+}  // namespace spdy
diff --git a/spdy/core/spdy_no_op_visitor.h b/spdy/core/spdy_no_op_visitor.h
new file mode 100644
index 0000000..80e1535
--- /dev/null
+++ b/spdy/core/spdy_no_op_visitor.h
@@ -0,0 +1,89 @@
+// Copyright (c) 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.
+
+// SpdyNoOpVisitor implements several of the visitor and handler interfaces
+// to make it easier to write tests that need to provide instances. Other
+// interfaces can be added as needed.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_NO_OP_VISITOR_H_
+#define QUICHE_SPDY_CORE_SPDY_NO_OP_VISITOR_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+namespace test {
+
+class SpdyNoOpVisitor : public SpdyFramerVisitorInterface,
+                        public SpdyFramerDebugVisitorInterface,
+                        public SpdyHeadersHandlerInterface {
+ public:
+  SpdyNoOpVisitor();
+  ~SpdyNoOpVisitor() override;
+
+  // SpdyFramerVisitorInterface methods:
+  void OnError(http2::Http2DecoderAdapter::SpdyFramerError error) override {}
+  SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+      SpdyStreamId stream_id) override;
+  void OnHeaderFrameEnd(SpdyStreamId stream_id) override {}
+  void OnDataFrameHeader(SpdyStreamId stream_id,
+                         size_t length,
+                         bool fin) override {}
+  void OnStreamFrameData(SpdyStreamId stream_id,
+                         const char* data,
+                         size_t len) override {}
+  void OnStreamEnd(SpdyStreamId stream_id) override {}
+  void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {}
+  void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override {}
+  void OnSetting(SpdySettingsId id, uint32_t value) override {}
+  void OnPing(SpdyPingId unique_id, bool is_ack) override {}
+  void OnSettingsEnd() override {}
+  void OnSettingsAck() override {}
+  void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                SpdyErrorCode error_code) override {}
+  void OnHeaders(SpdyStreamId stream_id,
+                 bool has_priority,
+                 int weight,
+                 SpdyStreamId parent_stream_id,
+                 bool exclusive,
+                 bool fin,
+                 bool end) override {}
+  void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override {}
+  void OnPushPromise(SpdyStreamId stream_id,
+                     SpdyStreamId promised_stream_id,
+                     bool end) override {}
+  void OnContinuation(SpdyStreamId stream_id, bool end) override {}
+  void OnAltSvc(SpdyStreamId stream_id,
+                SpdyStringPiece origin,
+                const SpdyAltSvcWireFormat::AlternativeServiceVector&
+                    altsvc_vector) override {}
+  void OnPriority(SpdyStreamId stream_id,
+                  SpdyStreamId parent_stream_id,
+                  int weight,
+                  bool exclusive) override {}
+  bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override;
+
+  // SpdyFramerDebugVisitorInterface methods:
+  void OnSendCompressedFrame(SpdyStreamId stream_id,
+                             SpdyFrameType type,
+                             size_t payload_len,
+                             size_t frame_len) override {}
+  void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+                                SpdyFrameType type,
+                                size_t frame_len) override {}
+
+  // SpdyHeadersHandlerInterface methods:
+  void OnHeaderBlockStart() override {}
+  void OnHeader(SpdyStringPiece key, SpdyStringPiece value) override {}
+  void OnHeaderBlockEnd(size_t /* uncompressed_header_bytes */,
+                        size_t /* compressed_header_bytes */) override {}
+};
+
+}  // namespace test
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_NO_OP_VISITOR_H_
diff --git a/spdy/core/spdy_pinnable_buffer_piece.cc b/spdy/core/spdy_pinnable_buffer_piece.cc
new file mode 100644
index 0000000..8670962
--- /dev/null
+++ b/spdy/core/spdy_pinnable_buffer_piece.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h"
+
+#include <new>
+
+namespace spdy {
+
+SpdyPinnableBufferPiece::SpdyPinnableBufferPiece()
+    : buffer_(nullptr), length_(0) {}
+
+SpdyPinnableBufferPiece::~SpdyPinnableBufferPiece() = default;
+
+void SpdyPinnableBufferPiece::Pin() {
+  if (!storage_ && buffer_ != nullptr && length_ != 0) {
+    storage_.reset(new char[length_]);
+    std::copy(buffer_, buffer_ + length_, storage_.get());
+    buffer_ = storage_.get();
+  }
+}
+
+void SpdyPinnableBufferPiece::Swap(SpdyPinnableBufferPiece* other) {
+  size_t length = length_;
+  length_ = other->length_;
+  other->length_ = length;
+
+  const char* buffer = buffer_;
+  buffer_ = other->buffer_;
+  other->buffer_ = buffer;
+
+  storage_.swap(other->storage_);
+}
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_pinnable_buffer_piece.h b/spdy/core/spdy_pinnable_buffer_piece.h
new file mode 100644
index 0000000..3032ad7
--- /dev/null
+++ b/spdy/core/spdy_pinnable_buffer_piece.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_PINNABLE_BUFFER_PIECE_H_
+#define QUICHE_SPDY_CORE_SPDY_PINNABLE_BUFFER_PIECE_H_
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+class SpdyPrefixedBufferReader;
+
+// Helper class of SpdyPrefixedBufferReader.
+// Represents a piece of consumed buffer which may (or may not) own its
+// underlying storage. Users may "pin" the buffer at a later time to ensure
+// a SpdyPinnableBufferPiece owns and retains storage of the buffer.
+struct SPDY_EXPORT_PRIVATE SpdyPinnableBufferPiece {
+ public:
+  SpdyPinnableBufferPiece();
+  ~SpdyPinnableBufferPiece();
+
+  const char* buffer() const { return buffer_; }
+
+  explicit operator SpdyStringPiece() const {
+    return SpdyStringPiece(buffer_, length_);
+  }
+
+  // Allocates and copies the buffer to internal storage.
+  void Pin();
+
+  bool IsPinned() const { return storage_ != nullptr; }
+
+  // Swaps buffers, including internal storage, with |other|.
+  void Swap(SpdyPinnableBufferPiece* other);
+
+ private:
+  friend class SpdyPrefixedBufferReader;
+
+  const char* buffer_;
+  size_t length_;
+  // Null iff |buffer_| isn't pinned.
+  std::unique_ptr<char[]> storage_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_PINNABLE_BUFFER_PIECE_H_
diff --git a/spdy/core/spdy_pinnable_buffer_piece_test.cc b/spdy/core/spdy_pinnable_buffer_piece_test.cc
new file mode 100644
index 0000000..d5a6c33
--- /dev/null
+++ b/spdy/core/spdy_pinnable_buffer_piece_test.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+
+namespace spdy {
+
+namespace test {
+
+class SpdyPinnableBufferPieceTest : public ::testing::Test {
+ protected:
+  SpdyPrefixedBufferReader Build(const SpdyString& prefix,
+                                 const SpdyString& suffix) {
+    prefix_ = prefix;
+    suffix_ = suffix;
+    return SpdyPrefixedBufferReader(prefix_.data(), prefix_.length(),
+                                    suffix_.data(), suffix_.length());
+  }
+  SpdyString prefix_, suffix_;
+};
+
+TEST_F(SpdyPinnableBufferPieceTest, Pin) {
+  SpdyPrefixedBufferReader reader = Build("foobar", "");
+  SpdyPinnableBufferPiece piece;
+  EXPECT_TRUE(reader.ReadN(6, &piece));
+
+  // Piece points to underlying prefix storage.
+  EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece));
+  EXPECT_FALSE(piece.IsPinned());
+  EXPECT_EQ(prefix_.data(), piece.buffer());
+
+  piece.Pin();
+
+  // Piece now points to allocated storage.
+  EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece));
+  EXPECT_TRUE(piece.IsPinned());
+  EXPECT_NE(prefix_.data(), piece.buffer());
+
+  // Pinning again has no effect.
+  const char* buffer = piece.buffer();
+  piece.Pin();
+  EXPECT_EQ(buffer, piece.buffer());
+}
+
+TEST_F(SpdyPinnableBufferPieceTest, Swap) {
+  SpdyPrefixedBufferReader reader = Build("foobar", "");
+  SpdyPinnableBufferPiece piece1, piece2;
+  EXPECT_TRUE(reader.ReadN(4, &piece1));
+  EXPECT_TRUE(reader.ReadN(2, &piece2));
+
+  piece1.Pin();
+
+  EXPECT_EQ(SpdyStringPiece("foob"), SpdyStringPiece(piece1));
+  EXPECT_TRUE(piece1.IsPinned());
+  EXPECT_EQ(SpdyStringPiece("ar"), SpdyStringPiece(piece2));
+  EXPECT_FALSE(piece2.IsPinned());
+
+  piece1.Swap(&piece2);
+
+  EXPECT_EQ(SpdyStringPiece("ar"), SpdyStringPiece(piece1));
+  EXPECT_FALSE(piece1.IsPinned());
+  EXPECT_EQ(SpdyStringPiece("foob"), SpdyStringPiece(piece2));
+  EXPECT_TRUE(piece2.IsPinned());
+
+  SpdyPinnableBufferPiece empty;
+  piece2.Swap(&empty);
+
+  EXPECT_EQ(SpdyStringPiece(""), SpdyStringPiece(piece2));
+  EXPECT_FALSE(piece2.IsPinned());
+}
+
+}  // namespace test
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_prefixed_buffer_reader.cc b/spdy/core/spdy_prefixed_buffer_reader.cc
new file mode 100644
index 0000000..9f1fa7f
--- /dev/null
+++ b/spdy/core/spdy_prefixed_buffer_reader.cc
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h"
+
+#include <new>
+
+#include "base/logging.h"
+
+namespace spdy {
+
+SpdyPrefixedBufferReader::SpdyPrefixedBufferReader(const char* prefix,
+                                                   size_t prefix_length,
+                                                   const char* suffix,
+                                                   size_t suffix_length)
+    : prefix_(prefix),
+      suffix_(suffix),
+      prefix_length_(prefix_length),
+      suffix_length_(suffix_length) {}
+
+size_t SpdyPrefixedBufferReader::Available() {
+  return prefix_length_ + suffix_length_;
+}
+
+bool SpdyPrefixedBufferReader::ReadN(size_t count, char* out) {
+  if (Available() < count) {
+    return false;
+  }
+
+  if (prefix_length_ >= count) {
+    // Read is fully satisfied by the prefix.
+    std::copy(prefix_, prefix_ + count, out);
+    prefix_ += count;
+    prefix_length_ -= count;
+    return true;
+  } else if (prefix_length_ != 0) {
+    // Read is partially satisfied by the prefix.
+    out = std::copy(prefix_, prefix_ + prefix_length_, out);
+    count -= prefix_length_;
+    prefix_length_ = 0;
+    // Fallthrough to suffix read.
+  }
+  DCHECK(suffix_length_ >= count);
+  // Read is satisfied by the suffix.
+  std::copy(suffix_, suffix_ + count, out);
+  suffix_ += count;
+  suffix_length_ -= count;
+  return true;
+}
+
+bool SpdyPrefixedBufferReader::ReadN(size_t count,
+                                     SpdyPinnableBufferPiece* out) {
+  if (Available() < count) {
+    return false;
+  }
+
+  out->storage_.reset();
+  out->length_ = count;
+
+  if (prefix_length_ >= count) {
+    // Read is fully satisfied by the prefix.
+    out->buffer_ = prefix_;
+    prefix_ += count;
+    prefix_length_ -= count;
+    return true;
+  } else if (prefix_length_ != 0) {
+    // Read is only partially satisfied by the prefix. We need to allocate
+    // contiguous storage as the read spans the prefix & suffix.
+    out->storage_.reset(new char[count]);
+    out->buffer_ = out->storage_.get();
+    ReadN(count, out->storage_.get());
+    return true;
+  } else {
+    DCHECK(suffix_length_ >= count);
+    // Read is fully satisfied by the suffix.
+    out->buffer_ = suffix_;
+    suffix_ += count;
+    suffix_length_ -= count;
+    return true;
+  }
+}
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_prefixed_buffer_reader.h b/spdy/core/spdy_prefixed_buffer_reader.h
new file mode 100644
index 0000000..2905698
--- /dev/null
+++ b/spdy/core/spdy_prefixed_buffer_reader.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_PREFIXED_BUFFER_READER_H_
+#define QUICHE_SPDY_CORE_SPDY_PREFIXED_BUFFER_READER_H_
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+
+namespace spdy {
+
+// Reader class which simplifies reading contiguously from
+// from a disjoint buffer prefix & suffix.
+class SPDY_EXPORT_PRIVATE SpdyPrefixedBufferReader {
+ public:
+  SpdyPrefixedBufferReader(const char* prefix,
+                           size_t prefix_length,
+                           const char* suffix,
+                           size_t suffix_length);
+
+  // Returns number of bytes available to be read.
+  size_t Available();
+
+  // Reads |count| bytes, copying into |*out|. Returns true on success,
+  // false if not enough bytes were available.
+  bool ReadN(size_t count, char* out);
+
+  // Reads |count| bytes, returned in |*out|. Returns true on success,
+  // false if not enough bytes were available.
+  bool ReadN(size_t count, SpdyPinnableBufferPiece* out);
+
+ private:
+  const char* prefix_;
+  const char* suffix_;
+
+  size_t prefix_length_;
+  size_t suffix_length_;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_PREFIXED_BUFFER_READER_H_
diff --git a/spdy/core/spdy_prefixed_buffer_reader_test.cc b/spdy/core/spdy_prefixed_buffer_reader_test.cc
new file mode 100644
index 0000000..1d052c7
--- /dev/null
+++ b/spdy/core/spdy_prefixed_buffer_reader_test.cc
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+
+using testing::ElementsAreArray;
+
+class SpdyPrefixedBufferReaderTest : public ::testing::Test {
+ protected:
+  SpdyPrefixedBufferReader Build(const SpdyString& prefix,
+                                 const SpdyString& suffix) {
+    prefix_ = prefix;
+    suffix_ = suffix;
+    return SpdyPrefixedBufferReader(prefix_.data(), prefix_.length(),
+                                    suffix_.data(), suffix_.length());
+  }
+  SpdyString prefix_, suffix_;
+};
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadRawFromPrefix) {
+  SpdyPrefixedBufferReader reader = Build("foobar", "");
+  EXPECT_EQ(6u, reader.Available());
+
+  char buffer[] = "123456";
+  EXPECT_FALSE(reader.ReadN(10, buffer));  // Not enough buffer.
+  EXPECT_TRUE(reader.ReadN(6, buffer));
+  EXPECT_THAT(buffer, ElementsAreArray("foobar"));
+  EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadPieceFromPrefix) {
+  SpdyPrefixedBufferReader reader = Build("foobar", "");
+  EXPECT_EQ(6u, reader.Available());
+
+  SpdyPinnableBufferPiece piece;
+  EXPECT_FALSE(reader.ReadN(10, &piece));  // Not enough buffer.
+  EXPECT_TRUE(reader.ReadN(6, &piece));
+  EXPECT_FALSE(piece.IsPinned());
+  EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece));
+  EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadRawFromSuffix) {
+  SpdyPrefixedBufferReader reader = Build("", "foobar");
+  EXPECT_EQ(6u, reader.Available());
+
+  char buffer[] = "123456";
+  EXPECT_FALSE(reader.ReadN(10, buffer));  // Not enough buffer.
+  EXPECT_TRUE(reader.ReadN(6, buffer));
+  EXPECT_THAT(buffer, ElementsAreArray("foobar"));
+  EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadPieceFromSuffix) {
+  SpdyPrefixedBufferReader reader = Build("", "foobar");
+  EXPECT_EQ(6u, reader.Available());
+
+  SpdyPinnableBufferPiece piece;
+  EXPECT_FALSE(reader.ReadN(10, &piece));  // Not enough buffer.
+  EXPECT_TRUE(reader.ReadN(6, &piece));
+  EXPECT_FALSE(piece.IsPinned());
+  EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece));
+  EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadRawSpanning) {
+  SpdyPrefixedBufferReader reader = Build("foob", "ar");
+  EXPECT_EQ(6u, reader.Available());
+
+  char buffer[] = "123456";
+  EXPECT_FALSE(reader.ReadN(10, buffer));  // Not enough buffer.
+  EXPECT_TRUE(reader.ReadN(6, buffer));
+  EXPECT_THAT(buffer, ElementsAreArray("foobar"));
+  EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadPieceSpanning) {
+  SpdyPrefixedBufferReader reader = Build("foob", "ar");
+  EXPECT_EQ(6u, reader.Available());
+
+  SpdyPinnableBufferPiece piece;
+  EXPECT_FALSE(reader.ReadN(10, &piece));  // Not enough buffer.
+  EXPECT_TRUE(reader.ReadN(6, &piece));
+  EXPECT_TRUE(piece.IsPinned());
+  EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece));
+  EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadMixed) {
+  SpdyPrefixedBufferReader reader = Build("abcdef", "hijkl");
+  EXPECT_EQ(11u, reader.Available());
+
+  char buffer[] = "1234";
+  SpdyPinnableBufferPiece piece;
+
+  EXPECT_TRUE(reader.ReadN(3, buffer));
+  EXPECT_THAT(buffer, ElementsAreArray("abc4"));
+  EXPECT_EQ(8u, reader.Available());
+
+  EXPECT_TRUE(reader.ReadN(2, buffer));
+  EXPECT_THAT(buffer, ElementsAreArray("dec4"));
+  EXPECT_EQ(6u, reader.Available());
+
+  EXPECT_TRUE(reader.ReadN(3, &piece));
+  EXPECT_EQ(SpdyStringPiece("fhi"), SpdyStringPiece(piece));
+  EXPECT_TRUE(piece.IsPinned());
+  EXPECT_EQ(3u, reader.Available());
+
+  EXPECT_TRUE(reader.ReadN(2, &piece));
+  EXPECT_EQ(SpdyStringPiece("jk"), SpdyStringPiece(piece));
+  EXPECT_FALSE(piece.IsPinned());
+  EXPECT_EQ(1u, reader.Available());
+
+  EXPECT_TRUE(reader.ReadN(1, buffer));
+  EXPECT_THAT(buffer, ElementsAreArray("lec4"));
+  EXPECT_EQ(0u, reader.Available());
+}
+
+}  // namespace test
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_protocol.cc b/spdy/core/spdy_protocol.cc
new file mode 100644
index 0000000..f51dc6e
--- /dev/null
+++ b/spdy/core/spdy_protocol.cc
@@ -0,0 +1,571 @@
+// Copyright (c) 2012 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/spdy/core/spdy_protocol.h"
+
+#include <ostream>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+const char* const kHttp2ConnectionHeaderPrefix =
+    "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
+
+std::ostream& operator<<(std::ostream& out, SpdyKnownSettingsId id) {
+  return out << static_cast<SpdySettingsId>(id);
+}
+
+std::ostream& operator<<(std::ostream& out, SpdyFrameType frame_type) {
+  return out << SerializeFrameType(frame_type);
+}
+
+SpdyPriority ClampSpdy3Priority(SpdyPriority priority) {
+  if (priority < kV3HighestPriority) {
+    SPDY_BUG << "Invalid priority: " << static_cast<int>(priority);
+    return kV3HighestPriority;
+  }
+  if (priority > kV3LowestPriority) {
+    SPDY_BUG << "Invalid priority: " << static_cast<int>(priority);
+    return kV3LowestPriority;
+  }
+  return priority;
+}
+
+int ClampHttp2Weight(int weight) {
+  if (weight < kHttp2MinStreamWeight) {
+    SPDY_BUG << "Invalid weight: " << weight;
+    return kHttp2MinStreamWeight;
+  }
+  if (weight > kHttp2MaxStreamWeight) {
+    SPDY_BUG << "Invalid weight: " << weight;
+    return kHttp2MaxStreamWeight;
+  }
+  return weight;
+}
+
+int Spdy3PriorityToHttp2Weight(SpdyPriority priority) {
+  priority = ClampSpdy3Priority(priority);
+  const float kSteps = 255.9f / 7.f;
+  return static_cast<int>(kSteps * (7.f - priority)) + 1;
+}
+
+SpdyPriority Http2WeightToSpdy3Priority(int weight) {
+  weight = ClampHttp2Weight(weight);
+  const float kSteps = 255.9f / 7.f;
+  return static_cast<SpdyPriority>(7.f - (weight - 1) / kSteps);
+}
+
+bool IsDefinedFrameType(uint8_t frame_type_field) {
+  return frame_type_field <= SerializeFrameType(SpdyFrameType::MAX_FRAME_TYPE);
+}
+
+SpdyFrameType ParseFrameType(uint8_t frame_type_field) {
+  SPDY_BUG_IF(!IsDefinedFrameType(frame_type_field))
+      << "Frame type not defined: " << static_cast<int>(frame_type_field);
+  return static_cast<SpdyFrameType>(frame_type_field);
+}
+
+uint8_t SerializeFrameType(SpdyFrameType frame_type) {
+  return static_cast<uint8_t>(frame_type);
+}
+
+bool IsValidHTTP2FrameStreamId(SpdyStreamId current_frame_stream_id,
+                               SpdyFrameType frame_type_field) {
+  if (current_frame_stream_id == 0) {
+    switch (frame_type_field) {
+      case SpdyFrameType::DATA:
+      case SpdyFrameType::HEADERS:
+      case SpdyFrameType::PRIORITY:
+      case SpdyFrameType::RST_STREAM:
+      case SpdyFrameType::CONTINUATION:
+      case SpdyFrameType::PUSH_PROMISE:
+        // These frame types must specify a stream
+        return false;
+      default:
+        return true;
+    }
+  } else {
+    switch (frame_type_field) {
+      case SpdyFrameType::GOAWAY:
+      case SpdyFrameType::SETTINGS:
+      case SpdyFrameType::PING:
+        // These frame types must not specify a stream
+        return false;
+      default:
+        return true;
+    }
+  }
+}
+
+const char* FrameTypeToString(SpdyFrameType frame_type) {
+  switch (frame_type) {
+    case SpdyFrameType::DATA:
+      return "DATA";
+    case SpdyFrameType::RST_STREAM:
+      return "RST_STREAM";
+    case SpdyFrameType::SETTINGS:
+      return "SETTINGS";
+    case SpdyFrameType::PING:
+      return "PING";
+    case SpdyFrameType::GOAWAY:
+      return "GOAWAY";
+    case SpdyFrameType::HEADERS:
+      return "HEADERS";
+    case SpdyFrameType::WINDOW_UPDATE:
+      return "WINDOW_UPDATE";
+    case SpdyFrameType::PUSH_PROMISE:
+      return "PUSH_PROMISE";
+    case SpdyFrameType::CONTINUATION:
+      return "CONTINUATION";
+    case SpdyFrameType::PRIORITY:
+      return "PRIORITY";
+    case SpdyFrameType::ALTSVC:
+      return "ALTSVC";
+    case SpdyFrameType::EXTENSION:
+      return "EXTENSION (unspecified)";
+  }
+  return "UNKNOWN_FRAME_TYPE";
+}
+
+bool ParseSettingsId(SpdySettingsId wire_setting_id,
+                     SpdyKnownSettingsId* setting_id) {
+  if (wire_setting_id != SETTINGS_EXPERIMENT_SCHEDULER &&
+      (wire_setting_id < SETTINGS_MIN || wire_setting_id > SETTINGS_MAX)) {
+    return false;
+  }
+
+  *setting_id = static_cast<SpdyKnownSettingsId>(wire_setting_id);
+  // This switch ensures that the casted value is valid. The default case is
+  // explicitly omitted to have compile-time guarantees that new additions to
+  // |SpdyKnownSettingsId| must also be handled here.
+  switch (*setting_id) {
+    case SETTINGS_HEADER_TABLE_SIZE:
+    case SETTINGS_ENABLE_PUSH:
+    case SETTINGS_MAX_CONCURRENT_STREAMS:
+    case SETTINGS_INITIAL_WINDOW_SIZE:
+    case SETTINGS_MAX_FRAME_SIZE:
+    case SETTINGS_MAX_HEADER_LIST_SIZE:
+    case SETTINGS_ENABLE_CONNECT_PROTOCOL:
+    case SETTINGS_EXPERIMENT_SCHEDULER:
+      // FALLTHROUGH_INTENDED
+      return true;
+  }
+  return false;
+}
+
+SpdyString SettingsIdToString(SpdySettingsId id) {
+  SpdyKnownSettingsId known_id;
+  if (!ParseSettingsId(id, &known_id)) {
+    return SpdyStrCat("SETTINGS_UNKNOWN_",
+                      SpdyHexEncodeUInt32AndTrim(uint32_t{id}));
+  }
+
+  switch (known_id) {
+    case SETTINGS_HEADER_TABLE_SIZE:
+      return "SETTINGS_HEADER_TABLE_SIZE";
+    case SETTINGS_ENABLE_PUSH:
+      return "SETTINGS_ENABLE_PUSH";
+    case SETTINGS_MAX_CONCURRENT_STREAMS:
+      return "SETTINGS_MAX_CONCURRENT_STREAMS";
+    case SETTINGS_INITIAL_WINDOW_SIZE:
+      return "SETTINGS_INITIAL_WINDOW_SIZE";
+    case SETTINGS_MAX_FRAME_SIZE:
+      return "SETTINGS_MAX_FRAME_SIZE";
+    case SETTINGS_MAX_HEADER_LIST_SIZE:
+      return "SETTINGS_MAX_HEADER_LIST_SIZE";
+    case SETTINGS_ENABLE_CONNECT_PROTOCOL:
+      return "SETTINGS_ENABLE_CONNECT_PROTOCOL";
+    case SETTINGS_EXPERIMENT_SCHEDULER:
+      return "SETTINGS_EXPERIMENT_SCHEDULER";
+  }
+
+  return SpdyStrCat("SETTINGS_UNKNOWN_",
+                    SpdyHexEncodeUInt32AndTrim(uint32_t{id}));
+}
+
+SpdyErrorCode ParseErrorCode(uint32_t wire_error_code) {
+  if (wire_error_code > ERROR_CODE_MAX) {
+    return ERROR_CODE_INTERNAL_ERROR;
+  }
+
+  return static_cast<SpdyErrorCode>(wire_error_code);
+}
+
+const char* ErrorCodeToString(SpdyErrorCode error_code) {
+  switch (error_code) {
+    case ERROR_CODE_NO_ERROR:
+      return "NO_ERROR";
+    case ERROR_CODE_PROTOCOL_ERROR:
+      return "PROTOCOL_ERROR";
+    case ERROR_CODE_INTERNAL_ERROR:
+      return "INTERNAL_ERROR";
+    case ERROR_CODE_FLOW_CONTROL_ERROR:
+      return "FLOW_CONTROL_ERROR";
+    case ERROR_CODE_SETTINGS_TIMEOUT:
+      return "SETTINGS_TIMEOUT";
+    case ERROR_CODE_STREAM_CLOSED:
+      return "STREAM_CLOSED";
+    case ERROR_CODE_FRAME_SIZE_ERROR:
+      return "FRAME_SIZE_ERROR";
+    case ERROR_CODE_REFUSED_STREAM:
+      return "REFUSED_STREAM";
+    case ERROR_CODE_CANCEL:
+      return "CANCEL";
+    case ERROR_CODE_COMPRESSION_ERROR:
+      return "COMPRESSION_ERROR";
+    case ERROR_CODE_CONNECT_ERROR:
+      return "CONNECT_ERROR";
+    case ERROR_CODE_ENHANCE_YOUR_CALM:
+      return "ENHANCE_YOUR_CALM";
+    case ERROR_CODE_INADEQUATE_SECURITY:
+      return "INADEQUATE_SECURITY";
+    case ERROR_CODE_HTTP_1_1_REQUIRED:
+      return "HTTP_1_1_REQUIRED";
+  }
+  return "UNKNOWN_ERROR_CODE";
+}
+
+size_t GetNumberRequiredContinuationFrames(size_t size) {
+  DCHECK_GT(size, kHttp2MaxControlFrameSendSize);
+  size_t overflow = size - kHttp2MaxControlFrameSendSize;
+  int payload_size =
+      kHttp2MaxControlFrameSendSize - kContinuationFrameMinimumSize;
+  // This is ceiling(overflow/payload_size) using integer arithmetics.
+  return (overflow - 1) / payload_size + 1;
+}
+
+const char* const kHttp2Npn = "h2";
+
+const char* const kHttp2AuthorityHeader = ":authority";
+const char* const kHttp2MethodHeader = ":method";
+const char* const kHttp2PathHeader = ":path";
+const char* const kHttp2SchemeHeader = ":scheme";
+const char* const kHttp2ProtocolHeader = ":protocol";
+
+const char* const kHttp2StatusHeader = ":status";
+
+bool SpdyFrameIR::fin() const {
+  return false;
+}
+
+int SpdyFrameIR::flow_control_window_consumed() const {
+  return 0;
+}
+
+bool SpdyFrameWithFinIR::fin() const {
+  return fin_;
+}
+
+SpdyFrameWithHeaderBlockIR::SpdyFrameWithHeaderBlockIR(
+    SpdyStreamId stream_id,
+    SpdyHeaderBlock header_block)
+    : SpdyFrameWithFinIR(stream_id), header_block_(std::move(header_block)) {}
+
+SpdyFrameWithHeaderBlockIR::~SpdyFrameWithHeaderBlockIR() = default;
+
+SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, SpdyStringPiece data)
+    : SpdyFrameWithFinIR(stream_id),
+      data_(nullptr),
+      data_len_(0),
+      padded_(false),
+      padding_payload_len_(0) {
+  SetDataDeep(data);
+}
+
+SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, const char* data)
+    : SpdyDataIR(stream_id, SpdyStringPiece(data)) {}
+
+SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, SpdyString data)
+    : SpdyFrameWithFinIR(stream_id),
+      data_store_(SpdyMakeUnique<SpdyString>(std::move(data))),
+      data_(data_store_->data()),
+      data_len_(data_store_->size()),
+      padded_(false),
+      padding_payload_len_(0) {}
+
+SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id)
+    : SpdyFrameWithFinIR(stream_id),
+      data_(nullptr),
+      data_len_(0),
+      padded_(false),
+      padding_payload_len_(0) {}
+
+SpdyDataIR::~SpdyDataIR() = default;
+
+void SpdyDataIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitData(*this);
+}
+
+SpdyFrameType SpdyDataIR::frame_type() const {
+  return SpdyFrameType::DATA;
+}
+
+int SpdyDataIR::flow_control_window_consumed() const {
+  return padded_ ? 1 + padding_payload_len_ + data_len_ : data_len_;
+}
+
+size_t SpdyDataIR::size() const {
+  return kFrameHeaderSize +
+         (padded() ? 1 + padding_payload_len() + data_len() : data_len());
+}
+
+SpdyRstStreamIR::SpdyRstStreamIR(SpdyStreamId stream_id,
+                                 SpdyErrorCode error_code)
+    : SpdyFrameIR(stream_id) {
+  set_error_code(error_code);
+}
+
+SpdyRstStreamIR::~SpdyRstStreamIR() = default;
+
+void SpdyRstStreamIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitRstStream(*this);
+}
+
+SpdyFrameType SpdyRstStreamIR::frame_type() const {
+  return SpdyFrameType::RST_STREAM;
+}
+
+size_t SpdyRstStreamIR::size() const {
+  return kRstStreamFrameSize;
+}
+
+SpdySettingsIR::SpdySettingsIR() : is_ack_(false) {}
+
+SpdySettingsIR::~SpdySettingsIR() = default;
+
+void SpdySettingsIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitSettings(*this);
+}
+
+SpdyFrameType SpdySettingsIR::frame_type() const {
+  return SpdyFrameType::SETTINGS;
+}
+
+size_t SpdySettingsIR::size() const {
+  return kFrameHeaderSize + values_.size() * kSettingsOneSettingSize;
+}
+
+void SpdyPingIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitPing(*this);
+}
+
+SpdyFrameType SpdyPingIR::frame_type() const {
+  return SpdyFrameType::PING;
+}
+
+size_t SpdyPingIR::size() const {
+  return kPingFrameSize;
+}
+
+SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+                           SpdyErrorCode error_code,
+                           SpdyStringPiece description)
+    : description_(description) {
+  set_last_good_stream_id(last_good_stream_id);
+  set_error_code(error_code);
+}
+
+SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+                           SpdyErrorCode error_code,
+                           const char* description)
+    : SpdyGoAwayIR(last_good_stream_id,
+                   error_code,
+                   SpdyStringPiece(description)) {}
+
+SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+                           SpdyErrorCode error_code,
+                           SpdyString description)
+    : description_store_(std::move(description)),
+      description_(description_store_) {
+  set_last_good_stream_id(last_good_stream_id);
+  set_error_code(error_code);
+}
+
+SpdyGoAwayIR::~SpdyGoAwayIR() = default;
+
+void SpdyGoAwayIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitGoAway(*this);
+}
+
+SpdyFrameType SpdyGoAwayIR::frame_type() const {
+  return SpdyFrameType::GOAWAY;
+}
+
+size_t SpdyGoAwayIR::size() const {
+  return kGoawayFrameMinimumSize + description_.size();
+}
+
+SpdyContinuationIR::SpdyContinuationIR(SpdyStreamId stream_id)
+    : SpdyFrameIR(stream_id), end_headers_(false) {
+  encoding_ = SpdyMakeUnique<SpdyString>();
+}
+
+SpdyContinuationIR::~SpdyContinuationIR() = default;
+
+void SpdyContinuationIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitContinuation(*this);
+}
+
+SpdyFrameType SpdyContinuationIR::frame_type() const {
+  return SpdyFrameType::CONTINUATION;
+}
+
+size_t SpdyContinuationIR::size() const {
+  // We don't need to get the size of CONTINUATION frame directly. It is
+  // calculated in HEADERS or PUSH_PROMISE frame.
+  DLOG(WARNING) << "Shouldn't not call size() for CONTINUATION frame.";
+  return 0;
+}
+
+void SpdyHeadersIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitHeaders(*this);
+}
+
+SpdyFrameType SpdyHeadersIR::frame_type() const {
+  return SpdyFrameType::HEADERS;
+}
+
+size_t SpdyHeadersIR::size() const {
+  size_t size = kHeadersFrameMinimumSize;
+
+  if (padded_) {
+    // Padding field length.
+    size += 1;
+    size += padding_payload_len_;
+  }
+
+  if (has_priority_) {
+    size += 5;
+  }
+
+  // Assume no hpack encoding is applied.
+  size += header_block().TotalBytesUsed() +
+          header_block().size() * kPerHeaderHpackOverhead;
+  if (size > kHttp2MaxControlFrameSendSize) {
+    size += GetNumberRequiredContinuationFrames(size) *
+            kContinuationFrameMinimumSize;
+  }
+  return size;
+}
+
+void SpdyWindowUpdateIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitWindowUpdate(*this);
+}
+
+SpdyFrameType SpdyWindowUpdateIR::frame_type() const {
+  return SpdyFrameType::WINDOW_UPDATE;
+}
+
+size_t SpdyWindowUpdateIR::size() const {
+  return kWindowUpdateFrameSize;
+}
+
+void SpdyPushPromiseIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitPushPromise(*this);
+}
+
+SpdyFrameType SpdyPushPromiseIR::frame_type() const {
+  return SpdyFrameType::PUSH_PROMISE;
+}
+
+size_t SpdyPushPromiseIR::size() const {
+  size_t size = kPushPromiseFrameMinimumSize;
+
+  if (padded_) {
+    // Padding length field.
+    size += 1;
+    size += padding_payload_len_;
+  }
+
+  size += header_block().TotalBytesUsed();
+  if (size > kHttp2MaxControlFrameSendSize) {
+    size += GetNumberRequiredContinuationFrames(size) *
+            kContinuationFrameMinimumSize;
+  }
+  return size;
+}
+
+SpdyAltSvcIR::SpdyAltSvcIR(SpdyStreamId stream_id) : SpdyFrameIR(stream_id) {}
+
+SpdyAltSvcIR::~SpdyAltSvcIR() = default;
+
+void SpdyAltSvcIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitAltSvc(*this);
+}
+
+SpdyFrameType SpdyAltSvcIR::frame_type() const {
+  return SpdyFrameType::ALTSVC;
+}
+
+size_t SpdyAltSvcIR::size() const {
+  size_t size = kGetAltSvcFrameMinimumSize;
+  size += origin_.length();
+  // TODO(yasong): estimates the size without serializing the vector.
+  SpdyString str =
+      SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector_);
+  size += str.size();
+  return size;
+}
+
+void SpdyPriorityIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitPriority(*this);
+}
+
+SpdyFrameType SpdyPriorityIR::frame_type() const {
+  return SpdyFrameType::PRIORITY;
+}
+
+size_t SpdyPriorityIR::size() const {
+  return kPriorityFrameSize;
+}
+
+void SpdyUnknownIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitUnknown(*this);
+}
+
+SpdyFrameType SpdyUnknownIR::frame_type() const {
+  return static_cast<SpdyFrameType>(type());
+}
+
+size_t SpdyUnknownIR::size() const {
+  return kFrameHeaderSize + payload_.size();
+}
+
+int SpdyUnknownIR::flow_control_window_consumed() const {
+  if (frame_type() == SpdyFrameType::DATA) {
+    return payload_.size();
+  } else {
+    return 0;
+  }
+}
+
+// Wire size of pad length field.
+const size_t kPadLengthFieldSize = 1;
+
+size_t GetHeaderFrameSizeSansBlock(const SpdyHeadersIR& header_ir) {
+  size_t min_size = kFrameHeaderSize;
+  if (header_ir.padded()) {
+    min_size += kPadLengthFieldSize;
+    min_size += header_ir.padding_payload_len();
+  }
+  if (header_ir.has_priority()) {
+    min_size += 5;
+  }
+  return min_size;
+}
+
+size_t GetPushPromiseFrameSizeSansBlock(
+    const SpdyPushPromiseIR& push_promise_ir) {
+  size_t min_size = kPushPromiseFrameMinimumSize;
+  if (push_promise_ir.padded()) {
+    min_size += kPadLengthFieldSize;
+    min_size += push_promise_ir.padding_payload_len();
+  }
+  return min_size;
+}
+
+}  // namespace spdy
diff --git a/spdy/core/spdy_protocol.h b/spdy/core/spdy_protocol.h
new file mode 100644
index 0000000..d300d10
--- /dev/null
+++ b/spdy/core/spdy_protocol.h
@@ -0,0 +1,1066 @@
+// Copyright (c) 2012 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.
+
+// This file contains some protocol structures for use with SPDY 3 and HTTP 2
+// The SPDY 3 spec can be found at:
+// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3
+
+#ifndef QUICHE_SPDY_CORE_SPDY_PROTOCOL_H_
+#define QUICHE_SPDY_CORE_SPDY_PROTOCOL_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <iosfwd>
+#include <limits>
+#include <map>
+#include <memory>
+#include <new>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+// A stream ID is a 31-bit entity.
+using SpdyStreamId = uint32_t;
+
+// A SETTINGS ID is a 16-bit entity.
+using SpdySettingsId = uint16_t;
+
+// Specifies the stream ID used to denote the current session (for
+// flow control).
+const SpdyStreamId kSessionFlowControlStreamId = 0;
+
+// 0 is not a valid stream ID for any other purpose than flow control.
+const SpdyStreamId kInvalidStreamId = 0;
+
+// Max stream id.
+const SpdyStreamId kMaxStreamId = 0x7fffffff;
+
+// The maximum possible frame payload size allowed by the spec.
+const uint32_t kSpdyMaxFrameSizeLimit = (1 << 24) - 1;
+
+// The initial value for the maximum frame payload size as per the spec. This is
+// the maximum control frame size we accept.
+const uint32_t kHttp2DefaultFramePayloadLimit = 1 << 14;
+
+// The maximum size of the control frames that we send, including the size of
+// the header. This limit is arbitrary. We can enforce it here or at the
+// application layer. We chose the framing layer, but this can be changed (or
+// removed) if necessary later down the line.
+const size_t kHttp2MaxControlFrameSendSize = kHttp2DefaultFramePayloadLimit - 1;
+
+// Number of octets in the frame header.
+const size_t kFrameHeaderSize = 9;
+
+// The initial value for the maximum frame payload size as per the spec. This is
+// the maximum control frame size we accept.
+const uint32_t kHttp2DefaultFrameSizeLimit =
+    kHttp2DefaultFramePayloadLimit + kFrameHeaderSize;
+
+// The initial value for the maximum size of the header list, "unlimited" (max
+// unsigned 32-bit int) as per the spec.
+const uint32_t kSpdyInitialHeaderListSizeLimit = 0xFFFFFFFF;
+
+// Maximum window size for a Spdy stream or session.
+const int32_t kSpdyMaximumWindowSize = 0x7FFFFFFF;  // Max signed 32bit int
+
+// Maximum padding size in octets for one DATA or HEADERS or PUSH_PROMISE frame.
+const int32_t kPaddingSizePerFrame = 256;
+
+// The HTTP/2 connection preface, which must be the first bytes sent by the
+// client upon starting an HTTP/2 connection, and which must be followed by a
+// SETTINGS frame.  Note that even though |kHttp2ConnectionHeaderPrefix| is
+// defined as a string literal with a null terminator, the actual connection
+// preface is only the first |kHttp2ConnectionHeaderPrefixSize| bytes, which
+// excludes the null terminator.
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2ConnectionHeaderPrefix;
+const int kHttp2ConnectionHeaderPrefixSize = 24;
+
+// Wire values for HTTP2 frame types.
+enum class SpdyFrameType : uint8_t {
+  DATA = 0x00,
+  HEADERS = 0x01,
+  PRIORITY = 0x02,
+  RST_STREAM = 0x03,
+  SETTINGS = 0x04,
+  PUSH_PROMISE = 0x05,
+  PING = 0x06,
+  GOAWAY = 0x07,
+  WINDOW_UPDATE = 0x08,
+  CONTINUATION = 0x09,
+  // ALTSVC is a public extension.
+  ALTSVC = 0x0a,
+  MAX_FRAME_TYPE = ALTSVC,
+  // The specific value of EXTENSION is meaningless; it is a placeholder used
+  // within SpdyFramer's state machine when handling unknown frames via an
+  // extension API.
+  // TODO(birenroy): Remove the fake EXTENSION value from the SpdyFrameType
+  // enum.
+  EXTENSION = 0xff
+};
+
+// Flags on data packets.
+enum SpdyDataFlags {
+  DATA_FLAG_NONE = 0x00,
+  DATA_FLAG_FIN = 0x01,
+  DATA_FLAG_PADDED = 0x08,
+};
+
+// Flags on control packets
+enum SpdyControlFlags {
+  CONTROL_FLAG_NONE = 0x00,
+  CONTROL_FLAG_FIN = 0x01,
+};
+
+enum SpdyPingFlags {
+  PING_FLAG_ACK = 0x01,
+};
+
+// Used by HEADERS, PUSH_PROMISE, and CONTINUATION.
+enum SpdyHeadersFlags {
+  HEADERS_FLAG_END_HEADERS = 0x04,
+  HEADERS_FLAG_PADDED = 0x08,
+  HEADERS_FLAG_PRIORITY = 0x20,
+};
+
+enum SpdyPushPromiseFlags {
+  PUSH_PROMISE_FLAG_END_PUSH_PROMISE = 0x04,
+  PUSH_PROMISE_FLAG_PADDED = 0x08,
+};
+
+enum Http2SettingsControlFlags {
+  SETTINGS_FLAG_ACK = 0x01,
+};
+
+// Wire values of HTTP/2 setting identifiers.
+enum SpdyKnownSettingsId : SpdySettingsId {
+  // HPACK header table maximum size.
+  SETTINGS_HEADER_TABLE_SIZE = 0x1,
+  SETTINGS_MIN = SETTINGS_HEADER_TABLE_SIZE,
+  // Whether or not server push (PUSH_PROMISE) is enabled.
+  SETTINGS_ENABLE_PUSH = 0x2,
+  // The maximum number of simultaneous live streams in each direction.
+  SETTINGS_MAX_CONCURRENT_STREAMS = 0x3,
+  // Initial window size in bytes
+  SETTINGS_INITIAL_WINDOW_SIZE = 0x4,
+  // The size of the largest frame payload that a receiver is willing to accept.
+  SETTINGS_MAX_FRAME_SIZE = 0x5,
+  // The maximum size of header list that the sender is prepared to accept.
+  SETTINGS_MAX_HEADER_LIST_SIZE = 0x6,
+  // Enable Websockets over HTTP/2, see
+  // https://tools.ietf.org/html/draft-ietf-httpbis-h2-websockets-00.
+  SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x8,
+  SETTINGS_MAX = SETTINGS_ENABLE_CONNECT_PROTOCOL,
+  // Experimental setting used to configure an alternative write scheduler.
+  SETTINGS_EXPERIMENT_SCHEDULER = 0xFF45,
+};
+
+// This explicit operator is needed, otherwise compiler finds
+// overloaded operator to be ambiguous.
+SPDY_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                             SpdyKnownSettingsId id);
+
+// This operator is needed, because SpdyFrameType is an enum class,
+// therefore implicit conversion to underlying integer type is not allowed.
+SPDY_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                             SpdyFrameType frame_type);
+
+using SettingsMap = std::map<SpdySettingsId, uint32_t>;
+
+// HTTP/2 error codes, RFC 7540 Section 7.
+enum SpdyErrorCode : uint32_t {
+  ERROR_CODE_NO_ERROR = 0x0,
+  ERROR_CODE_PROTOCOL_ERROR = 0x1,
+  ERROR_CODE_INTERNAL_ERROR = 0x2,
+  ERROR_CODE_FLOW_CONTROL_ERROR = 0x3,
+  ERROR_CODE_SETTINGS_TIMEOUT = 0x4,
+  ERROR_CODE_STREAM_CLOSED = 0x5,
+  ERROR_CODE_FRAME_SIZE_ERROR = 0x6,
+  ERROR_CODE_REFUSED_STREAM = 0x7,
+  ERROR_CODE_CANCEL = 0x8,
+  ERROR_CODE_COMPRESSION_ERROR = 0x9,
+  ERROR_CODE_CONNECT_ERROR = 0xa,
+  ERROR_CODE_ENHANCE_YOUR_CALM = 0xb,
+  ERROR_CODE_INADEQUATE_SECURITY = 0xc,
+  ERROR_CODE_HTTP_1_1_REQUIRED = 0xd,
+  ERROR_CODE_MAX = ERROR_CODE_HTTP_1_1_REQUIRED
+};
+
+// A SPDY priority is a number between 0 and 7 (inclusive).
+typedef uint8_t SpdyPriority;
+
+// Lowest and Highest here refer to SPDY priorities as described in
+// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-2.3.3-Stream-priority
+const SpdyPriority kV3HighestPriority = 0;
+const SpdyPriority kV3LowestPriority = 7;
+
+// Returns SPDY 3.x priority value clamped to the valid range of [0, 7].
+SPDY_EXPORT_PRIVATE SpdyPriority ClampSpdy3Priority(SpdyPriority priority);
+
+// HTTP/2 stream weights are integers in range [1, 256], as specified in RFC
+// 7540 section 5.3.2. Default stream weight is defined in section 5.3.5.
+const int kHttp2MinStreamWeight = 1;
+const int kHttp2MaxStreamWeight = 256;
+const int kHttp2DefaultStreamWeight = 16;
+
+// Returns HTTP/2 weight clamped to the valid range of [1, 256].
+SPDY_EXPORT_PRIVATE int ClampHttp2Weight(int weight);
+
+// Maps SPDY 3.x priority value in range [0, 7] to HTTP/2 weight value in range
+// [1, 256], where priority 0 (i.e. highest precedence) corresponds to maximum
+// weight 256 and priority 7 (lowest precedence) corresponds to minimum weight
+// 1.
+SPDY_EXPORT_PRIVATE int Spdy3PriorityToHttp2Weight(SpdyPriority priority);
+
+// Maps HTTP/2 weight value in range [1, 256] to SPDY 3.x priority value in
+// range [0, 7], where minimum weight 1 corresponds to priority 7 (lowest
+// precedence) and maximum weight 256 corresponds to priority 0 (highest
+// precedence).
+SPDY_EXPORT_PRIVATE SpdyPriority Http2WeightToSpdy3Priority(int weight);
+
+// Reserved ID for root stream of HTTP/2 stream dependency tree, as specified
+// in RFC 7540 section 5.3.1.
+const unsigned int kHttp2RootStreamId = 0;
+
+typedef uint64_t SpdyPingId;
+
+// Returns true if a given on-the-wire enumeration of a frame type is defined
+// in a standardized HTTP/2 specification, false otherwise.
+SPDY_EXPORT_PRIVATE bool IsDefinedFrameType(uint8_t frame_type_field);
+
+// Parses a frame type from an on-the-wire enumeration.
+// Behavior is undefined for invalid frame type fields; consumers should first
+// use IsValidFrameType() to verify validity of frame type fields.
+SPDY_EXPORT_PRIVATE SpdyFrameType ParseFrameType(uint8_t frame_type_field);
+
+// Serializes a frame type to the on-the-wire value.
+SPDY_EXPORT_PRIVATE uint8_t SerializeFrameType(SpdyFrameType frame_type);
+
+// (HTTP/2) All standard frame types except WINDOW_UPDATE are
+// (stream-specific xor connection-level). Returns false iff we know
+// the given frame type does not align with the given streamID.
+SPDY_EXPORT_PRIVATE bool IsValidHTTP2FrameStreamId(
+    SpdyStreamId current_frame_stream_id,
+    SpdyFrameType frame_type_field);
+
+// Serialize |frame_type| to string for logging/debugging.
+const char* FrameTypeToString(SpdyFrameType frame_type);
+
+// If |wire_setting_id| is the on-the-wire representation of a defined SETTINGS
+// parameter, parse it to |*setting_id| and return true.
+SPDY_EXPORT_PRIVATE bool ParseSettingsId(SpdySettingsId wire_setting_id,
+                                         SpdyKnownSettingsId* setting_id);
+
+// Returns a string representation of the |id| for logging/debugging. Returns
+// the |id| prefixed with "SETTINGS_UNKNOWN_" for unknown SETTINGS IDs. To parse
+// the |id| into a SpdyKnownSettingsId (if applicable), use ParseSettingsId().
+SPDY_EXPORT_PRIVATE SpdyString SettingsIdToString(SpdySettingsId id);
+
+// Parse |wire_error_code| to a SpdyErrorCode.
+// Treat unrecognized error codes as INTERNAL_ERROR
+// as recommended by the HTTP/2 specification.
+SPDY_EXPORT_PRIVATE SpdyErrorCode ParseErrorCode(uint32_t wire_error_code);
+
+// Serialize RST_STREAM or GOAWAY frame error code to string
+// for logging/debugging.
+const char* ErrorCodeToString(SpdyErrorCode error_code);
+
+// Minimum size of a frame, in octets.
+const size_t kFrameMinimumSize = kFrameHeaderSize;
+
+// Minimum frame size for variable size frame types (includes mandatory fields),
+// frame size for fixed size frames, in octets.
+
+const size_t kDataFrameMinimumSize = kFrameHeaderSize;
+const size_t kHeadersFrameMinimumSize = kFrameHeaderSize;
+// PRIORITY frame has stream_dependency (4 octets) and weight (1 octet) fields.
+const size_t kPriorityFrameSize = kFrameHeaderSize + 5;
+// RST_STREAM frame has error_code (4 octets) field.
+const size_t kRstStreamFrameSize = kFrameHeaderSize + 4;
+const size_t kSettingsFrameMinimumSize = kFrameHeaderSize;
+const size_t kSettingsOneSettingSize =
+    sizeof(uint32_t) + sizeof(SpdySettingsId);
+// PUSH_PROMISE frame has promised_stream_id (4 octet) field.
+const size_t kPushPromiseFrameMinimumSize = kFrameHeaderSize + 4;
+// PING frame has opaque_bytes (8 octet) field.
+const size_t kPingFrameSize = kFrameHeaderSize + 8;
+// GOAWAY frame has last_stream_id (4 octet) and error_code (4 octet) fields.
+const size_t kGoawayFrameMinimumSize = kFrameHeaderSize + 8;
+// WINDOW_UPDATE frame has window_size_increment (4 octet) field.
+const size_t kWindowUpdateFrameSize = kFrameHeaderSize + 4;
+const size_t kContinuationFrameMinimumSize = kFrameHeaderSize;
+// ALTSVC frame has origin_len (2 octets) field.
+const size_t kGetAltSvcFrameMinimumSize = kFrameHeaderSize + 2;
+
+// Maximum possible configurable size of a frame in octets.
+const size_t kMaxFrameSizeLimit = kSpdyMaxFrameSizeLimit + kFrameHeaderSize;
+// Size of a header block size field.
+const size_t kSizeOfSizeField = sizeof(uint32_t);
+// Per-header overhead for block size accounting in bytes.
+const size_t kPerHeaderOverhead = 32;
+// Initial window size for a stream in bytes.
+const int32_t kInitialStreamWindowSize = 64 * 1024 - 1;
+// Initial window size for a session in bytes.
+const int32_t kInitialSessionWindowSize = 64 * 1024 - 1;
+// The NPN string for HTTP2, "h2".
+extern const char* const kHttp2Npn;
+// An estimate size of the HPACK overhead for each header field. 1 bytes for
+// indexed literal, 1 bytes for key literal and length encoding, and 2 bytes for
+// value literal and length encoding.
+const size_t kPerHeaderHpackOverhead = 4;
+
+// Names of pseudo-headers defined for HTTP/2 requests.
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2AuthorityHeader;
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2MethodHeader;
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2PathHeader;
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2SchemeHeader;
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2ProtocolHeader;
+
+// Name of pseudo-header defined for HTTP/2 responses.
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2StatusHeader;
+
+SPDY_EXPORT_PRIVATE size_t GetNumberRequiredContinuationFrames(size_t size);
+
+// Variant type (i.e. tagged union) that is either a SPDY 3.x priority value,
+// or else an HTTP/2 stream dependency tuple {parent stream ID, weight,
+// exclusive bit}. Templated to allow for use by QUIC code; SPDY and HTTP/2
+// code should use the concrete type instantiation SpdyStreamPrecedence.
+template <typename StreamIdType>
+class StreamPrecedence {
+ public:
+  // Constructs instance that is a SPDY 3.x priority. Clamps priority value to
+  // the valid range [0, 7].
+  explicit StreamPrecedence(SpdyPriority priority)
+      : is_spdy3_priority_(true),
+        spdy3_priority_(ClampSpdy3Priority(priority)) {}
+
+  // Constructs instance that is an HTTP/2 stream weight, parent stream ID, and
+  // exclusive bit. Clamps stream weight to the valid range [1, 256].
+  StreamPrecedence(StreamIdType parent_id, int weight, bool is_exclusive)
+      : is_spdy3_priority_(false),
+        http2_stream_dependency_{parent_id, ClampHttp2Weight(weight),
+                                 is_exclusive} {}
+
+  // Intentionally copyable, to support pass by value.
+  StreamPrecedence(const StreamPrecedence& other) = default;
+  StreamPrecedence& operator=(const StreamPrecedence& other) = default;
+
+  // Returns true if this instance is a SPDY 3.x priority, or false if this
+  // instance is an HTTP/2 stream dependency.
+  bool is_spdy3_priority() const { return is_spdy3_priority_; }
+
+  // Returns SPDY 3.x priority value. If |is_spdy3_priority()| is true, this is
+  // the value provided at construction, clamped to the legal priority
+  // range. Otherwise, it is the HTTP/2 stream weight mapped to a SPDY 3.x
+  // priority value, where minimum weight 1 corresponds to priority 7 (lowest
+  // precedence) and maximum weight 256 corresponds to priority 0 (highest
+  // precedence).
+  SpdyPriority spdy3_priority() const {
+    return is_spdy3_priority_
+               ? spdy3_priority_
+               : Http2WeightToSpdy3Priority(http2_stream_dependency_.weight);
+  }
+
+  // Returns HTTP/2 parent stream ID. If |is_spdy3_priority()| is false, this is
+  // the value provided at construction, otherwise it is |kHttp2RootStreamId|.
+  StreamIdType parent_id() const {
+    return is_spdy3_priority_ ? kHttp2RootStreamId
+                              : http2_stream_dependency_.parent_id;
+  }
+
+  // Returns HTTP/2 stream weight. If |is_spdy3_priority()| is false, this is
+  // the value provided at construction, clamped to the legal weight
+  // range. Otherwise, it is the SPDY 3.x priority value mapped to an HTTP/2
+  // stream weight, where priority 0 (i.e. highest precedence) corresponds to
+  // maximum weight 256 and priority 7 (lowest precedence) corresponds to
+  // minimum weight 1.
+  int weight() const {
+    return is_spdy3_priority_ ? Spdy3PriorityToHttp2Weight(spdy3_priority_)
+                              : http2_stream_dependency_.weight;
+  }
+
+  // Returns HTTP/2 parent stream exclusivity. If |is_spdy3_priority()| is
+  // false, this is the value provided at construction, otherwise it is false.
+  bool is_exclusive() const {
+    return !is_spdy3_priority_ && http2_stream_dependency_.is_exclusive;
+  }
+
+  // Facilitates test assertions.
+  bool operator==(const StreamPrecedence& other) const {
+    if (is_spdy3_priority()) {
+      return other.is_spdy3_priority() &&
+             (spdy3_priority() == other.spdy3_priority());
+    } else {
+      return !other.is_spdy3_priority() && (parent_id() == other.parent_id()) &&
+             (weight() == other.weight()) &&
+             (is_exclusive() == other.is_exclusive());
+    }
+  }
+
+  bool operator!=(const StreamPrecedence& other) const {
+    return !(*this == other);
+  }
+
+ private:
+  struct Http2StreamDependency {
+    StreamIdType parent_id;
+    int weight;
+    bool is_exclusive;
+  };
+
+  bool is_spdy3_priority_;
+  union {
+    SpdyPriority spdy3_priority_;
+    Http2StreamDependency http2_stream_dependency_;
+  };
+};
+
+typedef StreamPrecedence<SpdyStreamId> SpdyStreamPrecedence;
+
+class SpdyFrameVisitor;
+
+// Intermediate representation for HTTP2 frames.
+class SPDY_EXPORT_PRIVATE SpdyFrameIR {
+ public:
+  virtual ~SpdyFrameIR() {}
+
+  virtual void Visit(SpdyFrameVisitor* visitor) const = 0;
+  virtual SpdyFrameType frame_type() const = 0;
+  SpdyStreamId stream_id() const { return stream_id_; }
+  virtual bool fin() const;
+  // Returns an estimate of the size of the serialized frame, without applying
+  // compression. May not be exact.
+  virtual size_t size() const = 0;
+
+  // Returns the number of bytes of flow control window that would be consumed
+  // by this frame if written to the wire.
+  virtual int flow_control_window_consumed() const;
+
+ protected:
+  SpdyFrameIR() : stream_id_(0) {}
+  explicit SpdyFrameIR(SpdyStreamId stream_id) : stream_id_(stream_id) {}
+  SpdyFrameIR(const SpdyFrameIR&) = delete;
+  SpdyFrameIR& operator=(const SpdyFrameIR&) = delete;
+
+ private:
+  SpdyStreamId stream_id_;
+};
+
+// Abstract class intended to be inherited by IRs that have the option of a FIN
+// flag.
+class SPDY_EXPORT_PRIVATE SpdyFrameWithFinIR : public SpdyFrameIR {
+ public:
+  ~SpdyFrameWithFinIR() override {}
+  bool fin() const override;
+  void set_fin(bool fin) { fin_ = fin; }
+
+ protected:
+  explicit SpdyFrameWithFinIR(SpdyStreamId stream_id)
+      : SpdyFrameIR(stream_id), fin_(false) {}
+  SpdyFrameWithFinIR(const SpdyFrameWithFinIR&) = delete;
+  SpdyFrameWithFinIR& operator=(const SpdyFrameWithFinIR&) = delete;
+
+ private:
+  bool fin_;
+};
+
+// Abstract class intended to be inherited by IRs that contain a header
+// block. Implies SpdyFrameWithFinIR.
+class SPDY_EXPORT_PRIVATE SpdyFrameWithHeaderBlockIR
+    : public SpdyFrameWithFinIR {
+ public:
+  ~SpdyFrameWithHeaderBlockIR() override;
+
+  const SpdyHeaderBlock& header_block() const { return header_block_; }
+  void set_header_block(SpdyHeaderBlock header_block) {
+    // Deep copy.
+    header_block_ = std::move(header_block);
+  }
+  void SetHeader(SpdyStringPiece name, SpdyStringPiece value) {
+    header_block_[name] = value;
+  }
+
+ protected:
+  SpdyFrameWithHeaderBlockIR(SpdyStreamId stream_id,
+                             SpdyHeaderBlock header_block);
+  SpdyFrameWithHeaderBlockIR(const SpdyFrameWithHeaderBlockIR&) = delete;
+  SpdyFrameWithHeaderBlockIR& operator=(const SpdyFrameWithHeaderBlockIR&) =
+      delete;
+
+ private:
+  SpdyHeaderBlock header_block_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyDataIR : public SpdyFrameWithFinIR {
+ public:
+  // Performs a deep copy on data.
+  SpdyDataIR(SpdyStreamId stream_id, SpdyStringPiece data);
+
+  // Performs a deep copy on data.
+  SpdyDataIR(SpdyStreamId stream_id, const char* data);
+
+  // Moves data into data_store_. Makes a copy if passed a non-movable string.
+  SpdyDataIR(SpdyStreamId stream_id, SpdyString data);
+
+  // Use in conjunction with SetDataShallow() for shallow-copy on data.
+  explicit SpdyDataIR(SpdyStreamId stream_id);
+  SpdyDataIR(const SpdyDataIR&) = delete;
+  SpdyDataIR& operator=(const SpdyDataIR&) = delete;
+
+  ~SpdyDataIR() override;
+
+  const char* data() const { return data_; }
+  size_t data_len() const { return data_len_; }
+
+  bool padded() const { return padded_; }
+
+  int padding_payload_len() const { return padding_payload_len_; }
+
+  void set_padding_len(int padding_len) {
+    DCHECK_GT(padding_len, 0);
+    DCHECK_LE(padding_len, kPaddingSizePerFrame);
+    padded_ = true;
+    // The pad field takes one octet on the wire.
+    padding_payload_len_ = padding_len - 1;
+  }
+
+  // Deep-copy of data (keep private copy).
+  void SetDataDeep(SpdyStringPiece data) {
+    data_store_ = SpdyMakeUnique<SpdyString>(data.data(), data.size());
+    data_ = data_store_->data();
+    data_len_ = data.size();
+  }
+
+  // Shallow-copy of data (do not keep private copy).
+  void SetDataShallow(SpdyStringPiece data) {
+    data_store_.reset();
+    data_ = data.data();
+    data_len_ = data.size();
+  }
+
+  // Use this method if we don't have a contiguous buffer and only
+  // need a length.
+  void SetDataShallow(size_t len) {
+    data_store_.reset();
+    data_ = nullptr;
+    data_len_ = len;
+  }
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  int flow_control_window_consumed() const override;
+
+  size_t size() const override;
+
+ private:
+  // Used to store data that this SpdyDataIR should own.
+  std::unique_ptr<SpdyString> data_store_;
+  const char* data_;
+  size_t data_len_;
+
+  bool padded_;
+  // padding_payload_len_ = desired padding length - len(padding length field).
+  int padding_payload_len_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyRstStreamIR : public SpdyFrameIR {
+ public:
+  SpdyRstStreamIR(SpdyStreamId stream_id, SpdyErrorCode error_code);
+  SpdyRstStreamIR(const SpdyRstStreamIR&) = delete;
+  SpdyRstStreamIR& operator=(const SpdyRstStreamIR&) = delete;
+
+  ~SpdyRstStreamIR() override;
+
+  SpdyErrorCode error_code() const { return error_code_; }
+  void set_error_code(SpdyErrorCode error_code) { error_code_ = error_code; }
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  size_t size() const override;
+
+ private:
+  SpdyErrorCode error_code_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdySettingsIR : public SpdyFrameIR {
+ public:
+  SpdySettingsIR();
+  SpdySettingsIR(const SpdySettingsIR&) = delete;
+  SpdySettingsIR& operator=(const SpdySettingsIR&) = delete;
+  ~SpdySettingsIR() override;
+
+  // Overwrites as appropriate.
+  const SettingsMap& values() const { return values_; }
+  void AddSetting(SpdySettingsId id, int32_t value) { values_[id] = value; }
+
+  bool is_ack() const { return is_ack_; }
+  void set_is_ack(bool is_ack) { is_ack_ = is_ack; }
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  size_t size() const override;
+
+ private:
+  SettingsMap values_;
+  bool is_ack_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyPingIR : public SpdyFrameIR {
+ public:
+  explicit SpdyPingIR(SpdyPingId id) : id_(id), is_ack_(false) {}
+  SpdyPingIR(const SpdyPingIR&) = delete;
+  SpdyPingIR& operator=(const SpdyPingIR&) = delete;
+  SpdyPingId id() const { return id_; }
+
+  bool is_ack() const { return is_ack_; }
+  void set_is_ack(bool is_ack) { is_ack_ = is_ack; }
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  size_t size() const override;
+
+ private:
+  SpdyPingId id_;
+  bool is_ack_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyGoAwayIR : public SpdyFrameIR {
+ public:
+  // References description, doesn't copy it, so description must outlast
+  // this SpdyGoAwayIR.
+  SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+               SpdyErrorCode error_code,
+               SpdyStringPiece description);
+
+  // References description, doesn't copy it, so description must outlast
+  // this SpdyGoAwayIR.
+  SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+               SpdyErrorCode error_code,
+               const char* description);
+
+  // Moves description into description_store_, so caller doesn't need to
+  // keep description live after constructing this SpdyGoAwayIR.
+  SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+               SpdyErrorCode error_code,
+               SpdyString description);
+  SpdyGoAwayIR(const SpdyGoAwayIR&) = delete;
+  SpdyGoAwayIR& operator=(const SpdyGoAwayIR&) = delete;
+
+  ~SpdyGoAwayIR() override;
+
+  SpdyStreamId last_good_stream_id() const { return last_good_stream_id_; }
+  void set_last_good_stream_id(SpdyStreamId last_good_stream_id) {
+    DCHECK_EQ(0u, last_good_stream_id & ~kStreamIdMask);
+    last_good_stream_id_ = last_good_stream_id;
+  }
+  SpdyErrorCode error_code() const { return error_code_; }
+  void set_error_code(SpdyErrorCode error_code) {
+    // TODO(hkhalil): Check valid ranges of error_code?
+    error_code_ = error_code;
+  }
+
+  const SpdyStringPiece& description() const { return description_; }
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  size_t size() const override;
+
+ private:
+  SpdyStreamId last_good_stream_id_;
+  SpdyErrorCode error_code_;
+  const SpdyString description_store_;
+  const SpdyStringPiece description_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyHeadersIR : public SpdyFrameWithHeaderBlockIR {
+ public:
+  explicit SpdyHeadersIR(SpdyStreamId stream_id)
+      : SpdyHeadersIR(stream_id, SpdyHeaderBlock()) {}
+  SpdyHeadersIR(SpdyStreamId stream_id, SpdyHeaderBlock header_block)
+      : SpdyFrameWithHeaderBlockIR(stream_id, std::move(header_block)) {}
+  SpdyHeadersIR(const SpdyHeadersIR&) = delete;
+  SpdyHeadersIR& operator=(const SpdyHeadersIR&) = delete;
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  size_t size() const override;
+
+  bool has_priority() const { return has_priority_; }
+  void set_has_priority(bool has_priority) { has_priority_ = has_priority; }
+  int weight() const { return weight_; }
+  void set_weight(int weight) { weight_ = weight; }
+  SpdyStreamId parent_stream_id() const { return parent_stream_id_; }
+  void set_parent_stream_id(SpdyStreamId id) { parent_stream_id_ = id; }
+  bool exclusive() const { return exclusive_; }
+  void set_exclusive(bool exclusive) { exclusive_ = exclusive; }
+  bool padded() const { return padded_; }
+  int padding_payload_len() const { return padding_payload_len_; }
+  void set_padding_len(int padding_len) {
+    DCHECK_GT(padding_len, 0);
+    DCHECK_LE(padding_len, kPaddingSizePerFrame);
+    padded_ = true;
+    // The pad field takes one octet on the wire.
+    padding_payload_len_ = padding_len - 1;
+  }
+
+ private:
+  bool has_priority_ = false;
+  int weight_ = kHttp2DefaultStreamWeight;
+  SpdyStreamId parent_stream_id_ = 0;
+  bool exclusive_ = false;
+  bool padded_ = false;
+  int padding_payload_len_ = 0;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyWindowUpdateIR : public SpdyFrameIR {
+ public:
+  SpdyWindowUpdateIR(SpdyStreamId stream_id, int32_t delta)
+      : SpdyFrameIR(stream_id) {
+    set_delta(delta);
+  }
+  SpdyWindowUpdateIR(const SpdyWindowUpdateIR&) = delete;
+  SpdyWindowUpdateIR& operator=(const SpdyWindowUpdateIR&) = delete;
+
+  int32_t delta() const { return delta_; }
+  void set_delta(int32_t delta) {
+    DCHECK_LE(0, delta);
+    DCHECK_LE(delta, kSpdyMaximumWindowSize);
+    delta_ = delta;
+  }
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  size_t size() const override;
+
+ private:
+  int32_t delta_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyPushPromiseIR
+    : public SpdyFrameWithHeaderBlockIR {
+ public:
+  SpdyPushPromiseIR(SpdyStreamId stream_id, SpdyStreamId promised_stream_id)
+      : SpdyPushPromiseIR(stream_id, promised_stream_id, SpdyHeaderBlock()) {}
+  SpdyPushPromiseIR(SpdyStreamId stream_id,
+                    SpdyStreamId promised_stream_id,
+                    SpdyHeaderBlock header_block)
+      : SpdyFrameWithHeaderBlockIR(stream_id, std::move(header_block)),
+        promised_stream_id_(promised_stream_id),
+        padded_(false),
+        padding_payload_len_(0) {}
+  SpdyPushPromiseIR(const SpdyPushPromiseIR&) = delete;
+  SpdyPushPromiseIR& operator=(const SpdyPushPromiseIR&) = delete;
+  SpdyStreamId promised_stream_id() const { return promised_stream_id_; }
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  size_t size() const override;
+
+  bool padded() const { return padded_; }
+  int padding_payload_len() const { return padding_payload_len_; }
+  void set_padding_len(int padding_len) {
+    DCHECK_GT(padding_len, 0);
+    DCHECK_LE(padding_len, kPaddingSizePerFrame);
+    padded_ = true;
+    // The pad field takes one octet on the wire.
+    padding_payload_len_ = padding_len - 1;
+  }
+
+ private:
+  SpdyStreamId promised_stream_id_;
+
+  bool padded_;
+  int padding_payload_len_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyContinuationIR : public SpdyFrameIR {
+ public:
+  explicit SpdyContinuationIR(SpdyStreamId stream_id);
+  SpdyContinuationIR(const SpdyContinuationIR&) = delete;
+  SpdyContinuationIR& operator=(const SpdyContinuationIR&) = delete;
+  ~SpdyContinuationIR() override;
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  bool end_headers() const { return end_headers_; }
+  void set_end_headers(bool end_headers) { end_headers_ = end_headers; }
+  const SpdyString& encoding() const { return *encoding_; }
+  void take_encoding(std::unique_ptr<SpdyString> encoding) {
+    encoding_ = std::move(encoding);
+  }
+  size_t size() const override;
+
+ private:
+  std::unique_ptr<SpdyString> encoding_;
+  bool end_headers_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyAltSvcIR : public SpdyFrameIR {
+ public:
+  explicit SpdyAltSvcIR(SpdyStreamId stream_id);
+  SpdyAltSvcIR(const SpdyAltSvcIR&) = delete;
+  SpdyAltSvcIR& operator=(const SpdyAltSvcIR&) = delete;
+  ~SpdyAltSvcIR() override;
+
+  SpdyString origin() const { return origin_; }
+  const SpdyAltSvcWireFormat::AlternativeServiceVector& altsvc_vector() const {
+    return altsvc_vector_;
+  }
+
+  void set_origin(SpdyString origin) { origin_ = std::move(origin); }
+  void add_altsvc(const SpdyAltSvcWireFormat::AlternativeService& altsvc) {
+    altsvc_vector_.push_back(altsvc);
+  }
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  size_t size() const override;
+
+ private:
+  SpdyString origin_;
+  SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyPriorityIR : public SpdyFrameIR {
+ public:
+  SpdyPriorityIR(SpdyStreamId stream_id,
+                 SpdyStreamId parent_stream_id,
+                 int weight,
+                 bool exclusive)
+      : SpdyFrameIR(stream_id),
+        parent_stream_id_(parent_stream_id),
+        weight_(weight),
+        exclusive_(exclusive) {}
+  SpdyPriorityIR(const SpdyPriorityIR&) = delete;
+  SpdyPriorityIR& operator=(const SpdyPriorityIR&) = delete;
+  SpdyStreamId parent_stream_id() const { return parent_stream_id_; }
+  int weight() const { return weight_; }
+  bool exclusive() const { return exclusive_; }
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  size_t size() const override;
+
+ private:
+  SpdyStreamId parent_stream_id_;
+  int weight_;
+  bool exclusive_;
+};
+
+// Represents a frame of unrecognized type.
+class SPDY_EXPORT_PRIVATE SpdyUnknownIR : public SpdyFrameIR {
+ public:
+  SpdyUnknownIR(SpdyStreamId stream_id,
+                uint8_t type,
+                uint8_t flags,
+                SpdyString payload)
+      : SpdyFrameIR(stream_id),
+        type_(type),
+        flags_(flags),
+        length_(payload.size()),
+        payload_(std::move(payload)) {}
+  SpdyUnknownIR(const SpdyUnknownIR&) = delete;
+  SpdyUnknownIR& operator=(const SpdyUnknownIR&) = delete;
+  uint8_t type() const { return type_; }
+  uint8_t flags() const { return flags_; }
+  size_t length() const { return length_; }
+  const SpdyString& payload() const { return payload_; }
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  int flow_control_window_consumed() const override;
+
+  size_t size() const override;
+
+ protected:
+  // Allows subclasses to overwrite the default payload length.
+  void set_length(size_t length) { length_ = length; }
+
+ private:
+  uint8_t type_;
+  uint8_t flags_;
+  size_t length_;
+  const SpdyString payload_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdySerializedFrame {
+ public:
+  SpdySerializedFrame()
+      : frame_(const_cast<char*>("")), size_(0), owns_buffer_(false) {}
+
+  // Create a valid SpdySerializedFrame using a pre-created buffer.
+  // If |owns_buffer| is true, this class takes ownership of the buffer and will
+  // delete it on cleanup.  The buffer must have been created using new char[].
+  // If |owns_buffer| is false, the caller retains ownership of the buffer and
+  // is responsible for making sure the buffer outlives this frame.  In other
+  // words, this class does NOT create a copy of the buffer.
+  SpdySerializedFrame(char* data, size_t size, bool owns_buffer)
+      : frame_(data), size_(size), owns_buffer_(owns_buffer) {}
+
+  SpdySerializedFrame(SpdySerializedFrame&& other)
+      : frame_(other.frame_),
+        size_(other.size_),
+        owns_buffer_(other.owns_buffer_) {
+    // |other| is no longer responsible for the buffer.
+    other.owns_buffer_ = false;
+  }
+  SpdySerializedFrame(const SpdySerializedFrame&) = delete;
+  SpdySerializedFrame& operator=(const SpdySerializedFrame&) = delete;
+
+  SpdySerializedFrame& operator=(SpdySerializedFrame&& other) {
+    // Free buffer if necessary.
+    if (owns_buffer_) {
+      delete[] frame_;
+    }
+    // Take over |other|.
+    frame_ = other.frame_;
+    size_ = other.size_;
+    owns_buffer_ = other.owns_buffer_;
+    // |other| is no longer responsible for the buffer.
+    other.owns_buffer_ = false;
+    return *this;
+  }
+
+  ~SpdySerializedFrame() {
+    if (owns_buffer_) {
+      delete[] frame_;
+    }
+  }
+
+  // Provides access to the frame bytes, which is a buffer containing the frame
+  // packed as expected for sending over the wire.
+  char* data() const { return frame_; }
+
+  // Returns the actual size of the underlying buffer.
+  size_t size() const { return size_; }
+
+  // Returns a buffer containing the contents of the frame, of which the caller
+  // takes ownership, and clears this SpdySerializedFrame.
+  char* ReleaseBuffer() {
+    char* buffer;
+    if (owns_buffer_) {
+      // If the buffer is owned, relinquish ownership to the caller.
+      buffer = frame_;
+      owns_buffer_ = false;
+    } else {
+      // Otherwise, we need to make a copy to give to the caller.
+      buffer = new char[size_];
+      memcpy(buffer, frame_, size_);
+    }
+    *this = SpdySerializedFrame();
+    return buffer;
+  }
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const { return owns_buffer_ ? size_ : 0; }
+
+ protected:
+  char* frame_;
+
+ private:
+  size_t size_;
+  bool owns_buffer_;
+};
+
+// This interface is for classes that want to process SpdyFrameIRs without
+// having to know what type they are.  An instance of this interface can be
+// passed to a SpdyFrameIR's Visit method, and the appropriate type-specific
+// method of this class will be called.
+class SPDY_EXPORT_PRIVATE SpdyFrameVisitor {
+ public:
+  virtual void VisitRstStream(const SpdyRstStreamIR& rst_stream) = 0;
+  virtual void VisitSettings(const SpdySettingsIR& settings) = 0;
+  virtual void VisitPing(const SpdyPingIR& ping) = 0;
+  virtual void VisitGoAway(const SpdyGoAwayIR& goaway) = 0;
+  virtual void VisitHeaders(const SpdyHeadersIR& headers) = 0;
+  virtual void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) = 0;
+  virtual void VisitPushPromise(const SpdyPushPromiseIR& push_promise) = 0;
+  virtual void VisitContinuation(const SpdyContinuationIR& continuation) = 0;
+  virtual void VisitAltSvc(const SpdyAltSvcIR& altsvc) = 0;
+  virtual void VisitPriority(const SpdyPriorityIR& priority) = 0;
+  virtual void VisitData(const SpdyDataIR& data) = 0;
+  virtual void VisitUnknown(const SpdyUnknownIR& unknown) {
+    // TODO(birenroy): make abstract.
+  }
+
+ protected:
+  SpdyFrameVisitor() {}
+  SpdyFrameVisitor(const SpdyFrameVisitor&) = delete;
+  SpdyFrameVisitor& operator=(const SpdyFrameVisitor&) = delete;
+  virtual ~SpdyFrameVisitor() {}
+};
+
+// Optionally, and in addition to SpdyFramerVisitorInterface, a class supporting
+// SpdyFramerDebugVisitorInterface may be used in conjunction with SpdyFramer in
+// order to extract debug/internal information about the SpdyFramer as it
+// operates.
+//
+// Most HTTP2 implementations need not bother with this interface at all.
+class SPDY_EXPORT_PRIVATE SpdyFramerDebugVisitorInterface {
+ public:
+  virtual ~SpdyFramerDebugVisitorInterface() {}
+
+  // Called after compressing a frame with a payload of
+  // a list of name-value pairs.
+  // |payload_len| is the uncompressed payload size.
+  // |frame_len| is the compressed frame size.
+  virtual void OnSendCompressedFrame(SpdyStreamId stream_id,
+                                     SpdyFrameType type,
+                                     size_t payload_len,
+                                     size_t frame_len) {}
+
+  // Called when a frame containing a compressed payload of
+  // name-value pairs is received.
+  // |frame_len| is the compressed frame size.
+  virtual void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+                                        SpdyFrameType type,
+                                        size_t frame_len) {}
+};
+
+// Calculates the number of bytes required to serialize a SpdyHeadersIR, not
+// including the bytes to be used for the encoded header set.
+size_t GetHeaderFrameSizeSansBlock(const SpdyHeadersIR& header_ir);
+
+// Calculates the number of bytes required to serialize a SpdyPushPromiseIR,
+// not including the bytes to be used for the encoded header set.
+size_t GetPushPromiseFrameSizeSansBlock(
+    const SpdyPushPromiseIR& push_promise_ir);
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_PROTOCOL_H_
diff --git a/spdy/core/spdy_protocol_test.cc b/spdy/core/spdy_protocol_test.cc
new file mode 100644
index 0000000..09a56cb
--- /dev/null
+++ b/spdy/core/spdy_protocol_test.cc
@@ -0,0 +1,270 @@
+// Copyright (c) 2012 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/spdy/core/spdy_protocol.h"
+
+#include <iostream>
+#include <limits>
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+
+namespace spdy {
+
+std::ostream& operator<<(std::ostream& os,
+                         const SpdyStreamPrecedence precedence) {
+  if (precedence.is_spdy3_priority()) {
+    os << "SpdyStreamPrecedence[spdy3_priority=" << precedence.spdy3_priority()
+       << "]";
+  } else {
+    os << "SpdyStreamPrecedence[parent_id=" << precedence.parent_id()
+       << ", weight=" << precedence.weight()
+       << ", is_exclusive=" << precedence.is_exclusive() << "]";
+  }
+  return os;
+}
+
+namespace test {
+
+TEST(SpdyProtocolTest, ClampSpdy3Priority) {
+  EXPECT_SPDY_BUG(EXPECT_EQ(7, ClampSpdy3Priority(8)), "Invalid priority: 8");
+  EXPECT_EQ(kV3LowestPriority, ClampSpdy3Priority(kV3LowestPriority));
+  EXPECT_EQ(kV3HighestPriority, ClampSpdy3Priority(kV3HighestPriority));
+}
+
+TEST(SpdyProtocolTest, ClampHttp2Weight) {
+  EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MinStreamWeight, ClampHttp2Weight(0)),
+                  "Invalid weight: 0");
+  EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MaxStreamWeight, ClampHttp2Weight(300)),
+                  "Invalid weight: 300");
+  EXPECT_EQ(kHttp2MinStreamWeight, ClampHttp2Weight(kHttp2MinStreamWeight));
+  EXPECT_EQ(kHttp2MaxStreamWeight, ClampHttp2Weight(kHttp2MaxStreamWeight));
+}
+
+TEST(SpdyProtocolTest, Spdy3PriorityToHttp2Weight) {
+  EXPECT_EQ(256, Spdy3PriorityToHttp2Weight(0));
+  EXPECT_EQ(220, Spdy3PriorityToHttp2Weight(1));
+  EXPECT_EQ(183, Spdy3PriorityToHttp2Weight(2));
+  EXPECT_EQ(147, Spdy3PriorityToHttp2Weight(3));
+  EXPECT_EQ(110, Spdy3PriorityToHttp2Weight(4));
+  EXPECT_EQ(74, Spdy3PriorityToHttp2Weight(5));
+  EXPECT_EQ(37, Spdy3PriorityToHttp2Weight(6));
+  EXPECT_EQ(1, Spdy3PriorityToHttp2Weight(7));
+}
+
+TEST(SpdyProtocolTest, Http2WeightToSpdy3Priority) {
+  EXPECT_EQ(0u, Http2WeightToSpdy3Priority(256));
+  EXPECT_EQ(0u, Http2WeightToSpdy3Priority(221));
+  EXPECT_EQ(1u, Http2WeightToSpdy3Priority(220));
+  EXPECT_EQ(1u, Http2WeightToSpdy3Priority(184));
+  EXPECT_EQ(2u, Http2WeightToSpdy3Priority(183));
+  EXPECT_EQ(2u, Http2WeightToSpdy3Priority(148));
+  EXPECT_EQ(3u, Http2WeightToSpdy3Priority(147));
+  EXPECT_EQ(3u, Http2WeightToSpdy3Priority(111));
+  EXPECT_EQ(4u, Http2WeightToSpdy3Priority(110));
+  EXPECT_EQ(4u, Http2WeightToSpdy3Priority(75));
+  EXPECT_EQ(5u, Http2WeightToSpdy3Priority(74));
+  EXPECT_EQ(5u, Http2WeightToSpdy3Priority(38));
+  EXPECT_EQ(6u, Http2WeightToSpdy3Priority(37));
+  EXPECT_EQ(6u, Http2WeightToSpdy3Priority(2));
+  EXPECT_EQ(7u, Http2WeightToSpdy3Priority(1));
+}
+
+TEST(SpdyProtocolTest, IsValidHTTP2FrameStreamId) {
+  // Stream-specific frames must have non-zero stream ids
+  EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::DATA));
+  EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::DATA));
+  EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::HEADERS));
+  EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::HEADERS));
+  EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::PRIORITY));
+  EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::PRIORITY));
+  EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::RST_STREAM));
+  EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::RST_STREAM));
+  EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::CONTINUATION));
+  EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::CONTINUATION));
+  EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::PUSH_PROMISE));
+  EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::PUSH_PROMISE));
+
+  // Connection-level frames must have zero stream ids
+  EXPECT_FALSE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::GOAWAY));
+  EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::GOAWAY));
+  EXPECT_FALSE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::SETTINGS));
+  EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::SETTINGS));
+  EXPECT_FALSE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::PING));
+  EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::PING));
+
+  // Frames that are neither stream-specific nor connection-level
+  // should not have their stream id declared invalid
+  EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::WINDOW_UPDATE));
+  EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::WINDOW_UPDATE));
+}
+
+TEST(SpdyProtocolTest, ParseSettingsId) {
+  SpdyKnownSettingsId setting_id;
+  EXPECT_FALSE(ParseSettingsId(0, &setting_id));
+  EXPECT_TRUE(ParseSettingsId(1, &setting_id));
+  EXPECT_EQ(SETTINGS_HEADER_TABLE_SIZE, setting_id);
+  EXPECT_TRUE(ParseSettingsId(2, &setting_id));
+  EXPECT_EQ(SETTINGS_ENABLE_PUSH, setting_id);
+  EXPECT_TRUE(ParseSettingsId(3, &setting_id));
+  EXPECT_EQ(SETTINGS_MAX_CONCURRENT_STREAMS, setting_id);
+  EXPECT_TRUE(ParseSettingsId(4, &setting_id));
+  EXPECT_EQ(SETTINGS_INITIAL_WINDOW_SIZE, setting_id);
+  EXPECT_TRUE(ParseSettingsId(5, &setting_id));
+  EXPECT_EQ(SETTINGS_MAX_FRAME_SIZE, setting_id);
+  EXPECT_TRUE(ParseSettingsId(6, &setting_id));
+  EXPECT_EQ(SETTINGS_MAX_HEADER_LIST_SIZE, setting_id);
+  EXPECT_FALSE(ParseSettingsId(7, &setting_id));
+  EXPECT_TRUE(ParseSettingsId(8, &setting_id));
+  EXPECT_EQ(SETTINGS_ENABLE_CONNECT_PROTOCOL, setting_id);
+  EXPECT_FALSE(ParseSettingsId(9, &setting_id));
+  EXPECT_FALSE(ParseSettingsId(0xFF44, &setting_id));
+  EXPECT_TRUE(ParseSettingsId(0xFF45, &setting_id));
+  EXPECT_EQ(SETTINGS_EXPERIMENT_SCHEDULER, setting_id);
+  EXPECT_FALSE(ParseSettingsId(0xFF46, &setting_id));
+}
+
+TEST(SpdyProtocolTest, SettingsIdToString) {
+  struct {
+    SpdySettingsId setting_id;
+    const SpdyString expected_string;
+  } test_cases[] = {
+      {0, "SETTINGS_UNKNOWN_0"},
+      {SETTINGS_HEADER_TABLE_SIZE, "SETTINGS_HEADER_TABLE_SIZE"},
+      {SETTINGS_ENABLE_PUSH, "SETTINGS_ENABLE_PUSH"},
+      {SETTINGS_MAX_CONCURRENT_STREAMS, "SETTINGS_MAX_CONCURRENT_STREAMS"},
+      {SETTINGS_INITIAL_WINDOW_SIZE, "SETTINGS_INITIAL_WINDOW_SIZE"},
+      {SETTINGS_MAX_FRAME_SIZE, "SETTINGS_MAX_FRAME_SIZE"},
+      {SETTINGS_MAX_HEADER_LIST_SIZE, "SETTINGS_MAX_HEADER_LIST_SIZE"},
+      {7, "SETTINGS_UNKNOWN_7"},
+      {SETTINGS_ENABLE_CONNECT_PROTOCOL, "SETTINGS_ENABLE_CONNECT_PROTOCOL"},
+      {9, "SETTINGS_UNKNOWN_9"},
+      {0xFF44, "SETTINGS_UNKNOWN_ff44"},
+      {0xFF45, "SETTINGS_EXPERIMENT_SCHEDULER"},
+      {0xFF46, "SETTINGS_UNKNOWN_ff46"}};
+  for (auto test_case : test_cases) {
+    EXPECT_EQ(test_case.expected_string,
+              SettingsIdToString(test_case.setting_id));
+  }
+}
+
+TEST(SpdyStreamPrecedenceTest, Basic) {
+  SpdyStreamPrecedence spdy3_prec(2);
+  EXPECT_TRUE(spdy3_prec.is_spdy3_priority());
+  EXPECT_EQ(2, spdy3_prec.spdy3_priority());
+  EXPECT_EQ(kHttp2RootStreamId, spdy3_prec.parent_id());
+  EXPECT_EQ(Spdy3PriorityToHttp2Weight(2), spdy3_prec.weight());
+  EXPECT_FALSE(spdy3_prec.is_exclusive());
+
+  for (bool is_exclusive : {true, false}) {
+    SpdyStreamPrecedence h2_prec(7, 123, is_exclusive);
+    EXPECT_FALSE(h2_prec.is_spdy3_priority());
+    EXPECT_EQ(Http2WeightToSpdy3Priority(123), h2_prec.spdy3_priority());
+    EXPECT_EQ(7u, h2_prec.parent_id());
+    EXPECT_EQ(123, h2_prec.weight());
+    EXPECT_EQ(is_exclusive, h2_prec.is_exclusive());
+  }
+}
+
+TEST(SpdyStreamPrecedenceTest, Clamping) {
+  EXPECT_SPDY_BUG(EXPECT_EQ(7, SpdyStreamPrecedence(8).spdy3_priority()),
+                  "Invalid priority: 8");
+  EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MinStreamWeight,
+                            SpdyStreamPrecedence(3, 0, false).weight()),
+                  "Invalid weight: 0");
+  EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MaxStreamWeight,
+                            SpdyStreamPrecedence(3, 300, false).weight()),
+                  "Invalid weight: 300");
+}
+
+TEST(SpdyStreamPrecedenceTest, Copying) {
+  SpdyStreamPrecedence prec1(3);
+  SpdyStreamPrecedence copy1(prec1);
+  EXPECT_TRUE(copy1.is_spdy3_priority());
+  EXPECT_EQ(3, copy1.spdy3_priority());
+
+  SpdyStreamPrecedence prec2(4, 5, true);
+  SpdyStreamPrecedence copy2(prec2);
+  EXPECT_FALSE(copy2.is_spdy3_priority());
+  EXPECT_EQ(4u, copy2.parent_id());
+  EXPECT_EQ(5, copy2.weight());
+  EXPECT_TRUE(copy2.is_exclusive());
+
+  copy1 = prec2;
+  EXPECT_FALSE(copy1.is_spdy3_priority());
+  EXPECT_EQ(4u, copy1.parent_id());
+  EXPECT_EQ(5, copy1.weight());
+  EXPECT_TRUE(copy1.is_exclusive());
+
+  copy2 = prec1;
+  EXPECT_TRUE(copy2.is_spdy3_priority());
+  EXPECT_EQ(3, copy2.spdy3_priority());
+}
+
+TEST(SpdyStreamPrecedenceTest, Equals) {
+  EXPECT_EQ(SpdyStreamPrecedence(3), SpdyStreamPrecedence(3));
+  EXPECT_NE(SpdyStreamPrecedence(3), SpdyStreamPrecedence(4));
+
+  EXPECT_EQ(SpdyStreamPrecedence(1, 2, false),
+            SpdyStreamPrecedence(1, 2, false));
+  EXPECT_NE(SpdyStreamPrecedence(1, 2, false),
+            SpdyStreamPrecedence(2, 2, false));
+  EXPECT_NE(SpdyStreamPrecedence(1, 2, false),
+            SpdyStreamPrecedence(1, 3, false));
+  EXPECT_NE(SpdyStreamPrecedence(1, 2, false),
+            SpdyStreamPrecedence(1, 2, true));
+
+  SpdyStreamPrecedence spdy3_prec(3);
+  SpdyStreamPrecedence h2_prec(spdy3_prec.parent_id(), spdy3_prec.weight(),
+                               spdy3_prec.is_exclusive());
+  EXPECT_NE(spdy3_prec, h2_prec);
+}
+
+TEST(SpdyDataIRTest, Construct) {
+  // Confirm that it makes a string of zero length from a
+  // SpdyStringPiece(nullptr).
+  SpdyStringPiece s1;
+  SpdyDataIR d1(/* stream_id = */ 1, s1);
+  EXPECT_EQ(0u, d1.data_len());
+  EXPECT_NE(nullptr, d1.data());
+
+  // Confirms makes a copy of char array.
+  const char s2[] = "something";
+  SpdyDataIR d2(/* stream_id = */ 2, s2);
+  EXPECT_EQ(SpdyStringPiece(d2.data(), d2.data_len()), s2);
+  EXPECT_NE(SpdyStringPiece(d1.data(), d1.data_len()), s2);
+  EXPECT_EQ((int)d1.data_len(), d1.flow_control_window_consumed());
+
+  // Confirm copies a const string.
+  const SpdyString foo = "foo";
+  SpdyDataIR d3(/* stream_id = */ 3, foo);
+  EXPECT_EQ(foo, d3.data());
+  EXPECT_EQ((int)d3.data_len(), d3.flow_control_window_consumed());
+
+  // Confirm copies a non-const string.
+  SpdyString bar = "bar";
+  SpdyDataIR d4(/* stream_id = */ 4, bar);
+  EXPECT_EQ("bar", bar);
+  EXPECT_EQ("bar", SpdyStringPiece(d4.data(), d4.data_len()));
+
+  // Confirm moves an rvalue reference. Note that the test string "baz" is too
+  // short to trigger the move optimization, and instead a copy occurs.
+  SpdyString baz = "the quick brown fox";
+  SpdyDataIR d5(/* stream_id = */ 5, std::move(baz));
+  EXPECT_EQ("", baz);
+  EXPECT_EQ(SpdyStringPiece(d5.data(), d5.data_len()), "the quick brown fox");
+
+  // Confirms makes a copy of string literal.
+  SpdyDataIR d7(/* stream_id = */ 7, "something else");
+  EXPECT_EQ(SpdyStringPiece(d7.data(), d7.data_len()), "something else");
+
+  SpdyDataIR d8(/* stream_id = */ 8, "shawarma");
+  d8.set_padding_len(20);
+  EXPECT_EQ(28, d8.flow_control_window_consumed());
+}
+
+}  // namespace test
+}  // namespace spdy
diff --git a/spdy/core/spdy_protocol_test_utils.cc b/spdy/core/spdy_protocol_test_utils.cc
new file mode 100644
index 0000000..14d3385
--- /dev/null
+++ b/spdy/core/spdy_protocol_test_utils.cc
@@ -0,0 +1,155 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h"
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+namespace test {
+
+// TODO(jamessynge): Where it makes sense in these functions, it would be nice
+// to make use of the existing gMock matchers here, instead of rolling our own.
+
+::testing::AssertionResult VerifySpdyFrameWithHeaderBlockIREquals(
+    const SpdyFrameWithHeaderBlockIR& expected,
+    const SpdyFrameWithHeaderBlockIR& actual) {
+  VLOG(1) << "VerifySpdyFrameWithHeaderBlockIREquals";
+  VERIFY_TRUE(actual.header_block() == expected.header_block());
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyAltSvcIR& expected,
+                                                   const SpdyAltSvcIR& actual) {
+  VERIFY_EQ(expected.stream_id(), actual.stream_id());
+  VERIFY_EQ(expected.origin(), actual.origin());
+  VERIFY_THAT(actual.altsvc_vector(),
+              ::testing::ContainerEq(expected.altsvc_vector()));
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdyContinuationIR& expected,
+    const SpdyContinuationIR& actual) {
+  return ::testing::AssertionFailure()
+         << "VerifySpdyFrameIREquals SpdyContinuationIR not yet implemented";
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyDataIR& expected,
+                                                   const SpdyDataIR& actual) {
+  VLOG(1) << "VerifySpdyFrameIREquals SpdyDataIR";
+  VERIFY_EQ(expected.stream_id(), actual.stream_id());
+  VERIFY_EQ(expected.fin(), actual.fin());
+  VERIFY_EQ(expected.data_len(), actual.data_len());
+  if (expected.data() == nullptr) {
+    VERIFY_EQ(nullptr, actual.data());
+  } else {
+    VERIFY_EQ(SpdyStringPiece(expected.data(), expected.data_len()),
+              SpdyStringPiece(actual.data(), actual.data_len()));
+  }
+  VERIFY_SUCCESS(VerifySpdyFrameWithPaddingIREquals(expected, actual));
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyGoAwayIR& expected,
+                                                   const SpdyGoAwayIR& actual) {
+  VLOG(1) << "VerifySpdyFrameIREquals SpdyGoAwayIR";
+  VERIFY_EQ(expected.last_good_stream_id(), actual.last_good_stream_id());
+  VERIFY_EQ(expected.error_code(), actual.error_code());
+  VERIFY_EQ(expected.description(), actual.description());
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdyHeadersIR& expected,
+    const SpdyHeadersIR& actual) {
+  VLOG(1) << "VerifySpdyFrameIREquals SpdyHeadersIR";
+  VERIFY_EQ(expected.stream_id(), actual.stream_id());
+  VERIFY_EQ(expected.fin(), actual.fin());
+  VERIFY_SUCCESS(VerifySpdyFrameWithHeaderBlockIREquals(expected, actual));
+  VERIFY_EQ(expected.has_priority(), actual.has_priority());
+  if (expected.has_priority()) {
+    VERIFY_SUCCESS(VerifySpdyFrameWithPriorityIREquals(expected, actual));
+  }
+  VERIFY_SUCCESS(VerifySpdyFrameWithPaddingIREquals(expected, actual));
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyPingIR& expected,
+                                                   const SpdyPingIR& actual) {
+  VLOG(1) << "VerifySpdyFrameIREquals SpdyPingIR";
+  VERIFY_EQ(expected.id(), actual.id());
+  VERIFY_EQ(expected.is_ack(), actual.is_ack());
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdyPriorityIR& expected,
+    const SpdyPriorityIR& actual) {
+  VLOG(1) << "VerifySpdyFrameIREquals SpdyPriorityIR";
+  VERIFY_EQ(expected.stream_id(), actual.stream_id());
+  VERIFY_SUCCESS(VerifySpdyFrameWithPriorityIREquals(expected, actual));
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdyPushPromiseIR& expected,
+    const SpdyPushPromiseIR& actual) {
+  VLOG(1) << "VerifySpdyFrameIREquals SpdyPushPromiseIR";
+  VERIFY_EQ(expected.stream_id(), actual.stream_id());
+  VERIFY_SUCCESS(VerifySpdyFrameWithPaddingIREquals(expected, actual));
+  VERIFY_EQ(expected.promised_stream_id(), actual.promised_stream_id());
+  VERIFY_SUCCESS(VerifySpdyFrameWithHeaderBlockIREquals(expected, actual));
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdyRstStreamIR& expected,
+    const SpdyRstStreamIR& actual) {
+  VLOG(1) << "VerifySpdyFrameIREquals SpdyRstStreamIR";
+  VERIFY_EQ(expected.stream_id(), actual.stream_id());
+  VERIFY_EQ(expected.error_code(), actual.error_code());
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdySettingsIR& expected,
+    const SpdySettingsIR& actual) {
+  VLOG(1) << "VerifySpdyFrameIREquals SpdySettingsIR";
+  // Note, ignoring non-HTTP/2 fields such as clear_settings.
+  VERIFY_EQ(expected.is_ack(), actual.is_ack());
+
+  // Note, the following doesn't work because there isn't a comparator and
+  // formatter for SpdySettingsIR::Value. Fixable if we cared.
+  //
+  //   VERIFY_THAT(actual.values(), ::testing::ContainerEq(actual.values()));
+
+  VERIFY_EQ(expected.values().size(), actual.values().size());
+  for (const auto& entry : expected.values()) {
+    const auto& param = entry.first;
+    auto actual_itr = actual.values().find(param);
+    VERIFY_TRUE(!(actual_itr == actual.values().end()))
+        << "actual doesn't contain param: " << param;
+    uint32_t expected_value = entry.second;
+    uint32_t actual_value = actual_itr->second;
+    VERIFY_EQ(expected_value, actual_value)
+        << "Values don't match for parameter: " << param;
+  }
+
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdyWindowUpdateIR& expected,
+    const SpdyWindowUpdateIR& actual) {
+  VLOG(1) << "VerifySpdyFrameIREquals SpdyWindowUpdateIR";
+  VERIFY_EQ(expected.stream_id(), actual.stream_id());
+  VERIFY_EQ(expected.delta(), actual.delta());
+  return ::testing::AssertionSuccess();
+}
+
+}  // namespace test
+}  // namespace spdy
diff --git a/spdy/core/spdy_protocol_test_utils.h b/spdy/core/spdy_protocol_test_utils.h
new file mode 100644
index 0000000..2a16cb1
--- /dev/null
+++ b/spdy/core/spdy_protocol_test_utils.h
@@ -0,0 +1,149 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_PROTOCOL_TEST_UTILS_H_
+#define QUICHE_SPDY_CORE_SPDY_PROTOCOL_TEST_UTILS_H_
+
+// These functions support tests that need to compare two concrete SpdyFrameIR
+// instances for equality. They return AssertionResult, so they may be used as
+// follows:
+//
+//    SomeSpdyFrameIRSubClass expected_ir(...);
+//    std::unique_ptr<SpdyFrameIR> collected_frame;
+//    ... some test code that may fill in collected_frame ...
+//    ASSERT_TRUE(VerifySpdyFrameIREquals(expected_ir, collected_frame.get()));
+//
+// TODO(jamessynge): Where it makes sense in these functions, it would be nice
+// to make use of the existing gMock matchers here, instead of rolling our own.
+
+#include <typeinfo>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+
+namespace spdy {
+namespace test {
+
+// Verify the header entries in two SpdyFrameWithHeaderBlockIR instances
+// are the same.
+::testing::AssertionResult VerifySpdyFrameWithHeaderBlockIREquals(
+    const SpdyFrameWithHeaderBlockIR& expected,
+    const SpdyFrameWithHeaderBlockIR& actual);
+
+// Verify that the padding in two frames of type T is the same.
+template <class T>
+::testing::AssertionResult VerifySpdyFrameWithPaddingIREquals(const T& expected,
+                                                              const T& actual) {
+  VLOG(1) << "VerifySpdyFrameWithPaddingIREquals";
+  VERIFY_EQ(expected.padded(), actual.padded());
+  if (expected.padded()) {
+    VERIFY_EQ(expected.padding_payload_len(), actual.padding_payload_len());
+  }
+
+  return ::testing::AssertionSuccess();
+}
+
+// Verify the priority fields in two frames of type T are the same.
+template <class T>
+::testing::AssertionResult VerifySpdyFrameWithPriorityIREquals(
+    const T& expected,
+    const T& actual) {
+  VLOG(1) << "VerifySpdyFrameWithPriorityIREquals";
+  VERIFY_EQ(expected.parent_stream_id(), actual.parent_stream_id());
+  VERIFY_EQ(expected.weight(), actual.weight());
+  VERIFY_EQ(expected.exclusive(), actual.exclusive());
+  return ::testing::AssertionSuccess();
+}
+
+// Verify that two SpdyAltSvcIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyAltSvcIR& expected,
+                                                   const SpdyAltSvcIR& actual);
+
+// VerifySpdyFrameIREquals for SpdyContinuationIR frames isn't really needed
+// because we don't really make use of SpdyContinuationIR, instead creating
+// SpdyHeadersIR or SpdyPushPromiseIR with the pre-encoding form of the HPACK
+// block (i.e. we don't yet have a CONTINUATION frame).
+//
+// ::testing::AssertionResult VerifySpdyFrameIREquals(
+//     const SpdyContinuationIR& expected,
+//     const SpdyContinuationIR& actual) {
+//   return ::testing::AssertionFailure()
+//          << "VerifySpdyFrameIREquals SpdyContinuationIR NYI";
+// }
+
+// Verify that two SpdyDataIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyDataIR& expected,
+                                                   const SpdyDataIR& actual);
+
+// Verify that two SpdyGoAwayIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyGoAwayIR& expected,
+                                                   const SpdyGoAwayIR& actual);
+
+// Verify that two SpdyHeadersIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdyHeadersIR& expected,
+    const SpdyHeadersIR& actual);
+
+// Verify that two SpdyPingIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyPingIR& expected,
+                                                   const SpdyPingIR& actual);
+
+// Verify that two SpdyPriorityIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdyPriorityIR& expected,
+    const SpdyPriorityIR& actual);
+
+// Verify that two SpdyPushPromiseIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdyPushPromiseIR& expected,
+    const SpdyPushPromiseIR& actual);
+
+// Verify that two SpdyRstStreamIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdyRstStreamIR& expected,
+    const SpdyRstStreamIR& actual);
+
+// Verify that two SpdySettingsIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdySettingsIR& expected,
+    const SpdySettingsIR& actual);
+
+// Verify that two SpdyWindowUpdateIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+    const SpdyWindowUpdateIR& expected,
+    const SpdyWindowUpdateIR& actual);
+
+// Verify that either expected and actual are both nullptr, or that both are not
+// nullptr, and that actual is of type E, and that it matches expected.
+template <class E>
+::testing::AssertionResult VerifySpdyFrameIREquals(const E* expected,
+                                                   const SpdyFrameIR* actual) {
+  if (expected == nullptr || actual == nullptr) {
+    VLOG(1) << "VerifySpdyFrameIREquals one null";
+    VERIFY_EQ(expected, nullptr);
+    VERIFY_EQ(actual, nullptr);
+    return ::testing::AssertionSuccess();
+  }
+  VLOG(1) << "VerifySpdyFrameIREquals not null";
+  VERIFY_EQ(actual->frame_type(), expected->frame_type());
+  const E* actual2 = down_cast<const E*>(actual);
+  return VerifySpdyFrameIREquals(*expected, *actual2);
+}
+
+// Verify that actual is not nullptr, that it is of type E and that it
+// matches expected.
+template <class E>
+::testing::AssertionResult VerifySpdyFrameIREquals(const E& expected,
+                                                   const SpdyFrameIR* actual) {
+  VLOG(1) << "VerifySpdyFrameIREquals";
+  return VerifySpdyFrameIREquals(&expected, actual);
+}
+
+}  // namespace test
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_PROTOCOL_TEST_UTILS_H_
diff --git a/spdy/core/spdy_test_utils.cc b/spdy/core/spdy_test_utils.cc
new file mode 100644
index 0000000..dd6c417
--- /dev/null
+++ b/spdy/core/spdy_test_utils.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2012 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/spdy/core/spdy_test_utils.h"
+
+#include <algorithm>
+#include <cstring>
+#include <memory>
+#include <new>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h"
+
+namespace spdy {
+namespace test {
+
+SpdyString HexDumpWithMarks(const unsigned char* data,
+                            int length,
+                            const bool* marks,
+                            int mark_length) {
+  static const char kHexChars[] = "0123456789abcdef";
+  static const int kColumns = 4;
+
+  const int kSizeLimit = 1024;
+  if (length > kSizeLimit || mark_length > kSizeLimit) {
+    LOG(ERROR) << "Only dumping first " << kSizeLimit << " bytes.";
+    length = std::min(length, kSizeLimit);
+    mark_length = std::min(mark_length, kSizeLimit);
+  }
+
+  SpdyString hex;
+  for (const unsigned char* row = data; length > 0;
+       row += kColumns, length -= kColumns) {
+    for (const unsigned char* p = row; p < row + 4; ++p) {
+      if (p < row + length) {
+        const bool mark =
+            (marks && (p - data) < mark_length && marks[p - data]);
+        hex += mark ? '*' : ' ';
+        hex += kHexChars[(*p & 0xf0) >> 4];
+        hex += kHexChars[*p & 0x0f];
+        hex += mark ? '*' : ' ';
+      } else {
+        hex += "    ";
+      }
+    }
+    hex = hex + "  ";
+
+    for (const unsigned char* p = row; p < row + 4 && p < row + length; ++p) {
+      hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.';
+    }
+
+    hex = hex + '\n';
+  }
+  return hex;
+}
+
+void CompareCharArraysWithHexError(const SpdyString& description,
+                                   const unsigned char* actual,
+                                   const int actual_len,
+                                   const unsigned char* expected,
+                                   const int expected_len) {
+  const int min_len = std::min(actual_len, expected_len);
+  const int max_len = std::max(actual_len, expected_len);
+  std::unique_ptr<bool[]> marks(new bool[max_len]);
+  bool identical = (actual_len == expected_len);
+  for (int i = 0; i < min_len; ++i) {
+    if (actual[i] != expected[i]) {
+      marks[i] = true;
+      identical = false;
+    } else {
+      marks[i] = false;
+    }
+  }
+  for (int i = min_len; i < max_len; ++i) {
+    marks[i] = true;
+  }
+  if (identical)
+    return;
+  ADD_FAILURE() << "Description:\n"
+                << description << "\n\nExpected:\n"
+                << HexDumpWithMarks(expected, expected_len, marks.get(),
+                                    max_len)
+                << "\nActual:\n"
+                << HexDumpWithMarks(actual, actual_len, marks.get(), max_len);
+}
+
+void SetFrameFlags(SpdySerializedFrame* frame, uint8_t flags) {
+  frame->data()[4] = flags;
+}
+
+void SetFrameLength(SpdySerializedFrame* frame, size_t length) {
+  CHECK_GT(1u << 14, length);
+  {
+    int32_t wire_length = SpdyHostToNet32(length);
+    memcpy(frame->data(), reinterpret_cast<char*>(&wire_length) + 1, 3);
+  }
+}
+
+void TestHeadersHandler::OnHeaderBlockStart() {
+  block_.clear();
+}
+
+void TestHeadersHandler::OnHeader(SpdyStringPiece name, SpdyStringPiece value) {
+  block_.AppendValueOrAddHeader(name, value);
+}
+
+void TestHeadersHandler::OnHeaderBlockEnd(
+    size_t header_bytes_parsed,
+    size_t compressed_header_bytes_parsed) {
+  header_bytes_parsed_ = header_bytes_parsed;
+  compressed_header_bytes_parsed_ = compressed_header_bytes_parsed;
+}
+
+}  // namespace test
+}  // namespace spdy
diff --git a/spdy/core/spdy_test_utils.h b/spdy/core/spdy_test_utils.h
new file mode 100644
index 0000000..bf62eeb
--- /dev/null
+++ b/spdy/core/spdy_test_utils.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 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_SPDY_CORE_SPDY_TEST_UTILS_H_
+#define QUICHE_SPDY_CORE_SPDY_TEST_UTILS_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h"
+
+namespace spdy {
+
+inline bool operator==(SpdyStringPiece x,
+                       const SpdyHeaderBlock::ValueProxy& y) {
+  return x == y.as_string();
+}
+
+namespace test {
+
+SpdyString HexDumpWithMarks(const unsigned char* data,
+                            int length,
+                            const bool* marks,
+                            int mark_length);
+
+void CompareCharArraysWithHexError(const SpdyString& description,
+                                   const unsigned char* actual,
+                                   const int actual_len,
+                                   const unsigned char* expected,
+                                   const int expected_len);
+
+void SetFrameFlags(SpdySerializedFrame* frame, uint8_t flags);
+
+void SetFrameLength(SpdySerializedFrame* frame, size_t length);
+
+// A test implementation of SpdyHeadersHandlerInterface that correctly
+// reconstructs multiple header values for the same name.
+class TestHeadersHandler : public SpdyHeadersHandlerInterface {
+ public:
+  TestHeadersHandler() {}
+  TestHeadersHandler(const TestHeadersHandler&) = delete;
+  TestHeadersHandler& operator=(const TestHeadersHandler&) = delete;
+
+  void OnHeaderBlockStart() override;
+
+  void OnHeader(SpdyStringPiece name, SpdyStringPiece value) override;
+
+  void OnHeaderBlockEnd(size_t header_bytes_parsed,
+                        size_t compressed_header_bytes_parsed) override;
+
+  const SpdyHeaderBlock& decoded_block() const { return block_; }
+  size_t header_bytes_parsed() const { return header_bytes_parsed_; }
+  size_t compressed_header_bytes_parsed() const {
+    return compressed_header_bytes_parsed_;
+  }
+
+ private:
+  SpdyHeaderBlock block_;
+  size_t header_bytes_parsed_ = 0;
+  size_t compressed_header_bytes_parsed_ = 0;
+};
+
+}  // namespace test
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_SPDY_TEST_UTILS_H_
diff --git a/spdy/core/write_scheduler.h b/spdy/core/write_scheduler.h
new file mode 100644
index 0000000..07e5fb3
--- /dev/null
+++ b/spdy/core/write_scheduler.h
@@ -0,0 +1,156 @@
+// Copyright (c) 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_SPDY_CORE_WRITE_SCHEDULER_H_
+#define QUICHE_SPDY_CORE_WRITE_SCHEDULER_H_
+
+#include <cstdint>
+#include <tuple>
+#include <vector>
+
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+
+namespace spdy {
+
+// Abstract superclass for classes that decide which SPDY or HTTP/2 stream to
+// write next. Concrete subclasses implement various scheduling policies:
+//
+// PriorityWriteScheduler: implements SPDY priority-based stream scheduling,
+//     where (writable) higher-priority streams are always given precedence
+//     over lower-priority streams.
+//
+// Http2PriorityWriteScheduler: implements SPDY priority-based stream
+//     scheduling coupled with the HTTP/2 stream dependency model. This is only
+//     intended as a transitional step towards Http2WeightedWriteScheduler.
+//
+// Http2WeightedWriteScheduler (coming soon): implements the HTTP/2 stream
+//     dependency model with weighted stream scheduling, fully conforming to
+//     RFC 7540.
+//
+// The type used to represent stream IDs (StreamIdType) is templated in order
+// to allow for use by both SPDY and QUIC codebases. It must be a POD that
+// supports comparison (i.e., a numeric type).
+//
+// Each stream can be in one of two states: ready or not ready (for writing).
+// Ready state is changed by calling the MarkStreamReady() and
+// MarkStreamNotReady() methods. Only streams in the ready state can be
+// returned by PopNextReadyStream(); when returned by that method, the stream's
+// state changes to not ready.
+template <typename StreamIdType>
+class SPDY_EXPORT_PRIVATE WriteScheduler {
+ public:
+  typedef StreamPrecedence<StreamIdType> StreamPrecedenceType;
+
+  virtual ~WriteScheduler() {}
+
+  // Registers new stream |stream_id| with the scheduler, assigning it the
+  // given precedence. If the scheduler supports stream dependencies, the
+  // stream is inserted into the dependency tree under
+  // |precedence.parent_id()|.
+  //
+  // Preconditions: |stream_id| should be unregistered, and
+  // |precedence.parent_id()| should be registered or |kHttp2RootStreamId|.
+  virtual void RegisterStream(StreamIdType stream_id,
+                              const StreamPrecedenceType& precedence) = 0;
+
+  // Unregisters the given stream from the scheduler, which will no longer keep
+  // state for it.
+  //
+  // Preconditions: |stream_id| should be registered.
+  virtual void UnregisterStream(StreamIdType stream_id) = 0;
+
+  // Returns true if the given stream is currently registered.
+  virtual bool StreamRegistered(StreamIdType stream_id) const = 0;
+
+  // Returns the precedence of the specified stream. If the scheduler supports
+  // stream dependencies, calling |parent_id()| on the return value returns the
+  // stream's parent, and calling |exclusive()| returns true iff the specified
+  // stream is an only child of the parent stream.
+  //
+  // Preconditions: |stream_id| should be registered.
+  virtual StreamPrecedenceType GetStreamPrecedence(
+      StreamIdType stream_id) const = 0;
+
+  // Updates the precedence of the given stream. If the scheduler supports
+  // stream dependencies, |stream_id|'s parent will be updated to be
+  // |precedence.parent_id()| if it is not already.
+  //
+  // Preconditions: |stream_id| should be unregistered, and
+  // |precedence.parent_id()| should be registered or |kHttp2RootStreamId|.
+  virtual void UpdateStreamPrecedence(
+      StreamIdType stream_id,
+      const StreamPrecedenceType& precedence) = 0;
+
+  // Returns child streams of the given stream, if any. If the scheduler
+  // doesn't support stream dependencies, returns an empty vector.
+  //
+  // Preconditions: |stream_id| should be registered.
+  virtual std::vector<StreamIdType> GetStreamChildren(
+      StreamIdType stream_id) const = 0;
+
+  // Records time (in microseconds) of a read/write event for the given
+  // stream.
+  //
+  // Preconditions: |stream_id| should be registered.
+  virtual void RecordStreamEventTime(StreamIdType stream_id,
+                                     int64_t now_in_usec) = 0;
+
+  // Returns time (in microseconds) of the last read/write event for a stream
+  // with higher priority than the priority of the given stream, or 0 if there
+  // is no such event.
+  //
+  // Preconditions: |stream_id| should be registered.
+  virtual int64_t GetLatestEventWithPrecedence(
+      StreamIdType stream_id) const = 0;
+
+  // If the scheduler has any ready streams, returns the next scheduled
+  // ready stream, in the process transitioning the stream from ready to not
+  // ready.
+  //
+  // Preconditions: |HasReadyStreams() == true|
+  virtual StreamIdType PopNextReadyStream() = 0;
+
+  // If the scheduler has any ready streams, returns the next scheduled
+  // ready stream and its priority, in the process transitioning the stream from
+  // ready to not ready.
+  //
+  // Preconditions: |HasReadyStreams() == true|
+  virtual std::tuple<StreamIdType, StreamPrecedenceType>
+  PopNextReadyStreamAndPrecedence() = 0;
+
+  // Returns true if there's another stream ahead of the given stream in the
+  // scheduling queue.  This function can be called to see if the given stream
+  // should yield work to another stream.
+  //
+  // Preconditions: |stream_id| should be registered.
+  virtual bool ShouldYield(StreamIdType stream_id) const = 0;
+
+  // Marks the stream as ready to write. If the stream was already ready, does
+  // nothing. If add_to_front is true, the stream is scheduled ahead of other
+  // streams of the same priority/weight, otherwise it is scheduled behind them.
+  //
+  // Preconditions: |stream_id| should be registered.
+  virtual void MarkStreamReady(StreamIdType stream_id, bool add_to_front) = 0;
+
+  // Marks the stream as not ready to write. If the stream is not registered or
+  // not ready, does nothing.
+  //
+  // Preconditions: |stream_id| should be registered.
+  virtual void MarkStreamNotReady(StreamIdType stream_id) = 0;
+
+  // Returns true iff the scheduler has any ready streams.
+  virtual bool HasReadyStreams() const = 0;
+
+  // Returns the number of streams currently marked ready.
+  virtual size_t NumReadyStreams() const = 0;
+
+  // Returns summary of internal state, for logging/debugging.
+  virtual SpdyString DebugString() const = 0;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_WRITE_SCHEDULER_H_
diff --git a/spdy/core/zero_copy_output_buffer.h b/spdy/core/zero_copy_output_buffer.h
new file mode 100644
index 0000000..3f35bab
--- /dev/null
+++ b/spdy/core/zero_copy_output_buffer.h
@@ -0,0 +1,30 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_ZERO_COPY_OUTPUT_BUFFER_H_
+#define QUICHE_SPDY_CORE_ZERO_COPY_OUTPUT_BUFFER_H_
+
+#include <cstdint>
+
+namespace spdy {
+
+class ZeroCopyOutputBuffer {
+ public:
+  virtual ~ZeroCopyOutputBuffer() {}
+
+  // Returns the next available segment of memory to write. Will always return
+  // the same segment until AdvanceWritePtr is called.
+  virtual void Next(char** data, int* size) = 0;
+
+  // After writing to a buffer returned from Next(), the caller should call
+  // this method to indicate how many bytes were written.
+  virtual void AdvanceWritePtr(int64_t count) = 0;
+
+  // Returns the available capacity of the buffer.
+  virtual uint64_t BytesFree() const = 0;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_ZERO_COPY_OUTPUT_BUFFER_H_