Project import generated by Copybara.

PiperOrigin-RevId: 229942388
Change-Id: Ib5a23c152c95ed4294cece9f902227c21ce531ef
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