Project import generated by Copybara.

PiperOrigin-RevId: 224614037
Change-Id: I14e53449d4aeccb328f86828c76b5f09dea0d4b8
diff --git a/http2/hpack/decoder/hpack_block_collector.cc b/http2/hpack/decoder/hpack_block_collector.cc
new file mode 100644
index 0000000..5b2c6b7
--- /dev/null
+++ b/http2/hpack/decoder/hpack_block_collector.cc
@@ -0,0 +1,151 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+
+HpackBlockCollector::HpackBlockCollector() = default;
+HpackBlockCollector::HpackBlockCollector(const HpackBlockCollector& other)
+    : pending_entry_(other.pending_entry_), entries_(other.entries_) {}
+HpackBlockCollector::~HpackBlockCollector() = default;
+
+void HpackBlockCollector::OnIndexedHeader(size_t index) {
+  pending_entry_.OnIndexedHeader(index);
+  PushPendingEntry();
+}
+void HpackBlockCollector::OnDynamicTableSizeUpdate(size_t size) {
+  pending_entry_.OnDynamicTableSizeUpdate(size);
+  PushPendingEntry();
+}
+void HpackBlockCollector::OnStartLiteralHeader(HpackEntryType header_type,
+                                               size_t maybe_name_index) {
+  pending_entry_.OnStartLiteralHeader(header_type, maybe_name_index);
+}
+void HpackBlockCollector::OnNameStart(bool huffman_encoded, size_t len) {
+  pending_entry_.OnNameStart(huffman_encoded, len);
+}
+void HpackBlockCollector::OnNameData(const char* data, size_t len) {
+  pending_entry_.OnNameData(data, len);
+}
+void HpackBlockCollector::OnNameEnd() {
+  pending_entry_.OnNameEnd();
+}
+void HpackBlockCollector::OnValueStart(bool huffman_encoded, size_t len) {
+  pending_entry_.OnValueStart(huffman_encoded, len);
+}
+void HpackBlockCollector::OnValueData(const char* data, size_t len) {
+  pending_entry_.OnValueData(data, len);
+}
+void HpackBlockCollector::OnValueEnd() {
+  pending_entry_.OnValueEnd();
+  PushPendingEntry();
+}
+
+void HpackBlockCollector::PushPendingEntry() {
+  EXPECT_TRUE(pending_entry_.IsComplete());
+  DVLOG(2) << "PushPendingEntry: " << pending_entry_;
+  entries_.push_back(pending_entry_);
+  EXPECT_TRUE(entries_.back().IsComplete());
+  pending_entry_.Clear();
+}
+void HpackBlockCollector::Clear() {
+  pending_entry_.Clear();
+  entries_.clear();
+}
+
+void HpackBlockCollector::ExpectIndexedHeader(size_t index) {
+  entries_.push_back(
+      HpackEntryCollector(HpackEntryType::kIndexedHeader, index));
+}
+void HpackBlockCollector::ExpectDynamicTableSizeUpdate(size_t size) {
+  entries_.push_back(
+      HpackEntryCollector(HpackEntryType::kDynamicTableSizeUpdate, size));
+}
+void HpackBlockCollector::ExpectNameIndexAndLiteralValue(
+    HpackEntryType type,
+    size_t index,
+    bool value_huffman,
+    const Http2String& value) {
+  entries_.push_back(HpackEntryCollector(type, index, value_huffman, value));
+}
+void HpackBlockCollector::ExpectLiteralNameAndValue(HpackEntryType type,
+                                                    bool name_huffman,
+                                                    const Http2String& name,
+                                                    bool value_huffman,
+                                                    const Http2String& value) {
+  entries_.push_back(
+      HpackEntryCollector(type, name_huffman, name, value_huffman, value));
+}
+
+void HpackBlockCollector::ShuffleEntries(Http2Random* rng) {
+  std::shuffle(entries_.begin(), entries_.end(), *rng);
+}
+
+void HpackBlockCollector::AppendToHpackBlockBuilder(
+    HpackBlockBuilder* hbb) const {
+  CHECK(IsNotPending());
+  for (const auto& entry : entries_) {
+    entry.AppendToHpackBlockBuilder(hbb);
+  }
+}
+
+AssertionResult HpackBlockCollector::ValidateSoleIndexedHeader(
+    size_t ndx) const {
+  VERIFY_TRUE(pending_entry_.IsClear());
+  VERIFY_EQ(1u, entries_.size());
+  VERIFY_TRUE(entries_.front().ValidateIndexedHeader(ndx));
+  return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleLiteralValueHeader(
+    HpackEntryType expected_type,
+    size_t expected_index,
+    bool expected_value_huffman,
+    Http2StringPiece expected_value) const {
+  VERIFY_TRUE(pending_entry_.IsClear());
+  VERIFY_EQ(1u, entries_.size());
+  VERIFY_TRUE(entries_.front().ValidateLiteralValueHeader(
+      expected_type, expected_index, expected_value_huffman, expected_value));
+  return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleLiteralNameValueHeader(
+    HpackEntryType expected_type,
+    bool expected_name_huffman,
+    Http2StringPiece expected_name,
+    bool expected_value_huffman,
+    Http2StringPiece expected_value) const {
+  VERIFY_TRUE(pending_entry_.IsClear());
+  VERIFY_EQ(1u, entries_.size());
+  VERIFY_TRUE(entries_.front().ValidateLiteralNameValueHeader(
+      expected_type, expected_name_huffman, expected_name,
+      expected_value_huffman, expected_value));
+  return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleDynamicTableSizeUpdate(
+    size_t size) const {
+  VERIFY_TRUE(pending_entry_.IsClear());
+  VERIFY_EQ(1u, entries_.size());
+  VERIFY_TRUE(entries_.front().ValidateDynamicTableSizeUpdate(size));
+  return AssertionSuccess();
+}
+
+AssertionResult HpackBlockCollector::VerifyEq(
+    const HpackBlockCollector& that) const {
+  VERIFY_EQ(pending_entry_, that.pending_entry_);
+  VERIFY_EQ(entries_, that.entries_);
+  return AssertionSuccess();
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_block_collector.h b/http2/hpack/decoder/hpack_block_collector.h
new file mode 100644
index 0000000..6ad1405
--- /dev/null
+++ b/http2/hpack/decoder/hpack_block_collector.h
@@ -0,0 +1,129 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_
+
+// HpackBlockCollector implements HpackEntryDecoderListener in order to record
+// the calls using HpackEntryCollector instances (one per HPACK entry). This
+// supports testing of HpackBlockDecoder, which decodes entire HPACK blocks.
+//
+// In addition to implementing the callback methods, HpackBlockCollector also
+// supports comparing two HpackBlockCollector instances (i.e. an expected and
+// an actual), or a sole HPACK entry against an expected value.
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+class HpackBlockCollector : public HpackEntryDecoderListener {
+ public:
+  HpackBlockCollector();
+  HpackBlockCollector(const HpackBlockCollector& other);
+  ~HpackBlockCollector() override;
+
+  // Implementations of HpackEntryDecoderListener, forwarding to pending_entry_,
+  // an HpackEntryCollector for the "in-progress" HPACK entry. OnIndexedHeader
+  // and OnDynamicTableSizeUpdate are pending only for that one call, while
+  // OnStartLiteralHeader is followed by many calls, ending with OnValueEnd.
+  // Once all the calls for one HPACK entry have been received, PushPendingEntry
+  // is used to append the pending_entry_ entry to the collected entries_.
+  void OnIndexedHeader(size_t index) override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+  void OnStartLiteralHeader(HpackEntryType header_type,
+                            size_t maybe_name_index) override;
+  void OnNameStart(bool huffman_encoded, size_t len) override;
+  void OnNameData(const char* data, size_t len) override;
+  void OnNameEnd() override;
+  void OnValueStart(bool huffman_encoded, size_t len) override;
+  void OnValueData(const char* data, size_t len) override;
+  void OnValueEnd() override;
+
+  // Methods for creating a set of expectations (i.e. HPACK entries to compare
+  // against those collected by another instance of HpackBlockCollector).
+
+  // Add an HPACK entry for an indexed header.
+  void ExpectIndexedHeader(size_t index);
+
+  // Add an HPACK entry for a dynamic table size update.
+  void ExpectDynamicTableSizeUpdate(size_t size);
+
+  // Add an HPACK entry for a header entry with an index for the name, and a
+  // literal value.
+  void ExpectNameIndexAndLiteralValue(HpackEntryType type,
+                                      size_t index,
+                                      bool value_huffman,
+                                      const Http2String& value);
+
+  // Add an HPACK entry for a header entry with a literal name and value.
+  void ExpectLiteralNameAndValue(HpackEntryType type,
+                                 bool name_huffman,
+                                 const Http2String& name,
+                                 bool value_huffman,
+                                 const Http2String& value);
+
+  // Shuffle the entries, in support of generating an HPACK block of entries
+  // in some random order.
+  void ShuffleEntries(Http2Random* rng);
+
+  // Serialize entries_ to the HpackBlockBuilder.
+  void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const;
+
+  // Return AssertionSuccess if there is just one entry, and it is an
+  // Indexed Header with the specified index.
+  ::testing::AssertionResult ValidateSoleIndexedHeader(size_t ndx) const;
+
+  // Return AssertionSuccess if there is just one entry, and it is a
+  // Dynamic Table Size Update with the specified size.
+  ::testing::AssertionResult ValidateSoleDynamicTableSizeUpdate(
+      size_t size) const;
+
+  // Return AssertionSuccess if there is just one entry, and it is a Header
+  // entry with an index for the name and a literal value.
+  ::testing::AssertionResult ValidateSoleLiteralValueHeader(
+      HpackEntryType expected_type,
+      size_t expected_index,
+      bool expected_value_huffman,
+      Http2StringPiece expected_value) const;
+
+  // Return AssertionSuccess if there is just one entry, and it is a Header
+  // with a literal name and literal value.
+  ::testing::AssertionResult ValidateSoleLiteralNameValueHeader(
+      HpackEntryType expected_type,
+      bool expected_name_huffman,
+      Http2StringPiece expected_name,
+      bool expected_value_huffman,
+      Http2StringPiece expected_value) const;
+
+  bool IsNotPending() const { return pending_entry_.IsClear(); }
+  bool IsClear() const { return IsNotPending() && entries_.empty(); }
+  void Clear();
+
+  ::testing::AssertionResult VerifyEq(const HpackBlockCollector& that) const;
+
+ private:
+  // Push the value of pending_entry_ onto entries_, and clear pending_entry_.
+  // The pending_entry_ must be complete.
+  void PushPendingEntry();
+
+  HpackEntryCollector pending_entry_;
+  std::vector<HpackEntryCollector> entries_;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_
diff --git a/http2/hpack/decoder/hpack_block_decoder.cc b/http2/hpack/decoder/hpack_block_decoder.cc
new file mode 100644
index 0000000..c6882d2
--- /dev/null
+++ b/http2/hpack/decoder/hpack_block_decoder.cc
@@ -0,0 +1,62 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h"
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+DecodeStatus HpackBlockDecoder::Decode(DecodeBuffer* db) {
+  if (!before_entry_) {
+    DVLOG(2) << "HpackBlockDecoder::Decode resume entry, db->Remaining="
+             << db->Remaining();
+    DecodeStatus status = entry_decoder_.Resume(db, listener_);
+    switch (status) {
+      case DecodeStatus::kDecodeDone:
+        before_entry_ = true;
+        break;
+      case DecodeStatus::kDecodeInProgress:
+        DCHECK_EQ(0u, db->Remaining());
+        return DecodeStatus::kDecodeInProgress;
+      case DecodeStatus::kDecodeError:
+        return DecodeStatus::kDecodeError;
+    }
+  }
+  DCHECK(before_entry_);
+  while (db->HasData()) {
+    DVLOG(2) << "HpackBlockDecoder::Decode start entry, db->Remaining="
+             << db->Remaining();
+    DecodeStatus status = entry_decoder_.Start(db, listener_);
+    switch (status) {
+      case DecodeStatus::kDecodeDone:
+        continue;
+      case DecodeStatus::kDecodeInProgress:
+        DCHECK_EQ(0u, db->Remaining());
+        before_entry_ = false;
+        return DecodeStatus::kDecodeInProgress;
+      case DecodeStatus::kDecodeError:
+        return DecodeStatus::kDecodeError;
+    }
+    DCHECK(false);
+  }
+  DCHECK(before_entry_);
+  return DecodeStatus::kDecodeDone;
+}
+
+Http2String HpackBlockDecoder::DebugString() const {
+  return Http2StrCat("HpackBlockDecoder(", entry_decoder_.DebugString(),
+                     ", listener@",
+                     Http2Hex(reinterpret_cast<intptr_t>(listener_)),
+                     (before_entry_ ? ", between entries)" : ", in an entry)"));
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackBlockDecoder& v) {
+  return out << v.DebugString();
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_block_decoder.h b/http2/hpack/decoder/hpack_block_decoder.h
new file mode 100644
index 0000000..e8b23e1
--- /dev/null
+++ b/http2/hpack/decoder/hpack_block_decoder.h
@@ -0,0 +1,64 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_
+
+// HpackBlockDecoder decodes an entire HPACK block (or the available portion
+// thereof in the DecodeBuffer) into entries, but doesn't include HPACK static
+// or dynamic table support, so table indices remain indices at this level.
+// Reports the entries to an HpackEntryDecoderListener.
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackBlockDecoder {
+ public:
+  explicit HpackBlockDecoder(HpackEntryDecoderListener* listener)
+      : listener_(listener) {
+    DCHECK_NE(listener_, nullptr);
+  }
+  ~HpackBlockDecoder() {}
+
+  HpackBlockDecoder(const HpackBlockDecoder&) = delete;
+  HpackBlockDecoder& operator=(const HpackBlockDecoder&) = delete;
+
+  // Prepares the decoder to start decoding a new HPACK block. Expected
+  // to be called from an implementation of Http2FrameDecoderListener's
+  // OnHeadersStart or OnPushPromiseStart methods.
+  void Reset() {
+    DVLOG(2) << "HpackBlockDecoder::Reset";
+    before_entry_ = true;
+  }
+
+  // Decode the fragment of the HPACK block contained in the decode buffer.
+  // Expected to be called from an implementation of Http2FrameDecoderListener's
+  // OnHpackFragment method.
+  DecodeStatus Decode(DecodeBuffer* db);
+
+  // Is the decoding process between entries (i.e. would the next byte be the
+  // first byte of a new HPACK entry)?
+  bool before_entry() const { return before_entry_; }
+
+  Http2String DebugString() const;
+
+ private:
+  HpackEntryDecoder entry_decoder_;
+  HpackEntryDecoderListener* const listener_;
+  bool before_entry_ = true;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const HpackBlockDecoder& v);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_
diff --git a/http2/hpack/decoder/hpack_block_decoder_test.cc b/http2/hpack/decoder/hpack_block_decoder_test.cc
new file mode 100644
index 0000000..fce45b2
--- /dev/null
+++ b/http2/hpack/decoder/hpack_block_decoder_test.cc
@@ -0,0 +1,294 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h"
+
+// Tests of HpackBlockDecoder.
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+
+class HpackBlockDecoderTest : public RandomDecoderTest {
+ protected:
+  HpackBlockDecoderTest() : listener_(&collector_), decoder_(&listener_) {
+    stop_decode_on_done_ = false;
+    decoder_.Reset();
+    // Make sure logging doesn't crash. Not examining the result.
+    std::ostringstream strm;
+    strm << decoder_;
+  }
+
+  DecodeStatus StartDecoding(DecodeBuffer* db) override {
+    collector_.Clear();
+    decoder_.Reset();
+    return ResumeDecoding(db);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* db) override {
+    DecodeStatus status = decoder_.Decode(db);
+
+    // Make sure logging doesn't crash. Not examining the result.
+    std::ostringstream strm;
+    strm << decoder_;
+
+    return status;
+  }
+
+  AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db,
+                                               const Validator& validator) {
+    bool return_non_zero_on_first = false;
+    return RandomDecoderTest::DecodeAndValidateSeveralWays(
+        db, return_non_zero_on_first, validator);
+  }
+
+  AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb,
+                                               const Validator& validator) {
+    DecodeBuffer db(hbb.buffer());
+    return DecodeAndValidateSeveralWays(&db, validator);
+  }
+
+  AssertionResult DecodeHpackExampleAndValidateSeveralWays(
+      Http2StringPiece hpack_example,
+      Validator validator) {
+    Http2String input = HpackExampleToStringOrDie(hpack_example);
+    DecodeBuffer db(input);
+    return DecodeAndValidateSeveralWays(&db, validator);
+  }
+
+  uint8_t Rand8() { return Random().Rand8(); }
+
+  Http2String Rand8String() { return Random().RandString(Rand8()); }
+
+  HpackBlockCollector collector_;
+  HpackEntryDecoderVLoggingListener listener_;
+  HpackBlockDecoder decoder_;
+};
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.1
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_1) {
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralNameValueHeader(
+        HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false,
+        "custom-header"));
+  };
+  const char hpack_example[] = R"(
+      40                                      | == Literal indexed ==
+      0a                                      |   Literal name (len = 10)
+      6375 7374 6f6d 2d6b 6579                | custom-key
+      0d                                      |   Literal value (len = 13)
+      6375 7374 6f6d 2d68 6561 6465 72        | custom-header
+                                              | -> custom-key:
+                                              |   custom-header
+      )";
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      hpack_example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.2
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_2) {
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralValueHeader(
+        HpackEntryType::kUnindexedLiteralHeader, 4, false, "/sample/path"));
+  };
+  const char hpack_example[] = R"(
+      04                                      | == Literal not indexed ==
+                                              |   Indexed name (idx = 4)
+                                              |     :path
+      0c                                      |   Literal value (len = 12)
+      2f73 616d 706c 652f 7061 7468           | /sample/path
+                                              | -> :path: /sample/path
+      )";
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      hpack_example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.3
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_3) {
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralNameValueHeader(
+        HpackEntryType::kNeverIndexedLiteralHeader, false, "password", false,
+        "secret"));
+  };
+  const char hpack_example[] = R"(
+      10                                      | == Literal never indexed ==
+      08                                      |   Literal name (len = 8)
+      7061 7373 776f 7264                     | password
+      06                                      |   Literal value (len = 6)
+      7365 6372 6574                          | secret
+                                              | -> password: secret
+      )";
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      hpack_example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.4
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_4) {
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleIndexedHeader(2));
+  };
+  const char hpack_example[] = R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      )";
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      hpack_example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3.1
+TEST_F(HpackBlockDecoderTest, SpecExample_C_3_1) {
+  Http2String example = R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      86                                      | == Indexed - Add ==
+                                              |   idx = 6
+                                              | -> :scheme: http
+      84                                      | == Indexed - Add ==
+                                              |   idx = 4
+                                              | -> :path: /
+      41                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 1)
+                                              |     :authority
+      0f                                      |   Literal value (len = 15)
+      7777 772e 6578 616d 706c 652e 636f 6d   | www.example.com
+                                              | -> :authority:
+                                              |   www.example.com
+      )";
+  HpackBlockCollector expected;
+  expected.ExpectIndexedHeader(2);
+  expected.ExpectIndexedHeader(6);
+  expected.ExpectIndexedHeader(4);
+  expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+                                          1, false, "www.example.com");
+  NoArgValidator do_check = [expected, this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected));
+  };
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.5.1
+TEST_F(HpackBlockDecoderTest, SpecExample_C_5_1) {
+  Http2String example = R"(
+      48                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 8)
+                                              |     :status
+      03                                      |   Literal value (len = 3)
+      3330 32                                 | 302
+                                              | -> :status: 302
+      58                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 24)
+                                              |     cache-control
+      07                                      |   Literal value (len = 7)
+      7072 6976 6174 65                       | private
+                                              | -> cache-control: private
+      61                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 33)
+                                              |     date
+      1d                                      |   Literal value (len = 29)
+      4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+      2032 303a 3133 3a32 3120 474d 54        |  20:13:21 GMT
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:21 GMT
+      6e                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 46)
+                                              |     location
+      17                                      |   Literal value (len = 23)
+      6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam
+      706c 652e 636f 6d                       | ple.com
+                                              | -> location:
+                                              |   https://www.example.com
+      )";
+  HpackBlockCollector expected;
+  expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+                                          8, false, "302");
+  expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+                                          24, false, "private");
+  expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+                                          33, false,
+                                          "Mon, 21 Oct 2013 20:13:21 GMT");
+  expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+                                          46, false, "https://www.example.com");
+  NoArgValidator do_check = [expected, this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected));
+  };
+  EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+      example, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+// Generate a bunch of HPACK block entries to expect, use those expectations
+// to generate an HPACK block, then decode it and confirm it matches those
+// expectations. Some of these are invalid (such as Indexed, with index=0),
+// but well-formed, and the decoder doesn't check for validity, just
+// well-formedness. That includes the validity of the strings not being checked,
+// such as lower-case ascii for the names, and valid Huffman encodings.
+TEST_F(HpackBlockDecoderTest, Computed) {
+  HpackBlockCollector expected;
+  expected.ExpectIndexedHeader(0);
+  expected.ExpectIndexedHeader(1);
+  expected.ExpectIndexedHeader(126);
+  expected.ExpectIndexedHeader(127);
+  expected.ExpectIndexedHeader(128);
+  expected.ExpectDynamicTableSizeUpdate(0);
+  expected.ExpectDynamicTableSizeUpdate(1);
+  expected.ExpectDynamicTableSizeUpdate(14);
+  expected.ExpectDynamicTableSizeUpdate(15);
+  expected.ExpectDynamicTableSizeUpdate(30);
+  expected.ExpectDynamicTableSizeUpdate(31);
+  expected.ExpectDynamicTableSizeUpdate(4095);
+  expected.ExpectDynamicTableSizeUpdate(4096);
+  expected.ExpectDynamicTableSizeUpdate(8192);
+  for (auto type : {HpackEntryType::kIndexedLiteralHeader,
+                    HpackEntryType::kUnindexedLiteralHeader,
+                    HpackEntryType::kNeverIndexedLiteralHeader}) {
+    for (bool value_huffman : {false, true}) {
+      // An entry with an index for the name. Ensure the name index
+      // is not zero by adding one to the Rand8() result.
+      expected.ExpectNameIndexAndLiteralValue(type, Rand8() + 1, value_huffman,
+                                              Rand8String());
+      // And two entries with literal names, one plain, one huffman encoded.
+      expected.ExpectLiteralNameAndValue(type, false, Rand8String(),
+                                         value_huffman, Rand8String());
+      expected.ExpectLiteralNameAndValue(type, true, Rand8String(),
+                                         value_huffman, Rand8String());
+    }
+  }
+  // Shuffle the entries and serialize them to produce an HPACK block.
+  expected.ShuffleEntries(RandomPtr());
+  HpackBlockBuilder hbb;
+  expected.AppendToHpackBlockBuilder(&hbb);
+
+  NoArgValidator do_check = [expected, this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected));
+  };
+  EXPECT_TRUE(
+      DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder.cc b/http2/hpack/decoder/hpack_decoder.cc
new file mode 100644
index 0000000..d6897cb
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder.cc
@@ -0,0 +1,122 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h"
+
+namespace http2 {
+
+HpackDecoder::HpackDecoder(HpackDecoderListener* listener,
+                           size_t max_string_size)
+    : decoder_state_(listener),
+      entry_buffer_(&decoder_state_, max_string_size),
+      block_decoder_(&entry_buffer_),
+      error_detected_(false) {}
+
+HpackDecoder::~HpackDecoder() = default;
+
+void HpackDecoder::set_tables_debug_listener(
+    HpackDecoderTablesDebugListener* debug_listener) {
+  decoder_state_.set_tables_debug_listener(debug_listener);
+}
+
+void HpackDecoder::set_max_string_size_bytes(size_t max_string_size_bytes) {
+  entry_buffer_.set_max_string_size_bytes(max_string_size_bytes);
+}
+
+void HpackDecoder::ApplyHeaderTableSizeSetting(uint32_t max_header_table_size) {
+  decoder_state_.ApplyHeaderTableSizeSetting(max_header_table_size);
+}
+
+bool HpackDecoder::StartDecodingBlock() {
+  DVLOG(3) << "HpackDecoder::StartDecodingBlock, error_detected="
+           << (error_detected() ? "true" : "false");
+  if (error_detected()) {
+    return false;
+  }
+  // TODO(jamessynge): Eliminate Reset(), which shouldn't be necessary
+  // if there are no errors, and shouldn't be necessary with errors if
+  // we never resume decoding after an error has been detected.
+  block_decoder_.Reset();
+  decoder_state_.OnHeaderBlockStart();
+  return true;
+}
+
+bool HpackDecoder::DecodeFragment(DecodeBuffer* db) {
+  DVLOG(3) << "HpackDecoder::DecodeFragment, error_detected="
+           << (error_detected() ? "true" : "false")
+           << ", size=" << db->Remaining();
+  if (error_detected()) {
+    return false;
+  }
+  // Decode contents of db as an HPACK block fragment, forwards the decoded
+  // entries to entry_buffer_, which in turn forwards them to decode_state_,
+  // which finally forwards them to the HpackDecoderListener.
+  DecodeStatus status = block_decoder_.Decode(db);
+  if (status == DecodeStatus::kDecodeError) {
+    // This has probably already been reported, but just in case...
+    ReportError("HPACK block malformed.");
+    return false;
+  } else if (error_detected()) {
+    return false;
+  }
+  // Should be positioned between entries iff decoding is complete.
+  DCHECK_EQ(block_decoder_.before_entry(), status == DecodeStatus::kDecodeDone)
+      << status;
+  if (!block_decoder_.before_entry()) {
+    entry_buffer_.BufferStringsIfUnbuffered();
+  }
+  return true;
+}
+
+bool HpackDecoder::EndDecodingBlock() {
+  DVLOG(3) << "HpackDecoder::EndDecodingBlock, error_detected="
+           << (error_detected() ? "true" : "false");
+  if (error_detected()) {
+    return false;
+  }
+  if (!block_decoder_.before_entry()) {
+    // The HPACK block ended in the middle of an entry.
+    ReportError("HPACK block truncated.");
+    return false;
+  }
+  decoder_state_.OnHeaderBlockEnd();
+  if (error_detected()) {
+    // HpackDecoderState will have reported the error.
+    return false;
+  }
+  return true;
+}
+
+bool HpackDecoder::error_detected() {
+  if (!error_detected_) {
+    if (entry_buffer_.error_detected()) {
+      DVLOG(2) << "HpackDecoder::error_detected in entry_buffer_";
+      error_detected_ = true;
+    } else if (decoder_state_.error_detected()) {
+      DVLOG(2) << "HpackDecoder::error_detected in decoder_state_";
+      error_detected_ = true;
+    }
+  }
+  return error_detected_;
+}
+
+size_t HpackDecoder::EstimateMemoryUsage() const {
+  return Http2EstimateMemoryUsage(entry_buffer_);
+}
+
+void HpackDecoder::ReportError(Http2StringPiece error_message) {
+  DVLOG(3) << "HpackDecoder::ReportError is new="
+           << (!error_detected_ ? "true" : "false")
+           << ", error_message: " << error_message;
+  if (!error_detected_) {
+    error_detected_ = true;
+    decoder_state_.listener()->OnHeaderErrorDetected(error_message);
+  }
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder.h b/http2/hpack/decoder/hpack_decoder.h
new file mode 100644
index 0000000..e173bc6
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder.h
@@ -0,0 +1,123 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_
+
+// Decodes HPACK blocks, calls an HpackDecoderListener with the decoded header
+// entries. Also notifies the listener of errors and of the boundaries of the
+// HPACK blocks.
+
+// TODO(jamessynge): Add feature allowing an HpackEntryDecoderListener
+// sub-class (and possibly others) to be passed in for counting events,
+// so that deciding whether to count is not done by having lots of if
+// statements, but instead by inserting an indirection only when needed.
+
+// TODO(jamessynge): Consider whether to return false from methods below
+// when an error has been previously detected. It protects calling code
+// from its failure to pay attention to previous errors, but should we
+// spend time to do that?
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+class HpackDecoderPeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE HpackDecoder {
+ public:
+  HpackDecoder(HpackDecoderListener* listener, size_t max_string_size);
+  virtual ~HpackDecoder();
+
+  HpackDecoder(const HpackDecoder&) = delete;
+  HpackDecoder& operator=(const HpackDecoder&) = delete;
+
+  // Set listener to be notified of insertions into the HPACK dynamic table,
+  // and uses of those entries.
+  void set_tables_debug_listener(
+      HpackDecoderTablesDebugListener* debug_listener);
+
+  // max_string_size specifies the maximum size of an on-the-wire string (name
+  // or value, plain or Huffman encoded) that will be accepted. See sections
+  // 5.1 and 5.2 of RFC 7541. This is a defense against OOM attacks; HTTP/2
+  // allows a decoder to enforce any limit of the size of the header lists
+  // that it is willing to decode, including less than the MAX_HEADER_LIST_SIZE
+  // setting, a setting that is initially unlimited. For example, we might
+  // choose to send a MAX_HEADER_LIST_SIZE of 64KB, and to use that same value
+  // as the upper bound for individual strings.
+  void set_max_string_size_bytes(size_t max_string_size_bytes);
+
+  // ApplyHeaderTableSizeSetting notifies this object that this endpoint has
+  // received a SETTINGS ACK frame acknowledging an earlier SETTINGS frame from
+  // this endpoint specifying a new value for SETTINGS_HEADER_TABLE_SIZE (the
+  // maximum size of the dynamic table that this endpoint will use to decode
+  // HPACK blocks).
+  // Because a SETTINGS frame can contain SETTINGS_HEADER_TABLE_SIZE values,
+  // the caller must keep track of those multiple changes, and make
+  // corresponding calls to this method. In particular, a call must be made
+  // with the lowest value acknowledged by the peer, and a call must be made
+  // with the final value acknowledged, in that order; additional calls may
+  // be made if additional values were sent. These calls must be made between
+  // decoding the SETTINGS ACK, and before the next HPACK block is decoded.
+  void ApplyHeaderTableSizeSetting(uint32_t max_header_table_size);
+
+  // Prepares the decoder for decoding a new HPACK block, and announces this to
+  // its listener. Returns true if OK to continue with decoding, false if an
+  // error has been detected, which for StartDecodingBlock means the error was
+  // detected while decoding a previous HPACK block.
+  bool StartDecodingBlock();
+
+  // Decodes a fragment (some or all of the remainder) of an HPACK block,
+  // reporting header entries (name & value pairs) that it completely decodes
+  // in the process to the listener. Returns true successfully decoded, false if
+  // an error has been detected, either during decoding of the fragment, or
+  // prior to this call.
+  bool DecodeFragment(DecodeBuffer* db);
+
+  // Completes the process of decoding an HPACK block: if the HPACK block was
+  // properly terminated, announces the end of the header list to the listener
+  // and returns true; else returns false.
+  bool EndDecodingBlock();
+
+  // Was an error detected?
+  bool error_detected();
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+ private:
+  friend class test::HpackDecoderPeer;
+
+  // Reports an error to the listener IF this is the first error detected.
+  void ReportError(Http2StringPiece error_message);
+
+  // The decompressor state, as defined by HPACK (i.e. the static and dynamic
+  // tables).
+  HpackDecoderState decoder_state_;
+
+  // Assembles the various parts of a header entry into whole entries.
+  HpackWholeEntryBuffer entry_buffer_;
+
+  // The decoder of HPACK blocks into entry parts, passed to entry_buffer_.
+  HpackBlockDecoder block_decoder_;
+
+  // Has an error been detected?
+  bool error_detected_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_
diff --git a/http2/hpack/decoder/hpack_decoder_listener.cc b/http2/hpack/decoder/hpack_decoder_listener.cc
new file mode 100644
index 0000000..8afa8aa
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_listener.cc
@@ -0,0 +1,30 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+
+namespace http2 {
+
+HpackDecoderListener::HpackDecoderListener() = default;
+HpackDecoderListener::~HpackDecoderListener() = default;
+
+HpackDecoderNoOpListener::HpackDecoderNoOpListener() = default;
+HpackDecoderNoOpListener::~HpackDecoderNoOpListener() = default;
+
+void HpackDecoderNoOpListener::OnHeaderListStart() {}
+void HpackDecoderNoOpListener::OnHeader(HpackEntryType entry_type,
+                                        const HpackString& name,
+                                        const HpackString& value) {}
+void HpackDecoderNoOpListener::OnHeaderListEnd() {}
+void HpackDecoderNoOpListener::OnHeaderErrorDetected(
+    Http2StringPiece error_message) {}
+
+// static
+HpackDecoderNoOpListener* HpackDecoderNoOpListener::NoOpListener() {
+  static HpackDecoderNoOpListener* static_instance =
+      new HpackDecoderNoOpListener();
+  return static_instance;
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_listener.h b/http2/hpack/decoder/hpack_decoder_listener.h
new file mode 100644
index 0000000..fa68591
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_listener.h
@@ -0,0 +1,66 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Defines HpackDecoderListener, the base class of listeners for HTTP header
+// lists decoded from an HPACK block.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderListener {
+ public:
+  HpackDecoderListener();
+  virtual ~HpackDecoderListener();
+
+  // OnHeaderListStart is called at the start of decoding an HPACK block into
+  // an HTTP/2 header list. Will only be called once per block, even if it
+  // extends into CONTINUATION frames.
+  virtual void OnHeaderListStart() = 0;
+
+  // Called for each header name-value pair that is decoded, in the order they
+  // appear in the HPACK block. Multiple values for a given key will be emitted
+  // as multiple calls to OnHeader.
+  virtual void OnHeader(HpackEntryType entry_type,
+                        const HpackString& name,
+                        const HpackString& value) = 0;
+
+  // OnHeaderListEnd is called after successfully decoding an HPACK block into
+  // an HTTP/2 header list. Will only be called once per block, even if it
+  // extends into CONTINUATION frames.
+  virtual void OnHeaderListEnd() = 0;
+
+  // OnHeaderErrorDetected is called if an error is detected while decoding.
+  // error_message may be used in a GOAWAY frame as the Opaque Data.
+  virtual void OnHeaderErrorDetected(Http2StringPiece error_message) = 0;
+};
+
+// A no-op implementation of HpackDecoderListener, useful for ignoring
+// callbacks once an error is detected.
+class HTTP2_EXPORT_PRIVATE HpackDecoderNoOpListener
+    : public HpackDecoderListener {
+ public:
+  HpackDecoderNoOpListener();
+  ~HpackDecoderNoOpListener() override;
+
+  void OnHeaderListStart() override;
+  void OnHeader(HpackEntryType entry_type,
+                const HpackString& name,
+                const HpackString& value) override;
+  void OnHeaderListEnd() override;
+  void OnHeaderErrorDetected(Http2StringPiece error_message) override;
+
+  // Returns a listener that ignores all the calls.
+  static HpackDecoderNoOpListener* NoOpListener();
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_
diff --git a/http2/hpack/decoder/hpack_decoder_state.cc b/http2/hpack/decoder/hpack_decoder_state.cc
new file mode 100644
index 0000000..2ddf421
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_state.cc
@@ -0,0 +1,218 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+namespace {
+
+HpackString ExtractHpackString(HpackDecoderStringBuffer* string_buffer) {
+  if (string_buffer->IsBuffered()) {
+    return HpackString(string_buffer->ReleaseString());
+  } else {
+    auto result = HpackString(string_buffer->str());
+    string_buffer->Reset();
+    return result;
+  }
+}
+
+}  // namespace
+
+HpackDecoderState::HpackDecoderState(HpackDecoderListener* listener)
+    : listener_(HTTP2_DIE_IF_NULL(listener)),
+      final_header_table_size_(Http2SettingsInfo::DefaultHeaderTableSize()),
+      lowest_header_table_size_(final_header_table_size_),
+      require_dynamic_table_size_update_(false),
+      allow_dynamic_table_size_update_(true),
+      saw_dynamic_table_size_update_(false),
+      error_detected_(false) {}
+HpackDecoderState::~HpackDecoderState() = default;
+
+void HpackDecoderState::set_tables_debug_listener(
+    HpackDecoderTablesDebugListener* debug_listener) {
+  decoder_tables_.set_debug_listener(debug_listener);
+}
+
+void HpackDecoderState::ApplyHeaderTableSizeSetting(
+    uint32_t header_table_size) {
+  DVLOG(2) << "HpackDecoderState::ApplyHeaderTableSizeSetting("
+           << header_table_size << ")";
+  DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
+  if (header_table_size < lowest_header_table_size_) {
+    lowest_header_table_size_ = header_table_size;
+  }
+  final_header_table_size_ = header_table_size;
+  DVLOG(2) << "low water mark: " << lowest_header_table_size_;
+  DVLOG(2) << "final limit: " << final_header_table_size_;
+}
+
+// Called to notify this object that we're starting to decode an HPACK block
+// (e.g. a HEADERS or PUSH_PROMISE frame's header has been decoded).
+void HpackDecoderState::OnHeaderBlockStart() {
+  DVLOG(2) << "HpackDecoderState::OnHeaderBlockStart";
+  // This instance can't be reused after an error has been detected, as we must
+  // assume that the encoder and decoder compression states are no longer
+  // synchronized.
+  DCHECK(!error_detected_);
+  DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
+  allow_dynamic_table_size_update_ = true;
+  saw_dynamic_table_size_update_ = false;
+  // If the peer has acknowledged a HEADER_TABLE_SIZE smaller than that which
+  // its HPACK encoder has been using, then the next HPACK block it sends MUST
+  // start with a Dynamic Table Size Update entry that is at least as low as
+  // lowest_header_table_size_. That may be followed by another as great as
+  // final_header_table_size_, if those are different.
+  require_dynamic_table_size_update_ =
+      (lowest_header_table_size_ <
+           decoder_tables_.current_header_table_size() ||
+       final_header_table_size_ < decoder_tables_.header_table_size_limit());
+  DVLOG(2) << "HpackDecoderState::OnHeaderListStart "
+           << "require_dynamic_table_size_update_="
+           << require_dynamic_table_size_update_;
+  listener_->OnHeaderListStart();
+}
+
+void HpackDecoderState::OnIndexedHeader(size_t index) {
+  DVLOG(2) << "HpackDecoderState::OnIndexedHeader: " << index;
+  if (error_detected_) {
+    return;
+  }
+  if (require_dynamic_table_size_update_) {
+    ReportError("Missing dynamic table size update.");
+    return;
+  }
+  allow_dynamic_table_size_update_ = false;
+  const HpackStringPair* entry = decoder_tables_.Lookup(index);
+  if (entry != nullptr) {
+    listener_->OnHeader(HpackEntryType::kIndexedHeader, entry->name,
+                        entry->value);
+  } else {
+    ReportError("Invalid index.");
+  }
+}
+
+void HpackDecoderState::OnNameIndexAndLiteralValue(
+    HpackEntryType entry_type,
+    size_t name_index,
+    HpackDecoderStringBuffer* value_buffer) {
+  DVLOG(2) << "HpackDecoderState::OnNameIndexAndLiteralValue " << entry_type
+           << ", " << name_index << ", " << value_buffer->str();
+  if (error_detected_) {
+    return;
+  }
+  if (require_dynamic_table_size_update_) {
+    ReportError("Missing dynamic table size update.");
+    return;
+  }
+  allow_dynamic_table_size_update_ = false;
+  const HpackStringPair* entry = decoder_tables_.Lookup(name_index);
+  if (entry != nullptr) {
+    HpackString value(ExtractHpackString(value_buffer));
+    listener_->OnHeader(entry_type, entry->name, value);
+    if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
+      decoder_tables_.Insert(entry->name, value);
+    }
+  } else {
+    ReportError("Invalid name index.");
+  }
+}
+
+void HpackDecoderState::OnLiteralNameAndValue(
+    HpackEntryType entry_type,
+    HpackDecoderStringBuffer* name_buffer,
+    HpackDecoderStringBuffer* value_buffer) {
+  DVLOG(2) << "HpackDecoderState::OnLiteralNameAndValue " << entry_type << ", "
+           << name_buffer->str() << ", " << value_buffer->str();
+  if (error_detected_) {
+    return;
+  }
+  if (require_dynamic_table_size_update_) {
+    ReportError("Missing dynamic table size update.");
+    return;
+  }
+  allow_dynamic_table_size_update_ = false;
+  HpackString name(ExtractHpackString(name_buffer));
+  HpackString value(ExtractHpackString(value_buffer));
+  listener_->OnHeader(entry_type, name, value);
+  if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
+    decoder_tables_.Insert(name, value);
+  }
+}
+
+void HpackDecoderState::OnDynamicTableSizeUpdate(size_t size_limit) {
+  DVLOG(2) << "HpackDecoderState::OnDynamicTableSizeUpdate " << size_limit
+           << ", required="
+           << (require_dynamic_table_size_update_ ? "true" : "false")
+           << ", allowed="
+           << (allow_dynamic_table_size_update_ ? "true" : "false");
+  if (error_detected_) {
+    return;
+  }
+  DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
+  if (!allow_dynamic_table_size_update_) {
+    // At most two dynamic table size updates allowed at the start, and not
+    // after a header.
+    ReportError("Dynamic table size update not allowed.");
+    return;
+  }
+  if (require_dynamic_table_size_update_) {
+    // The new size must not be greater than the low water mark.
+    if (size_limit > lowest_header_table_size_) {
+      ReportError("Initial dynamic table size update is above low water mark.");
+      return;
+    }
+    require_dynamic_table_size_update_ = false;
+  } else if (size_limit > final_header_table_size_) {
+    // The new size must not be greater than the final max header table size
+    // that the peer acknowledged.
+    ReportError("Dynamic table size update is above acknowledged setting.");
+    return;
+  }
+  decoder_tables_.DynamicTableSizeUpdate(size_limit);
+  if (saw_dynamic_table_size_update_) {
+    allow_dynamic_table_size_update_ = false;
+  } else {
+    saw_dynamic_table_size_update_ = true;
+  }
+  // We no longer need to keep an eye out for a lower header table size.
+  lowest_header_table_size_ = final_header_table_size_;
+}
+
+void HpackDecoderState::OnHpackDecodeError(Http2StringPiece error_message) {
+  DVLOG(2) << "HpackDecoderState::OnHpackDecodeError " << error_message;
+  if (!error_detected_) {
+    ReportError(error_message);
+  }
+}
+
+void HpackDecoderState::OnHeaderBlockEnd() {
+  DVLOG(2) << "HpackDecoderState::OnHeaderBlockEnd";
+  if (error_detected_) {
+    return;
+  }
+  if (require_dynamic_table_size_update_) {
+    // Apparently the HPACK block was empty, but we needed it to contain at
+    // least 1 dynamic table size update.
+    ReportError("Missing dynamic table size update.");
+  } else {
+    listener_->OnHeaderListEnd();
+  }
+}
+
+void HpackDecoderState::ReportError(Http2StringPiece error_message) {
+  DVLOG(2) << "HpackDecoderState::ReportError is new="
+           << (!error_detected_ ? "true" : "false")
+           << ", error_message: " << error_message;
+  if (!error_detected_) {
+    listener_->OnHeaderErrorDetected(error_message);
+    error_detected_ = true;
+  }
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_state.h b/http2/hpack/decoder/hpack_decoder_state.h
new file mode 100644
index 0000000..fd34179
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_state.h
@@ -0,0 +1,129 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// HpackDecoderState maintains the HPACK decompressor state; i.e. updates the
+// HPACK dynamic table according to RFC 7541 as the entries in an HPACK block
+// are decoded, and reads from the static and dynamic tables in order to build
+// complete header entries. Calls an HpackDecoderListener with the completely
+// decoded headers (i.e. after resolving table indices into names or values),
+// thus translating the decoded HPACK entries into HTTP/2 headers.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+class HpackDecoderStatePeer;
+}  // namespace test
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderState : public HpackWholeEntryListener {
+ public:
+  explicit HpackDecoderState(HpackDecoderListener* listener);
+  ~HpackDecoderState() override;
+
+  HpackDecoderState(const HpackDecoderState&) = delete;
+  HpackDecoderState& operator=(const HpackDecoderState&) = delete;
+
+  // Set the listener to be notified when a whole entry has been decoded,
+  // including resolving name or name and value references.
+  // The listener may be changed at any time.
+  HpackDecoderListener* listener() const { return listener_; }
+
+  // Set listener to be notified of insertions into the HPACK dynamic table,
+  // and uses of those entries.
+  void set_tables_debug_listener(
+      HpackDecoderTablesDebugListener* debug_listener);
+
+  // ApplyHeaderTableSizeSetting notifies this object that this endpoint has
+  // received a SETTINGS ACK frame acknowledging an earlier SETTINGS frame from
+  // this endpoint specifying a new value for SETTINGS_HEADER_TABLE_SIZE (the
+  // maximum size of the dynamic table that this endpoint will use to decode
+  // HPACK blocks).
+  // Because a SETTINGS frame can contain SETTINGS_HEADER_TABLE_SIZE values,
+  // the caller must keep track of those multiple changes, and make
+  // corresponding calls to this method. In particular, a call must be made
+  // with the lowest value acknowledged by the peer, and a call must be made
+  // with the final value acknowledged, in that order; additional calls may
+  // be made if additional values were sent. These calls must be made between
+  // decoding the SETTINGS ACK, and before the next HPACK block is decoded.
+  void ApplyHeaderTableSizeSetting(uint32_t max_header_table_size);
+
+  // OnHeaderBlockStart notifies this object that we're starting to decode the
+  // HPACK payload of a HEADERS or PUSH_PROMISE frame.
+  void OnHeaderBlockStart();
+
+  // Implement the HpackWholeEntryListener methods, each of which notifies this
+  // object when an entire entry has been decoded.
+  void OnIndexedHeader(size_t index) override;
+  void OnNameIndexAndLiteralValue(
+      HpackEntryType entry_type,
+      size_t name_index,
+      HpackDecoderStringBuffer* value_buffer) override;
+  void OnLiteralNameAndValue(HpackEntryType entry_type,
+                             HpackDecoderStringBuffer* name_buffer,
+                             HpackDecoderStringBuffer* value_buffer) override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+  void OnHpackDecodeError(Http2StringPiece error_message) override;
+
+  // OnHeaderBlockEnd notifies this object that an entire HPACK block has been
+  // decoded, which might have extended into CONTINUATION blocks.
+  void OnHeaderBlockEnd();
+
+  // Was an error detected? After an error has been detected and reported,
+  // no further callbacks will be made to the listener.
+  bool error_detected() const { return error_detected_; }
+
+  const HpackDecoderTables& decoder_tables_for_test() const {
+    return decoder_tables_;
+  }
+
+ private:
+  friend class test::HpackDecoderStatePeer;
+
+  // Reports an error to the listener IF this is the first error detected.
+  void ReportError(Http2StringPiece error_message);
+
+  // The static and dynamic HPACK tables.
+  HpackDecoderTables decoder_tables_;
+
+  // The listener to be notified of headers, the start and end of header
+  // lists, and of errors.
+  HpackDecoderListener* listener_;
+
+  // The most recent HEADER_TABLE_SIZE setting acknowledged by the peer.
+  uint32_t final_header_table_size_;
+
+  // The lowest HEADER_TABLE_SIZE setting acknowledged by the peer; valid until
+  // the next HPACK block is decoded.
+  // TODO(jamessynge): Test raising the HEADER_TABLE_SIZE.
+  uint32_t lowest_header_table_size_;
+
+  // Must the next (first) HPACK entry be a dynamic table size update?
+  bool require_dynamic_table_size_update_;
+
+  // May the next (first or second) HPACK entry be a dynamic table size update?
+  bool allow_dynamic_table_size_update_;
+
+  // Have we already seen a dynamic table size update in this HPACK block?
+  bool saw_dynamic_table_size_update_;
+
+  // Has an error already been detected and reported to the listener?
+  bool error_detected_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
diff --git a/http2/hpack/decoder/hpack_decoder_state_test.cc b/http2/hpack/decoder/hpack_decoder_state_test.cc
new file mode 100644
index 0000000..115827e
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_state_test.cc
@@ -0,0 +1,539 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+
+// Tests of HpackDecoderState.
+
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Mock;
+using ::testing::StrictMock;
+
+namespace http2 {
+namespace test {
+class HpackDecoderStatePeer {
+ public:
+  static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) {
+    return &state->decoder_tables_;
+  }
+};
+
+namespace {
+
+class MockHpackDecoderListener : public HpackDecoderListener {
+ public:
+  MOCK_METHOD0(OnHeaderListStart, void());
+  MOCK_METHOD3(OnHeader,
+               void(HpackEntryType entry_type,
+                    const HpackString& name,
+                    const HpackString& value));
+  MOCK_METHOD0(OnHeaderListEnd, void());
+  MOCK_METHOD1(OnHeaderErrorDetected, void(Http2StringPiece error_message));
+};
+
+enum StringBacking { STATIC, UNBUFFERED, BUFFERED };
+
+class HpackDecoderStateTest : public ::testing::Test {
+ protected:
+  HpackDecoderStateTest() : decoder_state_(&listener_) {}
+
+  HpackDecoderTables* GetDecoderTables() {
+    return HpackDecoderStatePeer::GetDecoderTables(&decoder_state_);
+  }
+
+  const HpackStringPair* Lookup(size_t index) {
+    return GetDecoderTables()->Lookup(index);
+  }
+
+  size_t current_header_table_size() {
+    return GetDecoderTables()->current_header_table_size();
+  }
+
+  size_t header_table_size_limit() {
+    return GetDecoderTables()->header_table_size_limit();
+  }
+
+  void set_header_table_size_limit(size_t size) {
+    GetDecoderTables()->DynamicTableSizeUpdate(size);
+  }
+
+  void SetStringBuffer(const char* s,
+                       StringBacking backing,
+                       HpackDecoderStringBuffer* string_buffer) {
+    switch (backing) {
+      case STATIC:
+        string_buffer->Set(s, true);
+        break;
+      case UNBUFFERED:
+        string_buffer->Set(s, false);
+        break;
+      case BUFFERED:
+        string_buffer->Set(s, false);
+        string_buffer->BufferStringIfUnbuffered();
+        break;
+    }
+  }
+
+  void SetName(const char* s, StringBacking backing) {
+    SetStringBuffer(s, backing, &name_buffer_);
+  }
+
+  void SetValue(const char* s, StringBacking backing) {
+    SetStringBuffer(s, backing, &value_buffer_);
+  }
+
+  void SendStartAndVerifyCallback() {
+    EXPECT_CALL(listener_, OnHeaderListStart());
+    decoder_state_.OnHeaderBlockStart();
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  void SendSizeUpdate(size_t size) {
+    decoder_state_.OnDynamicTableSizeUpdate(size);
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  void SendIndexAndVerifyCallback(size_t index,
+                                  HpackEntryType expected_type,
+                                  const char* expected_name,
+                                  const char* expected_value) {
+    EXPECT_CALL(listener_,
+                OnHeader(expected_type, Eq(expected_name), Eq(expected_value)));
+    decoder_state_.OnIndexedHeader(index);
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  void SendValueAndVerifyCallback(size_t name_index,
+                                  HpackEntryType entry_type,
+                                  const char* name,
+                                  const char* value,
+                                  StringBacking value_backing) {
+    SetValue(value, value_backing);
+    EXPECT_CALL(listener_, OnHeader(entry_type, Eq(name), Eq(value)));
+    decoder_state_.OnNameIndexAndLiteralValue(entry_type, name_index,
+                                              &value_buffer_);
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  void SendNameAndValueAndVerifyCallback(HpackEntryType entry_type,
+                                         const char* name,
+                                         StringBacking name_backing,
+                                         const char* value,
+                                         StringBacking value_backing) {
+    SetName(name, name_backing);
+    SetValue(value, value_backing);
+    EXPECT_CALL(listener_, OnHeader(entry_type, Eq(name), Eq(value)));
+    decoder_state_.OnLiteralNameAndValue(entry_type, &name_buffer_,
+                                         &value_buffer_);
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  void SendEndAndVerifyCallback() {
+    EXPECT_CALL(listener_, OnHeaderListEnd());
+    decoder_state_.OnHeaderBlockEnd();
+    Mock::VerifyAndClearExpectations(&listener_);
+  }
+
+  // dynamic_index is one-based, because that is the way RFC 7541 shows it.
+  AssertionResult VerifyEntry(size_t dynamic_index,
+                              const char* name,
+                              const char* value) {
+    const HpackStringPair* entry =
+        Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+    VERIFY_NE(entry, nullptr);
+    VERIFY_EQ(entry->name.ToStringPiece(), name);
+    VERIFY_EQ(entry->value.ToStringPiece(), value);
+    return AssertionSuccess();
+  }
+  AssertionResult VerifyNoEntry(size_t dynamic_index) {
+    const HpackStringPair* entry =
+        Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+    VERIFY_EQ(entry, nullptr);
+    return AssertionSuccess();
+  }
+  AssertionResult VerifyDynamicTableContents(
+      const std::vector<std::pair<const char*, const char*>>& entries) {
+    size_t index = 1;
+    for (const auto& entry : entries) {
+      VERIFY_SUCCESS(VerifyEntry(index, entry.first, entry.second));
+      ++index;
+    }
+    VERIFY_SUCCESS(VerifyNoEntry(index));
+    return AssertionSuccess();
+  }
+
+  StrictMock<MockHpackDecoderListener> listener_;
+  HpackDecoderState decoder_state_;
+  HpackDecoderStringBuffer name_buffer_, value_buffer_;
+};
+
+// Test based on RFC 7541, section C.3: Request Examples without Huffman Coding.
+// This section shows several consecutive header lists, corresponding to HTTP
+// requests, on the same connection.
+TEST_F(HpackDecoderStateTest, C3_RequestExamples) {
+  // C.3.1 First Request
+  //
+  // Header list to encode:
+  //
+  //   :method: GET
+  //   :scheme: http
+  //   :path: /
+  //   :authority: www.example.com
+
+  SendStartAndVerifyCallback();
+  SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method",
+                             "GET");
+  SendIndexAndVerifyCallback(6, HpackEntryType::kIndexedHeader, ":scheme",
+                             "http");
+  SendIndexAndVerifyCallback(4, HpackEntryType::kIndexedHeader, ":path", "/");
+  SendValueAndVerifyCallback(1, HpackEntryType::kIndexedLiteralHeader,
+                             ":authority", "www.example.com", UNBUFFERED);
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  57) :authority: www.example.com
+  //         Table size:  57
+
+  ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}}));
+  ASSERT_EQ(57u, current_header_table_size());
+
+  // C.3.2 Second Request
+  //
+  // Header list to encode:
+  //
+  //   :method: GET
+  //   :scheme: http
+  //   :path: /
+  //   :authority: www.example.com
+  //   cache-control: no-cache
+
+  SendStartAndVerifyCallback();
+  SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method",
+                             "GET");
+  SendIndexAndVerifyCallback(6, HpackEntryType::kIndexedHeader, ":scheme",
+                             "http");
+  SendIndexAndVerifyCallback(4, HpackEntryType::kIndexedHeader, ":path", "/");
+  SendIndexAndVerifyCallback(62, HpackEntryType::kIndexedHeader, ":authority",
+                             "www.example.com");
+  SendValueAndVerifyCallback(24, HpackEntryType::kIndexedLiteralHeader,
+                             "cache-control", "no-cache", UNBUFFERED);
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  53) cache-control: no-cache
+  //   [  2] (s =  57) :authority: www.example.com
+  //         Table size: 110
+
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"cache-control", "no-cache"}, {":authority", "www.example.com"}}));
+  ASSERT_EQ(110u, current_header_table_size());
+
+  // C.3.3 Third Request
+  //
+  // Header list to encode:
+  //
+  //   :method: GET
+  //   :scheme: https
+  //   :path: /index.html
+  //   :authority: www.example.com
+  //   custom-key: custom-value
+
+  SendStartAndVerifyCallback();
+  SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method",
+                             "GET");
+  SendIndexAndVerifyCallback(7, HpackEntryType::kIndexedHeader, ":scheme",
+                             "https");
+  SendIndexAndVerifyCallback(5, HpackEntryType::kIndexedHeader, ":path",
+                             "/index.html");
+  SendIndexAndVerifyCallback(63, HpackEntryType::kIndexedHeader, ":authority",
+                             "www.example.com");
+  SendNameAndValueAndVerifyCallback(HpackEntryType::kIndexedLiteralHeader,
+                                    "custom-key", UNBUFFERED, "custom-value",
+                                    UNBUFFERED);
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  54) custom-key: custom-value
+  //   [  2] (s =  53) cache-control: no-cache
+  //   [  3] (s =  57) :authority: www.example.com
+  //         Table size: 164
+
+  ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"},
+                                          {"cache-control", "no-cache"},
+                                          {":authority", "www.example.com"}}));
+  ASSERT_EQ(164u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.5: Response Examples without Huffman
+// Coding. This section shows several consecutive header lists, corresponding
+// to HTTP responses, on the same connection. The HTTP/2 setting parameter
+// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing
+// some evictions to occur.
+TEST_F(HpackDecoderStateTest, C5_ResponseExamples) {
+  set_header_table_size_limit(256);
+
+  // C.5.1 First Response
+  //
+  // Header list to encode:
+  //
+  //   :status: 302
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+
+  SendStartAndVerifyCallback();
+  SendValueAndVerifyCallback(8, HpackEntryType::kIndexedLiteralHeader,
+                             ":status", "302", BUFFERED);
+  SendValueAndVerifyCallback(24, HpackEntryType::kIndexedLiteralHeader,
+                             "cache-control", "private", UNBUFFERED);
+  SendValueAndVerifyCallback(33, HpackEntryType::kIndexedLiteralHeader, "date",
+                             "Mon, 21 Oct 2013 20:13:21 GMT", UNBUFFERED);
+  SendValueAndVerifyCallback(46, HpackEntryType::kIndexedLiteralHeader,
+                             "location", "https://www.example.com", UNBUFFERED);
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  63) location: https://www.example.com
+  //   [  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  3] (s =  52) cache-control: private
+  //   [  4] (s =  42) :status: 302
+  //         Table size: 222
+
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"},
+                                  {":status", "302"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.2 Second Response
+  //
+  // The (":status", "302") header field is evicted from the dynamic table to
+  // free space to allow adding the (":status", "307") header field.
+  //
+  // Header list to encode:
+  //
+  //   :status: 307
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+
+  SendStartAndVerifyCallback();
+  SendValueAndVerifyCallback(8, HpackEntryType::kIndexedLiteralHeader,
+                             ":status", "307", BUFFERED);
+  SendIndexAndVerifyCallback(65, HpackEntryType::kIndexedHeader,
+                             "cache-control", "private");
+  SendIndexAndVerifyCallback(64, HpackEntryType::kIndexedHeader, "date",
+                             "Mon, 21 Oct 2013 20:13:21 GMT");
+  SendIndexAndVerifyCallback(63, HpackEntryType::kIndexedHeader, "location",
+                             "https://www.example.com");
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  42) :status: 307
+  //   [  2] (s =  63) location: https://www.example.com
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  4] (s =  52) cache-control: private
+  //         Table size: 222
+
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{":status", "307"},
+                                  {"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.3 Third Response
+  //
+  // Several header fields are evicted from the dynamic table during the
+  // processing of this header list.
+  //
+  // Header list to encode:
+  //
+  //   :status: 200
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:22 GMT
+  //   location: https://www.example.com
+  //   content-encoding: gzip
+  //   set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
+
+  SendStartAndVerifyCallback();
+  SendIndexAndVerifyCallback(8, HpackEntryType::kIndexedHeader, ":status",
+                             "200");
+  SendIndexAndVerifyCallback(65, HpackEntryType::kIndexedHeader,
+                             "cache-control", "private");
+  SendValueAndVerifyCallback(33, HpackEntryType::kIndexedLiteralHeader, "date",
+                             "Mon, 21 Oct 2013 20:13:22 GMT", BUFFERED);
+  SendIndexAndVerifyCallback(64, HpackEntryType::kIndexedHeader, "location",
+                             "https://www.example.com");
+  SendValueAndVerifyCallback(26, HpackEntryType::kIndexedLiteralHeader,
+                             "content-encoding", "gzip", UNBUFFERED);
+  SendValueAndVerifyCallback(
+      55, HpackEntryType::kIndexedLiteralHeader, "set-cookie",
+      "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", BUFFERED);
+  SendEndAndVerifyCallback();
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
+  //                    max-age=3600; version=1
+  //   [  2] (s =  52) content-encoding: gzip
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT
+  //         Table size: 215
+
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"set-cookie",
+        "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+       {"content-encoding", "gzip"},
+       {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}}));
+  ASSERT_EQ(215u, current_header_table_size());
+}
+
+// Confirm that the table size can be changed, but at most twice.
+TEST_F(HpackDecoderStateTest, OptionalTableSizeChanges) {
+  SendStartAndVerifyCallback();
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  SendSizeUpdate(1024);
+  EXPECT_EQ(1024u, header_table_size_limit());
+  SendSizeUpdate(0);
+  EXPECT_EQ(0u, header_table_size_limit());
+
+  // Three updates aren't allowed.
+  EXPECT_CALL(listener_,
+              OnHeaderErrorDetected(HasSubstr("size update not allowed")));
+  SendSizeUpdate(0);
+}
+
+// Confirm that required size updates are indeed required before headers.
+TEST_F(HpackDecoderStateTest, RequiredTableSizeChangeBeforeHeader) {
+  decoder_state_.ApplyHeaderTableSizeSetting(1024);
+  decoder_state_.ApplyHeaderTableSizeSetting(2048);
+
+  // First provide the required update, and an allowed second update.
+  SendStartAndVerifyCallback();
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  SendSizeUpdate(1024);
+  EXPECT_EQ(1024u, header_table_size_limit());
+  SendSizeUpdate(1500);
+  EXPECT_EQ(1500u, header_table_size_limit());
+  SendEndAndVerifyCallback();
+
+  // Another HPACK block, but this time missing the required size update.
+  decoder_state_.ApplyHeaderTableSizeSetting(1024);
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_, OnHeaderErrorDetected(
+                             HasSubstr("Missing dynamic table size update")));
+  decoder_state_.OnIndexedHeader(1);
+
+  // Further decoded entries are ignored.
+  decoder_state_.OnIndexedHeader(1);
+  decoder_state_.OnDynamicTableSizeUpdate(1);
+  SetValue("value", UNBUFFERED);
+  decoder_state_.OnNameIndexAndLiteralValue(
+      HpackEntryType::kIndexedLiteralHeader, 4, &value_buffer_);
+  SetName("name", UNBUFFERED);
+  decoder_state_.OnLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader,
+                                       &name_buffer_, &value_buffer_);
+  decoder_state_.OnHeaderBlockEnd();
+  decoder_state_.OnHpackDecodeError("NOT FORWARDED");
+}
+
+// Confirm that required size updates are validated.
+TEST_F(HpackDecoderStateTest, InvalidRequiredSizeUpdate) {
+  // Require a size update, but provide one that isn't small enough.
+  decoder_state_.ApplyHeaderTableSizeSetting(1024);
+  SendStartAndVerifyCallback();
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  EXPECT_CALL(listener_,
+              OnHeaderErrorDetected(HasSubstr("above low water mark")));
+  SendSizeUpdate(2048);
+}
+
+// Confirm that required size updates are indeed required before the end.
+TEST_F(HpackDecoderStateTest, RequiredTableSizeChangeBeforeEnd) {
+  decoder_state_.ApplyHeaderTableSizeSetting(1024);
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_, OnHeaderErrorDetected(
+                             HasSubstr("Missing dynamic table size update")));
+  decoder_state_.OnHeaderBlockEnd();
+}
+
+// Confirm that optional size updates are validated.
+TEST_F(HpackDecoderStateTest, InvalidOptionalSizeUpdate) {
+  // Require a size update, but provide one that isn't small enough.
+  SendStartAndVerifyCallback();
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  EXPECT_CALL(listener_,
+              OnHeaderErrorDetected(HasSubstr("size update is above")));
+  SendSizeUpdate(Http2SettingsInfo::DefaultHeaderTableSize() + 1);
+}
+
+TEST_F(HpackDecoderStateTest, InvalidStaticIndex) {
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_, OnHeaderErrorDetected(HasSubstr("Invalid index")));
+  decoder_state_.OnIndexedHeader(0);
+}
+
+TEST_F(HpackDecoderStateTest, InvalidDynamicIndex) {
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_, OnHeaderErrorDetected(HasSubstr("Invalid index")));
+  decoder_state_.OnIndexedHeader(kFirstDynamicTableIndex);
+}
+
+TEST_F(HpackDecoderStateTest, InvalidNameIndex) {
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_,
+              OnHeaderErrorDetected(HasSubstr("Invalid name index")));
+  SetValue("value", UNBUFFERED);
+  decoder_state_.OnNameIndexAndLiteralValue(
+      HpackEntryType::kIndexedLiteralHeader, kFirstDynamicTableIndex,
+      &value_buffer_);
+}
+
+TEST_F(HpackDecoderStateTest, ErrorsSuppressCallbacks) {
+  SendStartAndVerifyCallback();
+  EXPECT_CALL(listener_,
+              OnHeaderErrorDetected(Http2StringPiece("Huffman decode error.")));
+  decoder_state_.OnHpackDecodeError("Huffman decode error.");
+
+  // Further decoded entries are ignored.
+  decoder_state_.OnIndexedHeader(1);
+  decoder_state_.OnDynamicTableSizeUpdate(1);
+  SetValue("value", UNBUFFERED);
+  decoder_state_.OnNameIndexAndLiteralValue(
+      HpackEntryType::kIndexedLiteralHeader, 4, &value_buffer_);
+  SetName("name", UNBUFFERED);
+  decoder_state_.OnLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader,
+                                       &name_buffer_, &value_buffer_);
+  decoder_state_.OnHeaderBlockEnd();
+  decoder_state_.OnHpackDecodeError("NOT FORWARDED");
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_string_buffer.cc b/http2/hpack/decoder/hpack_decoder_string_buffer.cc
new file mode 100644
index 0000000..b20c37a
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_string_buffer.cc
@@ -0,0 +1,235 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+                         const HpackDecoderStringBuffer::State v) {
+  switch (v) {
+    case HpackDecoderStringBuffer::State::RESET:
+      return out << "RESET";
+    case HpackDecoderStringBuffer::State::COLLECTING:
+      return out << "COLLECTING";
+    case HpackDecoderStringBuffer::State::COMPLETE:
+      return out << "COMPLETE";
+  }
+  // Since the value doesn't come over the wire, only a programming bug should
+  // result in reaching this point.
+  int unknown = static_cast<int>(v);
+  HTTP2_BUG << "Invalid HpackDecoderStringBuffer::State: " << unknown;
+  return out << "HpackDecoderStringBuffer::State(" << unknown << ")";
+}
+
+std::ostream& operator<<(std::ostream& out,
+                         const HpackDecoderStringBuffer::Backing v) {
+  switch (v) {
+    case HpackDecoderStringBuffer::Backing::RESET:
+      return out << "RESET";
+    case HpackDecoderStringBuffer::Backing::UNBUFFERED:
+      return out << "UNBUFFERED";
+    case HpackDecoderStringBuffer::Backing::BUFFERED:
+      return out << "BUFFERED";
+    case HpackDecoderStringBuffer::Backing::STATIC:
+      return out << "STATIC";
+  }
+  // Since the value doesn't come over the wire, only a programming bug should
+  // result in reaching this point.
+  auto v2 = static_cast<int>(v);
+  HTTP2_BUG << "Invalid HpackDecoderStringBuffer::Backing: " << v2;
+  return out << "HpackDecoderStringBuffer::Backing(" << v2 << ")";
+}
+
+HpackDecoderStringBuffer::HpackDecoderStringBuffer()
+    : remaining_len_(0),
+      is_huffman_encoded_(false),
+      state_(State::RESET),
+      backing_(Backing::RESET) {}
+HpackDecoderStringBuffer::~HpackDecoderStringBuffer() = default;
+
+void HpackDecoderStringBuffer::Reset() {
+  DVLOG(3) << "HpackDecoderStringBuffer::Reset";
+  state_ = State::RESET;
+}
+
+void HpackDecoderStringBuffer::Set(Http2StringPiece value, bool is_static) {
+  DVLOG(2) << "HpackDecoderStringBuffer::Set";
+  DCHECK_EQ(state_, State::RESET);
+  value_ = value;
+  state_ = State::COMPLETE;
+  backing_ = is_static ? Backing::STATIC : Backing::UNBUFFERED;
+  // TODO(jamessynge): Determine which of these two fields must be set.
+  remaining_len_ = 0;
+  is_huffman_encoded_ = false;
+}
+
+void HpackDecoderStringBuffer::OnStart(bool huffman_encoded, size_t len) {
+  DVLOG(2) << "HpackDecoderStringBuffer::OnStart";
+  DCHECK_EQ(state_, State::RESET);
+
+  remaining_len_ = len;
+  is_huffman_encoded_ = huffman_encoded;
+  state_ = State::COLLECTING;
+
+  if (huffman_encoded) {
+    // We don't set, clear or use value_ for buffered strings until OnEnd.
+    decoder_.Reset();
+    buffer_.clear();
+    backing_ = Backing::BUFFERED;
+
+    // Reserve space in buffer_ for the uncompressed string, assuming the
+    // maximum expansion. The shortest Huffman codes in the RFC are 5 bits long,
+    // which then expand to 8 bits during decoding (i.e. each code is for one
+    // plain text octet, aka byte), so the maximum size is 60% longer than the
+    // encoded size.
+    len = len * 8 / 5;
+    if (buffer_.capacity() < len) {
+      buffer_.reserve(len);
+    }
+  } else {
+    // Assume for now that we won't need to use buffer_, so don't reserve space
+    // in it.
+    backing_ = Backing::RESET;
+    // OnData is not called for empty (zero length) strings, so make sure that
+    // value_ is cleared.
+    value_ = Http2StringPiece();
+  }
+}
+
+bool HpackDecoderStringBuffer::OnData(const char* data, size_t len) {
+  DVLOG(2) << "HpackDecoderStringBuffer::OnData state=" << state_
+           << ", backing=" << backing_;
+  DCHECK_EQ(state_, State::COLLECTING);
+  DCHECK_LE(len, remaining_len_);
+  remaining_len_ -= len;
+
+  if (is_huffman_encoded_) {
+    DCHECK_EQ(backing_, Backing::BUFFERED);
+    return decoder_.Decode(Http2StringPiece(data, len), &buffer_);
+  }
+
+  if (backing_ == Backing::RESET) {
+    // This is the first call to OnData. If data contains the entire string,
+    // don't copy the string. If we later find that the HPACK entry is split
+    // across input buffers, then we'll copy the string into buffer_.
+    if (remaining_len_ == 0) {
+      value_ = Http2StringPiece(data, len);
+      backing_ = Backing::UNBUFFERED;
+      return true;
+    }
+
+    // We need to buffer the string because it is split across input buffers.
+    // Reserve space in buffer_ for the entire string.
+    backing_ = Backing::BUFFERED;
+    buffer_.reserve(remaining_len_ + len);
+    buffer_.assign(data, len);
+    return true;
+  }
+
+  // This is not the first call to OnData for this string, so it should be
+  // buffered.
+  DCHECK_EQ(backing_, Backing::BUFFERED);
+
+  // Append to the current contents of the buffer.
+  buffer_.append(data, len);
+  return true;
+}
+
+bool HpackDecoderStringBuffer::OnEnd() {
+  DVLOG(2) << "HpackDecoderStringBuffer::OnEnd";
+  DCHECK_EQ(state_, State::COLLECTING);
+  DCHECK_EQ(0u, remaining_len_);
+
+  if (is_huffman_encoded_) {
+    DCHECK_EQ(backing_, Backing::BUFFERED);
+    // Did the Huffman encoding of the string end properly?
+    if (!decoder_.InputProperlyTerminated()) {
+      return false;  // No, it didn't.
+    }
+    value_ = buffer_;
+  } else if (backing_ == Backing::BUFFERED) {
+    value_ = buffer_;
+  }
+  state_ = State::COMPLETE;
+  return true;
+}
+
+void HpackDecoderStringBuffer::BufferStringIfUnbuffered() {
+  DVLOG(3) << "HpackDecoderStringBuffer::BufferStringIfUnbuffered state="
+           << state_ << ", backing=" << backing_;
+  if (state_ != State::RESET && backing_ == Backing::UNBUFFERED) {
+    DVLOG(2) << "HpackDecoderStringBuffer buffering Http2String of length "
+             << value_.size();
+    buffer_.assign(value_.data(), value_.size());
+    if (state_ == State::COMPLETE) {
+      value_ = buffer_;
+    }
+    backing_ = Backing::BUFFERED;
+  }
+}
+
+bool HpackDecoderStringBuffer::IsBuffered() const {
+  DVLOG(3) << "HpackDecoderStringBuffer::IsBuffered";
+  return state_ != State::RESET && backing_ == Backing::BUFFERED;
+}
+
+size_t HpackDecoderStringBuffer::BufferedLength() const {
+  DVLOG(3) << "HpackDecoderStringBuffer::BufferedLength";
+  return IsBuffered() ? buffer_.size() : 0;
+}
+
+Http2StringPiece HpackDecoderStringBuffer::str() const {
+  DVLOG(3) << "HpackDecoderStringBuffer::str";
+  DCHECK_EQ(state_, State::COMPLETE);
+  return value_;
+}
+
+Http2String HpackDecoderStringBuffer::ReleaseString() {
+  DVLOG(3) << "HpackDecoderStringBuffer::ReleaseString";
+  DCHECK_EQ(state_, State::COMPLETE);
+  DCHECK_EQ(backing_, Backing::BUFFERED);
+  if (state_ == State::COMPLETE) {
+    state_ = State::RESET;
+    if (backing_ == Backing::BUFFERED) {
+      return std::move(buffer_);
+    } else {
+      return Http2String(value_);
+    }
+  }
+  return "";
+}
+
+void HpackDecoderStringBuffer::OutputDebugStringTo(std::ostream& out) const {
+  out << "{state=" << state_;
+  if (state_ != State::RESET) {
+    out << ", backing=" << backing_;
+    out << ", remaining_len=" << remaining_len_;
+    out << ", is_huffman_encoded=" << is_huffman_encoded_;
+    if (backing_ == Backing::BUFFERED) {
+      out << ", buffer: " << buffer_;
+    } else {
+      out << ", value: " << value_;
+    }
+  }
+  out << "}";
+}
+
+size_t HpackDecoderStringBuffer::EstimateMemoryUsage() const {
+  return Http2EstimateMemoryUsage(buffer_);
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackDecoderStringBuffer& v) {
+  v.OutputDebugStringTo(out);
+  return out;
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_string_buffer.h b/http2/hpack/decoder/hpack_decoder_string_buffer.h
new file mode 100644
index 0000000..8a810b2
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_string_buffer.h
@@ -0,0 +1,102 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_
+
+// HpackDecoderStringBuffer helps an HPACK decoder to avoid copies of a string
+// literal (name or value) except when necessary (e.g. when split across two
+// or more HPACK block fragments).
+
+#include <stddef.h>
+
+#include <ostream>
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderStringBuffer {
+ public:
+  enum class State : uint8_t { RESET, COLLECTING, COMPLETE };
+  enum class Backing : uint8_t { RESET, UNBUFFERED, BUFFERED, STATIC };
+
+  HpackDecoderStringBuffer();
+  ~HpackDecoderStringBuffer();
+
+  HpackDecoderStringBuffer(const HpackDecoderStringBuffer&) = delete;
+  HpackDecoderStringBuffer& operator=(const HpackDecoderStringBuffer&) = delete;
+
+  void Reset();
+  void Set(Http2StringPiece value, bool is_static);
+
+  // Note that for Huffman encoded strings the length of the string after
+  // decoding may be larger (expected), the same or even smaller; the latter
+  // are unlikely, but possible if the encoder makes odd choices.
+  void OnStart(bool huffman_encoded, size_t len);
+  bool OnData(const char* data, size_t len);
+  bool OnEnd();
+  void BufferStringIfUnbuffered();
+  bool IsBuffered() const;
+  size_t BufferedLength() const;
+
+  // Accessors for the completely collected string (i.e. Set or OnEnd has just
+  // been called, and no reset of the state has occurred).
+
+  // Returns a Http2StringPiece pointing to the backing store for the string,
+  // either the internal buffer or the original transport buffer (e.g. for a
+  // literal value that wasn't Huffman encoded, and that wasn't split across
+  // transport buffers).
+  Http2StringPiece str() const;
+
+  // Returns the completely collected string by value, using std::move in an
+  // effort to avoid unnecessary copies. ReleaseString() must not be called
+  // unless the string has been buffered (to avoid forcing a potentially
+  // unnecessary copy). ReleaseString() also resets the instance so that it can
+  // be used to collect another string.
+  Http2String ReleaseString();
+
+  State state_for_testing() const { return state_; }
+  Backing backing_for_testing() const { return backing_; }
+  void OutputDebugStringTo(std::ostream& out) const;
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+ private:
+  // Storage for the string being buffered, if buffering is necessary
+  // (e.g. if Huffman encoded, buffer_ is storage for the decoded string).
+  Http2String buffer_;
+
+  // The Http2StringPiece to be returned by HpackDecoderStringBuffer::str(). If
+  // a string has been collected, but not buffered, value_ points to that
+  // string.
+  Http2StringPiece value_;
+
+  // The decoder to use if the string is Huffman encoded.
+  HpackHuffmanDecoder decoder_;
+
+  // Count of bytes not yet passed to OnData.
+  size_t remaining_len_;
+
+  // Is the HPACK string Huffman encoded?
+  bool is_huffman_encoded_;
+
+  // State of the string decoding process.
+  State state_;
+
+  // Where is the string stored?
+  Backing backing_;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(
+    std::ostream& out,
+    const HpackDecoderStringBuffer& v);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_
diff --git a/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc b/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc
new file mode 100644
index 0000000..009b894
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc
@@ -0,0 +1,251 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+
+// Tests of HpackDecoderStringBuffer.
+
+#include <initializer_list>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::HasSubstr;
+
+namespace http2 {
+namespace test {
+namespace {
+
+class HpackDecoderStringBufferTest : public ::testing::Test {
+ protected:
+  typedef HpackDecoderStringBuffer::State State;
+  typedef HpackDecoderStringBuffer::Backing Backing;
+
+  State state() const { return buf_.state_for_testing(); }
+  Backing backing() const { return buf_.backing_for_testing(); }
+
+  // We want to know that LOG(x) << buf_ will work in production should that
+  // be needed, so we test that it outputs the expected values.
+  AssertionResult VerifyLogHasSubstrs(std::initializer_list<Http2String> strs) {
+    VLOG(1) << buf_;
+    std::ostringstream ss;
+    buf_.OutputDebugStringTo(ss);
+    Http2String dbg_str(ss.str());
+    for (const auto& expected : strs) {
+      VERIFY_THAT(dbg_str, HasSubstr(expected));
+    }
+    return AssertionSuccess();
+  }
+
+  HpackDecoderStringBuffer buf_;
+};
+
+TEST_F(HpackDecoderStringBufferTest, SetStatic) {
+  Http2StringPiece data("static string");
+
+  EXPECT_EQ(state(), State::RESET);
+  EXPECT_TRUE(VerifyLogHasSubstrs({"state=RESET"}));
+
+  buf_.Set(data, /*is_static*/ true);
+  LOG(INFO) << buf_;
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::STATIC);
+  EXPECT_EQ(data, buf_.str());
+  EXPECT_EQ(data.data(), buf_.str().data());
+  EXPECT_TRUE(VerifyLogHasSubstrs(
+      {"state=COMPLETE", "backing=STATIC", "value: static string"}));
+
+  // The string is static, so BufferStringIfUnbuffered won't change anything.
+  buf_.BufferStringIfUnbuffered();
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::STATIC);
+  EXPECT_EQ(data, buf_.str());
+  EXPECT_EQ(data.data(), buf_.str().data());
+  EXPECT_TRUE(VerifyLogHasSubstrs(
+      {"state=COMPLETE", "backing=STATIC", "value: static string"}));
+}
+
+TEST_F(HpackDecoderStringBufferTest, PlainWhole) {
+  Http2StringPiece data("some text.");
+
+  LOG(INFO) << buf_;
+  EXPECT_EQ(state(), State::RESET);
+
+  buf_.OnStart(/*huffman_encoded*/ false, data.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::RESET);
+  LOG(INFO) << buf_;
+
+  EXPECT_TRUE(buf_.OnData(data.data(), data.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::UNBUFFERED);
+
+  EXPECT_TRUE(buf_.OnEnd());
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::UNBUFFERED);
+  EXPECT_EQ(0u, buf_.BufferedLength());
+  EXPECT_TRUE(VerifyLogHasSubstrs(
+      {"state=COMPLETE", "backing=UNBUFFERED", "value: some text."}));
+
+  // We expect that the string buffer points to the passed in Http2StringPiece's
+  // backing store.
+  EXPECT_EQ(data.data(), buf_.str().data());
+
+  // Now force it to buffer the string, after which it will still have the same
+  // string value, but the backing store will be different.
+  buf_.BufferStringIfUnbuffered();
+  LOG(INFO) << buf_;
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), data.size());
+  EXPECT_EQ(data, buf_.str());
+  EXPECT_NE(data.data(), buf_.str().data());
+  EXPECT_TRUE(VerifyLogHasSubstrs(
+      {"state=COMPLETE", "backing=BUFFERED", "buffer: some text."}));
+}
+
+TEST_F(HpackDecoderStringBufferTest, PlainSplit) {
+  Http2StringPiece data("some text.");
+  Http2StringPiece part1 = data.substr(0, 1);
+  Http2StringPiece part2 = data.substr(1);
+
+  EXPECT_EQ(state(), State::RESET);
+  buf_.OnStart(/*huffman_encoded*/ false, data.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::RESET);
+
+  // OnData with only a part of the data, not the whole, so buf_ will buffer
+  // the data.
+  EXPECT_TRUE(buf_.OnData(part1.data(), part1.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), part1.size());
+  LOG(INFO) << buf_;
+
+  EXPECT_TRUE(buf_.OnData(part2.data(), part2.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), data.size());
+
+  EXPECT_TRUE(buf_.OnEnd());
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), data.size());
+  LOG(INFO) << buf_;
+
+  Http2StringPiece buffered = buf_.str();
+  EXPECT_EQ(data, buffered);
+  EXPECT_NE(data.data(), buffered.data());
+
+  // The string is already buffered, so BufferStringIfUnbuffered should not make
+  // any change.
+  buf_.BufferStringIfUnbuffered();
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), data.size());
+  EXPECT_EQ(buffered, buf_.str());
+  EXPECT_EQ(buffered.data(), buf_.str().data());
+}
+
+TEST_F(HpackDecoderStringBufferTest, HuffmanWhole) {
+  Http2String encoded = Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff");
+  Http2StringPiece decoded("www.example.com");
+
+  EXPECT_EQ(state(), State::RESET);
+  buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+
+  EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+
+  EXPECT_TRUE(buf_.OnEnd());
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), decoded.size());
+  EXPECT_EQ(decoded, buf_.str());
+  EXPECT_TRUE(VerifyLogHasSubstrs(
+      {"{state=COMPLETE", "backing=BUFFERED", "buffer: www.example.com}"}));
+
+  Http2String s = buf_.ReleaseString();
+  EXPECT_EQ(s, decoded);
+  EXPECT_EQ(state(), State::RESET);
+}
+
+TEST_F(HpackDecoderStringBufferTest, HuffmanSplit) {
+  Http2String encoded = Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff");
+  Http2String part1 = encoded.substr(0, 5);
+  Http2String part2 = encoded.substr(5);
+  Http2StringPiece decoded("www.example.com");
+
+  EXPECT_EQ(state(), State::RESET);
+  buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(0u, buf_.BufferedLength());
+  LOG(INFO) << buf_;
+
+  EXPECT_TRUE(buf_.OnData(part1.data(), part1.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_GT(buf_.BufferedLength(), 0u);
+  EXPECT_LT(buf_.BufferedLength(), decoded.size());
+  LOG(INFO) << buf_;
+
+  EXPECT_TRUE(buf_.OnData(part2.data(), part2.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), decoded.size());
+  LOG(INFO) << buf_;
+
+  EXPECT_TRUE(buf_.OnEnd());
+  EXPECT_EQ(state(), State::COMPLETE);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+  EXPECT_EQ(buf_.BufferedLength(), decoded.size());
+  EXPECT_EQ(decoded, buf_.str());
+  LOG(INFO) << buf_;
+
+  buf_.Reset();
+  EXPECT_EQ(state(), State::RESET);
+  LOG(INFO) << buf_;
+}
+
+TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnData) {
+  // Explicitly encode the End-of-String symbol, a no-no.
+  Http2String encoded = Http2HexDecode("ffffffff");
+
+  buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+
+  EXPECT_FALSE(buf_.OnData(encoded.data(), encoded.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+
+  LOG(INFO) << buf_;
+}
+
+TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnEnd) {
+  // Last byte of string doesn't end with prefix of End-of-String symbol.
+  Http2String encoded = Http2HexDecode("00");
+
+  buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+  EXPECT_EQ(state(), State::COLLECTING);
+
+  EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size()));
+  EXPECT_EQ(state(), State::COLLECTING);
+  EXPECT_EQ(backing(), Backing::BUFFERED);
+
+  EXPECT_FALSE(buf_.OnEnd());
+  LOG(INFO) << buf_;
+}
+
+// TODO(jamessynge): Add tests for ReleaseString().
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_tables.cc b/http2/hpack/decoder/hpack_decoder_tables.cc
new file mode 100644
index 0000000..5002ab1
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_tables.cc
@@ -0,0 +1,153 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+
+namespace http2 {
+namespace {
+
+std::vector<HpackStringPair>* MakeStaticTable() {
+  auto* ptr = new std::vector<HpackStringPair>();
+  ptr->reserve(kFirstDynamicTableIndex);
+  ptr->emplace_back("", "");
+
+#define STATIC_TABLE_ENTRY(name, value, index) \
+  DCHECK_EQ(ptr->size(), index);               \
+  ptr->emplace_back(name, value)
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc"
+
+#undef STATIC_TABLE_ENTRY
+
+  return ptr;
+}
+
+const std::vector<HpackStringPair>* GetStaticTable() {
+  static const std::vector<HpackStringPair>* const g_static_table =
+      MakeStaticTable();
+  return g_static_table;
+}
+
+}  // namespace
+
+HpackDecoderTablesDebugListener::HpackDecoderTablesDebugListener() = default;
+HpackDecoderTablesDebugListener::~HpackDecoderTablesDebugListener() = default;
+
+HpackDecoderStaticTable::HpackDecoderStaticTable(
+    const std::vector<HpackStringPair>* table)
+    : table_(table) {}
+
+HpackDecoderStaticTable::HpackDecoderStaticTable() : table_(GetStaticTable()) {}
+
+const HpackStringPair* HpackDecoderStaticTable::Lookup(size_t index) const {
+  if (0 < index && index < kFirstDynamicTableIndex) {
+    return &((*table_)[index]);
+  }
+  return nullptr;
+}
+
+HpackDecoderDynamicTable::HpackDecoderTableEntry::HpackDecoderTableEntry(
+    const HpackString& name,
+    const HpackString& value)
+    : HpackStringPair(name, value) {}
+
+HpackDecoderDynamicTable::HpackDecoderDynamicTable()
+    : insert_count_(kFirstDynamicTableIndex - 1), debug_listener_(nullptr) {}
+HpackDecoderDynamicTable::~HpackDecoderDynamicTable() = default;
+
+void HpackDecoderDynamicTable::DynamicTableSizeUpdate(size_t size_limit) {
+  DVLOG(3) << "HpackDecoderDynamicTable::DynamicTableSizeUpdate " << size_limit;
+  EnsureSizeNoMoreThan(size_limit);
+  DCHECK_LE(current_size_, size_limit);
+  size_limit_ = size_limit;
+}
+
+// TODO(jamessynge): Check somewhere before here that names received from the
+// peer are valid (e.g. are lower-case, no whitespace, etc.).
+bool HpackDecoderDynamicTable::Insert(const HpackString& name,
+                                      const HpackString& value) {
+  HpackDecoderTableEntry entry(name, value);
+  size_t entry_size = entry.size();
+  DVLOG(2) << "InsertEntry of size=" << entry_size << "\n     name: " << name
+           << "\n    value: " << value;
+  if (entry_size > size_limit_) {
+    DVLOG(2) << "InsertEntry: entry larger than table, removing "
+             << table_.size() << " entries, of total size " << current_size_
+             << " bytes.";
+    table_.clear();
+    current_size_ = 0;
+    return false;  // Not inserted because too large.
+  }
+  ++insert_count_;
+  if (debug_listener_ != nullptr) {
+    entry.time_added = debug_listener_->OnEntryInserted(entry, insert_count_);
+    DVLOG(2) << "OnEntryInserted returned time_added=" << entry.time_added
+             << " for insert_count_=" << insert_count_;
+  }
+  size_t insert_limit = size_limit_ - entry_size;
+  EnsureSizeNoMoreThan(insert_limit);
+  table_.push_front(entry);
+  current_size_ += entry_size;
+  DVLOG(2) << "InsertEntry: current_size_=" << current_size_;
+  DCHECK_GE(current_size_, entry_size);
+  DCHECK_LE(current_size_, size_limit_);
+  return true;
+}
+
+const HpackStringPair* HpackDecoderDynamicTable::Lookup(size_t index) const {
+  if (index < table_.size()) {
+    const HpackDecoderTableEntry& entry = table_[index];
+    if (debug_listener_ != nullptr) {
+      size_t insert_count_of_index = insert_count_ + table_.size() - index;
+      debug_listener_->OnUseEntry(entry, insert_count_of_index,
+                                  entry.time_added);
+    }
+    return &entry;
+  }
+  return nullptr;
+}
+
+void HpackDecoderDynamicTable::EnsureSizeNoMoreThan(size_t limit) {
+  DVLOG(2) << "EnsureSizeNoMoreThan limit=" << limit
+           << ", current_size_=" << current_size_;
+  // Not the most efficient choice, but any easy way to start.
+  while (current_size_ > limit) {
+    RemoveLastEntry();
+  }
+  DCHECK_LE(current_size_, limit);
+}
+
+void HpackDecoderDynamicTable::RemoveLastEntry() {
+  DCHECK(!table_.empty());
+  if (!table_.empty()) {
+    DVLOG(2) << "RemoveLastEntry current_size_=" << current_size_
+             << ", last entry size=" << table_.back().size();
+    DCHECK_GE(current_size_, table_.back().size());
+    current_size_ -= table_.back().size();
+    table_.pop_back();
+    // Empty IFF current_size_ == 0.
+    DCHECK_EQ(table_.empty(), current_size_ == 0);
+  }
+}
+
+HpackDecoderTables::HpackDecoderTables() = default;
+HpackDecoderTables::~HpackDecoderTables() = default;
+
+void HpackDecoderTables::set_debug_listener(
+    HpackDecoderTablesDebugListener* debug_listener) {
+  dynamic_table_.set_debug_listener(debug_listener);
+}
+
+const HpackStringPair* HpackDecoderTables::Lookup(size_t index) const {
+  if (index < kFirstDynamicTableIndex) {
+    return static_table_.Lookup(index);
+  } else {
+    return dynamic_table_.Lookup(index - kFirstDynamicTableIndex);
+  }
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_tables.h b/http2/hpack/decoder/hpack_decoder_tables.h
new file mode 100644
index 0000000..6df145c
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_tables.h
@@ -0,0 +1,197 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
+
+// Static and dynamic tables for the HPACK decoder. See:
+// http://httpwg.org/specs/rfc7541.html#indexing.tables
+
+// Note that the Lookup methods return nullptr if the requested index was not
+// found. This should be treated as a COMPRESSION error according to the HTTP/2
+// spec, which is a connection level protocol error (i.e. the connection must
+// be terminated). See these sections in the two RFCs:
+// http://httpwg.org/specs/rfc7541.html#indexed.header.representation
+// http://httpwg.org/specs/rfc7541.html#index.address.space
+// http://httpwg.org/specs/rfc7540.html#HeaderBlock
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <vector>
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_containers.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class HpackDecoderTablesPeer;
+}  // namespace test
+
+// HpackDecoderTablesDebugListener supports a QUIC experiment, enabling
+// the gathering of information about the time-line of use of HPACK
+// dynamic table entries.
+class HTTP2_EXPORT_PRIVATE HpackDecoderTablesDebugListener {
+ public:
+  HpackDecoderTablesDebugListener();
+  virtual ~HpackDecoderTablesDebugListener();
+
+  HpackDecoderTablesDebugListener(const HpackDecoderTablesDebugListener&) =
+      delete;
+  HpackDecoderTablesDebugListener& operator=(
+      const HpackDecoderTablesDebugListener&) = delete;
+
+  // The entry has been inserted into the dynamic table. insert_count starts at
+  // 62 because 61 is the last index in the static table; insert_count increases
+  // by 1 with each insert into the dynamic table; it is not incremented when
+  // when a entry is too large to fit into the dynamic table at all (which has
+  // the effect of emptying the dynamic table).
+  // Returns a value that can be used as time_added in OnUseEntry.
+  virtual int64_t OnEntryInserted(const HpackStringPair& entry,
+                                  size_t insert_count) = 0;
+
+  // The entry has been used, either for the name or for the name and value.
+  // insert_count is the same as passed to OnEntryInserted when entry was
+  // inserted to the dynamic table, and time_added is the value that was
+  // returned by OnEntryInserted.
+  virtual void OnUseEntry(const HpackStringPair& entry,
+                          size_t insert_count,
+                          int64_t time_added) = 0;
+};
+
+// See http://httpwg.org/specs/rfc7541.html#static.table.definition for the
+// contents, and http://httpwg.org/specs/rfc7541.html#index.address.space for
+// info about accessing the static table.
+class HTTP2_EXPORT_PRIVATE HpackDecoderStaticTable {
+ public:
+  explicit HpackDecoderStaticTable(const std::vector<HpackStringPair>* table);
+  // Uses a global table shared by all threads.
+  HpackDecoderStaticTable();
+
+  // If index is valid, returns a pointer to the entry, otherwise returns
+  // nullptr.
+  const HpackStringPair* Lookup(size_t index) const;
+
+ private:
+  friend class test::HpackDecoderTablesPeer;
+  const std::vector<HpackStringPair>* const table_;
+};
+
+// HpackDecoderDynamicTable implements HPACK compression feature "indexed
+// headers"; previously sent headers may be referenced later by their index
+// in the dynamic table. See these sections of the RFC:
+//   http://httpwg.org/specs/rfc7541.html#dynamic.table
+//   http://httpwg.org/specs/rfc7541.html#dynamic.table.management
+class HTTP2_EXPORT_PRIVATE HpackDecoderDynamicTable {
+ public:
+  HpackDecoderDynamicTable();
+  ~HpackDecoderDynamicTable();
+
+  HpackDecoderDynamicTable(const HpackDecoderDynamicTable&) = delete;
+  HpackDecoderDynamicTable& operator=(const HpackDecoderDynamicTable&) = delete;
+
+  // Set the listener to be notified of insertions into this table, and later
+  // uses of those entries. Added for evaluation of changes to QUIC's use
+  // of HPACK.
+  void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener) {
+    debug_listener_ = debug_listener;
+  }
+
+  // Sets a new size limit, received from the peer; performs evictions if
+  // necessary to ensure that the current size does not exceed the new limit.
+  // The caller needs to have validated that size_limit does not
+  // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
+  void DynamicTableSizeUpdate(size_t size_limit);
+
+  // Returns true if inserted, false if too large (at which point the
+  // dynamic table will be empty.)
+  bool Insert(const HpackString& name, const HpackString& value);
+
+  // If index is valid, returns a pointer to the entry, otherwise returns
+  // nullptr.
+  const HpackStringPair* Lookup(size_t index) const;
+
+  size_t size_limit() const { return size_limit_; }
+  size_t current_size() const { return current_size_; }
+
+ private:
+  friend class test::HpackDecoderTablesPeer;
+  struct HpackDecoderTableEntry : public HpackStringPair {
+    HpackDecoderTableEntry(const HpackString& name, const HpackString& value);
+    int64_t time_added;
+  };
+
+  // Drop older entries to ensure the size is not greater than limit.
+  void EnsureSizeNoMoreThan(size_t limit);
+
+  // Removes the oldest dynamic table entry.
+  void RemoveLastEntry();
+
+  Http2Deque<HpackDecoderTableEntry> table_;
+
+  // The last received DynamicTableSizeUpdate value, initialized to
+  // SETTINGS_HEADER_TABLE_SIZE.
+  size_t size_limit_ = Http2SettingsInfo::DefaultHeaderTableSize();
+
+  size_t current_size_ = 0;
+
+  // insert_count_ and debug_listener_ are used by a QUIC experiment; remove
+  // when the experiment is done.
+  size_t insert_count_;
+  HpackDecoderTablesDebugListener* debug_listener_;
+};
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderTables {
+ public:
+  HpackDecoderTables();
+  ~HpackDecoderTables();
+
+  HpackDecoderTables(const HpackDecoderTables&) = delete;
+  HpackDecoderTables& operator=(const HpackDecoderTables&) = delete;
+
+  // Set the listener to be notified of insertions into the dynamic table, and
+  // later uses of those entries. Added for evaluation of changes to QUIC's use
+  // of HPACK.
+  void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener);
+
+  // Sets a new size limit, received from the peer; performs evictions if
+  // necessary to ensure that the current size does not exceed the new limit.
+  // The caller needs to have validated that size_limit does not
+  // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
+  void DynamicTableSizeUpdate(size_t size_limit) {
+    dynamic_table_.DynamicTableSizeUpdate(size_limit);
+  }
+
+  // Returns true if inserted, false if too large (at which point the
+  // dynamic table will be empty.)
+  // TODO(jamessynge): Add methods for moving the string(s) into the table,
+  // or for otherwise avoiding unnecessary copies.
+  bool Insert(const HpackString& name, const HpackString& value) {
+    return dynamic_table_.Insert(name, value);
+  }
+
+  // If index is valid, returns a pointer to the entry, otherwise returns
+  // nullptr.
+  const HpackStringPair* Lookup(size_t index) const;
+
+  // The size limit that the peer (the HPACK encoder) has told the decoder it is
+  // currently operating with. Defaults to SETTINGS_HEADER_TABLE_SIZE, 4096.
+  size_t header_table_size_limit() const { return dynamic_table_.size_limit(); }
+
+  // Sum of the sizes of the dynamic table entries.
+  size_t current_header_table_size() const {
+    return dynamic_table_.current_size();
+  }
+
+ private:
+  friend class test::HpackDecoderTablesPeer;
+  HpackDecoderStaticTable static_table_;
+  HpackDecoderDynamicTable dynamic_table_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
diff --git a/http2/hpack/decoder/hpack_decoder_tables_test.cc b/http2/hpack/decoder/hpack_decoder_tables_test.cc
new file mode 100644
index 0000000..370c1a5
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_tables_test.cc
@@ -0,0 +1,266 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+
+#include <algorithm>
+#include <tuple>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_util.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+class HpackDecoderTablesPeer {
+ public:
+  static size_t num_dynamic_entries(const HpackDecoderTables& tables) {
+    return tables.dynamic_table_.table_.size();
+  }
+};
+
+namespace {
+struct StaticEntry {
+  const char* name;
+  const char* value;
+  size_t index;
+};
+
+std::vector<StaticEntry> MakeSpecStaticEntries() {
+  std::vector<StaticEntry> static_entries;
+
+#define STATIC_TABLE_ENTRY(name, value, index) \
+  DCHECK_EQ(static_entries.size() + 1, index); \
+  static_entries.push_back({name, value, index});
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc"
+
+#undef STATIC_TABLE_ENTRY
+
+  return static_entries;
+}
+
+template <class C>
+void ShuffleCollection(C* collection, Http2Random* r) {
+  std::shuffle(collection->begin(), collection->end(), *r);
+}
+
+class HpackDecoderStaticTableTest : public ::testing::Test {
+ protected:
+  HpackDecoderStaticTableTest() = default;
+
+  std::vector<StaticEntry> shuffled_static_entries() {
+    std::vector<StaticEntry> entries = MakeSpecStaticEntries();
+    ShuffleCollection(&entries, &random_);
+    return entries;
+  }
+
+  // This test is in a function so that it can be applied to both the static
+  // table and the combined static+dynamic tables.
+  AssertionResult VerifyStaticTableContents() {
+    for (const auto& expected : shuffled_static_entries()) {
+      const HpackStringPair* found = Lookup(expected.index);
+      VERIFY_NE(found, nullptr);
+      VERIFY_EQ(expected.name, found->name) << expected.index;
+      VERIFY_EQ(expected.value, found->value) << expected.index;
+    }
+
+    // There should be no entry with index 0.
+    VERIFY_EQ(nullptr, Lookup(0));
+    return AssertionSuccess();
+  }
+
+  virtual const HpackStringPair* Lookup(size_t index) {
+    return static_table_.Lookup(index);
+  }
+
+  Http2Random* RandomPtr() { return &random_; }
+
+  Http2Random random_;
+
+ private:
+  HpackDecoderStaticTable static_table_;
+};
+
+TEST_F(HpackDecoderStaticTableTest, StaticTableContents) {
+  EXPECT_TRUE(VerifyStaticTableContents());
+}
+
+size_t Size(const Http2String& name, const Http2String& value) {
+  return name.size() + value.size() + 32;
+}
+
+// To support tests with more than a few of hand crafted changes to the dynamic
+// table, we have another, exceedingly simple, implementation of the HPACK
+// dynamic table containing FakeHpackEntry instances. We can thus compare the
+// contents of the actual table with those in fake_dynamic_table_.
+
+typedef std::tuple<Http2String, Http2String, size_t> FakeHpackEntry;
+const Http2String& Name(const FakeHpackEntry& entry) {
+  return std::get<0>(entry);
+}
+const Http2String& Value(const FakeHpackEntry& entry) {
+  return std::get<1>(entry);
+}
+size_t Size(const FakeHpackEntry& entry) {
+  return std::get<2>(entry);
+}
+
+class HpackDecoderTablesTest : public HpackDecoderStaticTableTest {
+ protected:
+  const HpackStringPair* Lookup(size_t index) override {
+    return tables_.Lookup(index);
+  }
+
+  size_t dynamic_size_limit() const {
+    return tables_.header_table_size_limit();
+  }
+  size_t current_dynamic_size() const {
+    return tables_.current_header_table_size();
+  }
+  size_t num_dynamic_entries() const {
+    return HpackDecoderTablesPeer::num_dynamic_entries(tables_);
+  }
+
+  // Insert the name and value into fake_dynamic_table_.
+  void FakeInsert(const Http2String& name, const Http2String& value) {
+    FakeHpackEntry entry(name, value, Size(name, value));
+    fake_dynamic_table_.insert(fake_dynamic_table_.begin(), entry);
+  }
+
+  // Add up the size of all entries in fake_dynamic_table_.
+  size_t FakeSize() {
+    size_t sz = 0;
+    for (const auto& entry : fake_dynamic_table_) {
+      sz += Size(entry);
+    }
+    return sz;
+  }
+
+  // If the total size of the fake_dynamic_table_ is greater than limit,
+  // keep the first N entries such that those N entries have a size not
+  // greater than limit, and such that keeping entry N+1 would have a size
+  // greater than limit. Returns the count of removed bytes.
+  size_t FakeTrim(size_t limit) {
+    size_t original_size = FakeSize();
+    size_t total_size = 0;
+    for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) {
+      total_size += Size(fake_dynamic_table_[ndx]);
+      if (total_size > limit) {
+        // Need to get rid of ndx and all following entries.
+        fake_dynamic_table_.erase(fake_dynamic_table_.begin() + ndx,
+                                  fake_dynamic_table_.end());
+        return original_size - FakeSize();
+      }
+    }
+    return 0;
+  }
+
+  // Verify that the contents of the actual dynamic table match those in
+  // fake_dynamic_table_.
+  AssertionResult VerifyDynamicTableContents() {
+    VERIFY_EQ(current_dynamic_size(), FakeSize());
+    VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size());
+
+    for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) {
+      const HpackStringPair* found = Lookup(ndx + kFirstDynamicTableIndex);
+      VERIFY_NE(found, nullptr);
+
+      const auto& expected = fake_dynamic_table_[ndx];
+      VERIFY_EQ(Name(expected), found->name);
+      VERIFY_EQ(Value(expected), found->value);
+    }
+
+    // Make sure there are no more entries.
+    VERIFY_EQ(nullptr,
+              Lookup(fake_dynamic_table_.size() + kFirstDynamicTableIndex));
+    return AssertionSuccess();
+  }
+
+  // Apply an update to the limit on the maximum size of the dynamic table.
+  AssertionResult DynamicTableSizeUpdate(size_t size_limit) {
+    VERIFY_EQ(current_dynamic_size(), FakeSize());
+    if (size_limit < current_dynamic_size()) {
+      // Will need to trim the dynamic table's oldest entries.
+      tables_.DynamicTableSizeUpdate(size_limit);
+      FakeTrim(size_limit);
+      return VerifyDynamicTableContents();
+    }
+    // Shouldn't change the size.
+    tables_.DynamicTableSizeUpdate(size_limit);
+    return VerifyDynamicTableContents();
+  }
+
+  // Insert an entry into the dynamic table, confirming that trimming of entries
+  // occurs if the total size is greater than the limit, and that older entries
+  // move up by 1 index.
+  AssertionResult Insert(const Http2String& name, const Http2String& value) {
+    size_t old_count = num_dynamic_entries();
+    if (tables_.Insert(HpackString(name), HpackString(value))) {
+      VERIFY_GT(current_dynamic_size(), 0u);
+      VERIFY_GT(num_dynamic_entries(), 0u);
+    } else {
+      VERIFY_EQ(current_dynamic_size(), 0u);
+      VERIFY_EQ(num_dynamic_entries(), 0u);
+    }
+    FakeInsert(name, value);
+    VERIFY_EQ(old_count + 1, fake_dynamic_table_.size());
+    FakeTrim(dynamic_size_limit());
+    VERIFY_EQ(current_dynamic_size(), FakeSize());
+    VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size());
+    return VerifyDynamicTableContents();
+  }
+
+ private:
+  HpackDecoderTables tables_;
+
+  std::vector<FakeHpackEntry> fake_dynamic_table_;
+};
+
+TEST_F(HpackDecoderTablesTest, StaticTableContents) {
+  EXPECT_TRUE(VerifyStaticTableContents());
+}
+
+// Generate a bunch of random header entries, insert them, and confirm they
+// present, as required by the RFC, using VerifyDynamicTableContents above on
+// each Insert. Also apply various resizings of the dynamic table.
+TEST_F(HpackDecoderTablesTest, RandomDynamicTable) {
+  EXPECT_EQ(0u, current_dynamic_size());
+  EXPECT_TRUE(VerifyStaticTableContents());
+  EXPECT_TRUE(VerifyDynamicTableContents());
+
+  std::vector<size_t> table_sizes;
+  table_sizes.push_back(dynamic_size_limit());
+  table_sizes.push_back(0);
+  table_sizes.push_back(dynamic_size_limit() / 2);
+  table_sizes.push_back(dynamic_size_limit());
+  table_sizes.push_back(dynamic_size_limit() / 2);
+  table_sizes.push_back(0);
+  table_sizes.push_back(dynamic_size_limit());
+
+  for (size_t limit : table_sizes) {
+    ASSERT_TRUE(DynamicTableSizeUpdate(limit));
+    for (int insert_count = 0; insert_count < 100; ++insert_count) {
+      Http2String name =
+          GenerateHttp2HeaderName(random_.UniformInRange(2, 40), RandomPtr());
+      Http2String value =
+          GenerateWebSafeString(random_.UniformInRange(2, 600), RandomPtr());
+      ASSERT_TRUE(Insert(name, value));
+    }
+    EXPECT_TRUE(VerifyStaticTableContents());
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_test.cc b/http2/hpack/decoder/hpack_decoder_test.cc
new file mode 100644
index 0000000..563e977
--- /dev/null
+++ b/http2/hpack/decoder/hpack_decoder_test.cc
@@ -0,0 +1,1219 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h"
+
+// Tests of HpackDecoder.
+
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_util.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::ElementsAreArray;
+using ::testing::HasSubstr;
+
+namespace http2 {
+namespace test {
+class HpackDecoderStatePeer {
+ public:
+  static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) {
+    return &state->decoder_tables_;
+  }
+  static void set_listener(HpackDecoderState* state,
+                           HpackDecoderListener* listener) {
+    state->listener_ = listener;
+  }
+};
+class HpackDecoderPeer {
+ public:
+  static HpackDecoderState* GetDecoderState(HpackDecoder* decoder) {
+    return &decoder->decoder_state_;
+  }
+  static HpackDecoderTables* GetDecoderTables(HpackDecoder* decoder) {
+    return HpackDecoderStatePeer::GetDecoderTables(GetDecoderState(decoder));
+  }
+};
+
+namespace {
+
+typedef std::tuple<HpackEntryType, Http2String, Http2String> HpackHeaderEntry;
+typedef std::vector<HpackHeaderEntry> HpackHeaderEntries;
+
+// TODO(jamessynge): Create a ...test_utils.h file with the mock listener
+// and with VerifyDynamicTableContents.
+class MockHpackDecoderListener : public HpackDecoderListener {
+ public:
+  MOCK_METHOD0(OnHeaderListStart, void());
+  MOCK_METHOD3(OnHeader,
+               void(HpackEntryType entry_type,
+                    const HpackString& name,
+                    const HpackString& value));
+  MOCK_METHOD0(OnHeaderListEnd, void());
+  MOCK_METHOD1(OnHeaderErrorDetected, void(Http2StringPiece error_message));
+};
+
+class HpackDecoderTest : public ::testing::TestWithParam<bool>,
+                         public HpackDecoderListener {
+ protected:
+  // Note that we initialize the random number generator with the same seed
+  // for each individual test, therefore the order in which the tests are
+  // executed does not effect the sequence produced by the RNG within any
+  // one test.
+  HpackDecoderTest() : decoder_(this, 4096) {
+    fragment_the_hpack_block_ = GetParam();
+  }
+  ~HpackDecoderTest() override = default;
+
+  void OnHeaderListStart() override {
+    ASSERT_FALSE(saw_start_);
+    ASSERT_FALSE(saw_end_);
+    saw_start_ = true;
+    header_entries_.clear();
+  }
+
+  // Called for each header name-value pair that is decoded, in the order they
+  // appear in the HPACK block. Multiple values for a given key will be emitted
+  // as multiple calls to OnHeader.
+  void OnHeader(HpackEntryType entry_type,
+                const HpackString& name,
+                const HpackString& value) override {
+    ASSERT_TRUE(saw_start_);
+    ASSERT_FALSE(saw_end_);
+    //     header_entries_.push_back({entry_type, name.ToString(),
+    //     value.ToString()});
+    header_entries_.emplace_back(entry_type, name.ToString(), value.ToString());
+  }
+
+  // OnHeaderBlockEnd is called after successfully decoding an HPACK block. Will
+  // only be called once per block, even if it extends into CONTINUATION frames.
+  // A callback method which notifies when the parser finishes handling a
+  // header block (i.e. the containing frame has the END_STREAM flag set).
+  // Also indicates the total number of bytes in this block.
+  void OnHeaderListEnd() override {
+    ASSERT_TRUE(saw_start_);
+    ASSERT_FALSE(saw_end_);
+    ASSERT_TRUE(error_messages_.empty());
+    saw_end_ = true;
+  }
+
+  // OnHeaderErrorDetected is called if an error is detected while decoding.
+  // error_message may be used in a GOAWAY frame as the Opaque Data.
+  void OnHeaderErrorDetected(Http2StringPiece error_message) override {
+    ASSERT_TRUE(saw_start_);
+    error_messages_.push_back(Http2String(error_message));
+    // No further callbacks should be made at this point, so replace 'this' as
+    // the listener with mock_listener_, which is a strict mock, so will
+    // generate an error for any calls.
+    HpackDecoderStatePeer::set_listener(
+        HpackDecoderPeer::GetDecoderState(&decoder_), &mock_listener_);
+  }
+
+  AssertionResult DecodeBlock(Http2StringPiece block) {
+    VLOG(1) << "HpackDecoderTest::DecodeBlock";
+
+    VERIFY_FALSE(decoder_.error_detected());
+    VERIFY_TRUE(error_messages_.empty());
+    VERIFY_FALSE(saw_start_);
+    VERIFY_FALSE(saw_end_);
+    header_entries_.clear();
+
+    VERIFY_FALSE(decoder_.error_detected());
+    VERIFY_TRUE(decoder_.StartDecodingBlock());
+    VERIFY_FALSE(decoder_.error_detected());
+
+    if (fragment_the_hpack_block_) {
+      // See note in ctor regarding RNG.
+      while (!block.empty()) {
+        size_t fragment_size = random_.RandomSizeSkewedLow(block.size());
+        DecodeBuffer db(block.substr(0, fragment_size));
+        VERIFY_TRUE(decoder_.DecodeFragment(&db));
+        VERIFY_EQ(0u, db.Remaining());
+        block.remove_prefix(fragment_size);
+      }
+    } else {
+      DecodeBuffer db(block);
+      VERIFY_TRUE(decoder_.DecodeFragment(&db));
+      VERIFY_EQ(0u, db.Remaining());
+    }
+    VERIFY_FALSE(decoder_.error_detected());
+
+    VERIFY_TRUE(decoder_.EndDecodingBlock());
+    if (saw_end_) {
+      VERIFY_FALSE(decoder_.error_detected());
+      VERIFY_TRUE(error_messages_.empty());
+    } else {
+      VERIFY_TRUE(decoder_.error_detected());
+      VERIFY_FALSE(error_messages_.empty());
+    }
+
+    saw_start_ = saw_end_ = false;
+    return AssertionSuccess();
+  }
+
+  const HpackDecoderTables& GetDecoderTables() {
+    return *HpackDecoderPeer::GetDecoderTables(&decoder_);
+  }
+  const HpackStringPair* Lookup(size_t index) {
+    return GetDecoderTables().Lookup(index);
+  }
+  size_t current_header_table_size() {
+    return GetDecoderTables().current_header_table_size();
+  }
+  size_t header_table_size_limit() {
+    return GetDecoderTables().header_table_size_limit();
+  }
+  void set_header_table_size_limit(size_t size) {
+    HpackDecoderPeer::GetDecoderTables(&decoder_)->DynamicTableSizeUpdate(size);
+  }
+
+  // dynamic_index is one-based, because that is the way RFC 7541 shows it.
+  AssertionResult VerifyEntry(size_t dynamic_index,
+                              const char* name,
+                              const char* value) {
+    const HpackStringPair* entry =
+        Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+    VERIFY_NE(entry, nullptr);
+    VERIFY_EQ(entry->name.ToStringPiece(), name);
+    VERIFY_EQ(entry->value.ToStringPiece(), value);
+    return AssertionSuccess();
+  }
+  AssertionResult VerifyNoEntry(size_t dynamic_index) {
+    const HpackStringPair* entry =
+        Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+    VERIFY_EQ(entry, nullptr);
+    return AssertionSuccess();
+  }
+  AssertionResult VerifyDynamicTableContents(
+      const std::vector<std::pair<const char*, const char*>>& entries) {
+    size_t index = 1;
+    for (const auto& entry : entries) {
+      VERIFY_SUCCESS(VerifyEntry(index, entry.first, entry.second));
+      ++index;
+    }
+    VERIFY_SUCCESS(VerifyNoEntry(index));
+    return AssertionSuccess();
+  }
+
+  Http2Random random_;
+  HpackDecoder decoder_;
+  testing::StrictMock<MockHpackDecoderListener> mock_listener_;
+  HpackHeaderEntries header_entries_;
+  std::vector<Http2String> error_messages_;
+  bool fragment_the_hpack_block_;
+  bool saw_start_ = false;
+  bool saw_end_ = false;
+};
+INSTANTIATE_TEST_CASE_P(AllWays, HpackDecoderTest, ::testing::Bool());
+
+// Test based on RFC 7541, section C.3: Request Examples without Huffman Coding.
+// This section shows several consecutive header lists, corresponding to HTTP
+// requests, on the same connection.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3
+TEST_P(HpackDecoderTest, C3_RequestExamples) {
+  // C.3.1 First Request
+  Http2String hpack_block = HpackExampleToStringOrDie(R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      86                                      | == Indexed - Add ==
+                                              |   idx = 6
+                                              | -> :scheme: http
+      84                                      | == Indexed - Add ==
+                                              |   idx = 4
+                                              | -> :path: /
+      41                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 1)
+                                              |     :authority
+      0f                                      |   Literal value (len = 15)
+      7777 772e 6578 616d 706c 652e 636f 6d   | www.example.com
+                                              | -> :authority:
+                                              |   www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, ":authority",
+                           "www.example.com"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  57) :authority: www.example.com
+  //         Table size:  57
+  ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}}));
+  ASSERT_EQ(57u, current_header_table_size());
+
+  // C.3.2 Second Request
+  hpack_block = HpackExampleToStringOrDie(R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      86                                      | == Indexed - Add ==
+                                              |   idx = 6
+                                              | -> :scheme: http
+      84                                      | == Indexed - Add ==
+                                              |   idx = 4
+                                              | -> :path: /
+      be                                      | == Indexed - Add ==
+                                              |   idx = 62
+                                              | -> :authority:
+                                              |   www.example.com
+      58                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 24)
+                                              |     cache-control
+      08                                      |   Literal value (len = 8)
+      6e6f 2d63 6163 6865                     | no-cache
+                                              | -> cache-control: no-cache
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+                           "www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                           "cache-control", "no-cache"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  53) cache-control: no-cache
+  //   [  2] (s =  57) :authority: www.example.com
+  //         Table size: 110
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"cache-control", "no-cache"}, {":authority", "www.example.com"}}));
+  ASSERT_EQ(110u, current_header_table_size());
+
+  // C.3.2 Third Request
+  hpack_block = HpackExampleToStringOrDie(R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      87                                      | == Indexed - Add ==
+                                              |   idx = 7
+                                              | -> :scheme: https
+      85                                      | == Indexed - Add ==
+                                              |   idx = 5
+                                              | -> :path: /index.html
+      bf                                      | == Indexed - Add ==
+                                              |   idx = 63
+                                              | -> :authority:
+                                              |   www.example.com
+      40                                      | == Literal indexed ==
+      0a                                      |   Literal name (len = 10)
+      6375 7374 6f6d 2d6b 6579                | custom-key
+      0c                                      |   Literal value (len = 12)
+      6375 7374 6f6d 2d76 616c 7565           | custom-value
+                                              | -> custom-key:
+                                              |   custom-value
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "https"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path",
+                           "/index.html"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+                           "www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "custom-key",
+                           "custom-value"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  54) custom-key: custom-value
+  //   [  2] (s =  53) cache-control: no-cache
+  //   [  3] (s =  57) :authority: www.example.com
+  //         Table size: 164
+  ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"},
+                                          {"cache-control", "no-cache"},
+                                          {":authority", "www.example.com"}}));
+  ASSERT_EQ(164u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.4 Request Examples with Huffman Coding.
+// This section shows the same examples as the previous section but uses
+// Huffman encoding for the literal values.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.4
+TEST_P(HpackDecoderTest, C4_RequestExamplesWithHuffmanEncoding) {
+  // C.4.1 First Request
+  Http2String hpack_block = HpackExampleToStringOrDie(R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      86                                      | == Indexed - Add ==
+                                              |   idx = 6
+                                              | -> :scheme: http
+      84                                      | == Indexed - Add ==
+                                              |   idx = 4
+                                              | -> :path: /
+      41                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 1)
+                                              |     :authority
+      8c                                      |   Literal value (len = 12)
+                                              |     Huffman encoded:
+      f1e3 c2e5 f23a 6ba0 ab90 f4ff           | .....:k.....
+                                              |     Decoded:
+                                              | www.example.com
+                                              | -> :authority:
+                                              |   www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, ":authority",
+                           "www.example.com"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  57) :authority: www.example.com
+  //         Table size:  57
+  ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}}));
+  ASSERT_EQ(57u, current_header_table_size());
+
+  // C.4.2 Second Request
+  hpack_block = HpackExampleToStringOrDie(R"(
+      82                                      | == Indexed - Add ==
+                                              |   idx = 2
+                                              | -> :method: GET
+      86                                      | == Indexed - Add ==
+                                              |   idx = 6
+                                              | -> :scheme: http
+      84                                      | == Indexed - Add ==
+                                              |   idx = 4
+                                              | -> :path: /
+      be                                      | == Indexed - Add ==
+                                              |   idx = 62
+                                              | -> :authority:
+                                              |   www.example.com
+      58                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 24)
+                                              |     cache-control
+      86                                      |   Literal value (len = 6)
+                                              |     Huffman encoded:
+      a8eb 1064 9cbf                          | ...d..
+                                              |     Decoded:
+                                              | no-cache
+                                              | -> cache-control: no-cache
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+                           "www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                           "cache-control", "no-cache"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  53) cache-control: no-cache
+  //   [  2] (s =  57) :authority: www.example.com
+  //         Table size: 110
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"cache-control", "no-cache"}, {":authority", "www.example.com"}}));
+  ASSERT_EQ(110u, current_header_table_size());
+
+  // C.4.2 Third Request
+  hpack_block = HpackExampleToStringOrDie(R"(
+    82                                      | == Indexed - Add ==
+                                            |   idx = 2
+                                            | -> :method: GET
+    87                                      | == Indexed - Add ==
+                                            |   idx = 7
+                                            | -> :scheme: https
+    85                                      | == Indexed - Add ==
+                                            |   idx = 5
+                                            | -> :path: /index.html
+    bf                                      | == Indexed - Add ==
+                                            |   idx = 63
+                                            | -> :authority:
+                                            |   www.example.com
+    40                                      | == Literal indexed ==
+    88                                      |   Literal name (len = 8)
+                                            |     Huffman encoded:
+    25a8 49e9 5ba9 7d7f                     | %.I.[.}.
+                                            |     Decoded:
+                                            | custom-key
+    89                                      |   Literal value (len = 9)
+                                            |     Huffman encoded:
+    25a8 49e9 5bb8 e8b4 bf                  | %.I.[....
+                                            |     Decoded:
+                                            | custom-value
+                                            | -> custom-key:
+                                            |   custom-value
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "https"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path",
+                           "/index.html"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+                           "www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "custom-key",
+                           "custom-value"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  54) custom-key: custom-value
+  //   [  2] (s =  53) cache-control: no-cache
+  //   [  3] (s =  57) :authority: www.example.com
+  //         Table size: 164
+  ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"},
+                                          {"cache-control", "no-cache"},
+                                          {":authority", "www.example.com"}}));
+  ASSERT_EQ(164u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.5: Response Examples without Huffman
+// Coding. This section shows several consecutive header lists, corresponding
+// to HTTP responses, on the same connection. The HTTP/2 setting parameter
+// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing
+// some evictions to occur.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.5
+TEST_P(HpackDecoderTest, C5_ResponseExamples) {
+  set_header_table_size_limit(256);
+
+  // C.5.1 First Response
+  //
+  // Header list to encode:
+  //
+  //   :status: 302
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+
+  Http2String hpack_block = HpackExampleToStringOrDie(R"(
+      48                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 8)
+                                              |     :status
+      03                                      |   Literal value (len = 3)
+      3330 32                                 | 302
+                                              | -> :status: 302
+      58                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 24)
+                                              |     cache-control
+      07                                      |   Literal value (len = 7)
+      7072 6976 6174 65                       | private
+                                              | -> cache-control: private
+      61                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 33)
+                                              |     date
+      1d                                      |   Literal value (len = 29)
+      4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+      2032 303a 3133 3a32 3120 474d 54        |  20:13:21 GMT
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:21 GMT
+      6e                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 46)
+                                              |     location
+      17                                      |   Literal value (len = 23)
+      6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam
+      706c 652e 636f 6d                       | ple.com
+                                              | -> location:
+                                              |   https://www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(header_entries_,
+              ElementsAreArray({
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   ":status", "302"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "cache-control", "private"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "location", "https://www.example.com"},
+              }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  63) location: https://www.example.com
+  //   [  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  3] (s =  52) cache-control: private
+  //   [  4] (s =  42) :status: 302
+  //         Table size: 222
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"},
+                                  {":status", "302"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.2 Second Response
+  //
+  // The (":status", "302") header field is evicted from the dynamic table to
+  // free space to allow adding the (":status", "307") header field.
+  //
+  // Header list to encode:
+  //
+  //   :status: 307
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+
+  hpack_block = HpackExampleToStringOrDie(R"(
+      48                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 8)
+                                              |     :status
+      03                                      |   Literal value (len = 3)
+      3330 37                                 | 307
+                                              | - evict: :status: 302
+                                              | -> :status: 307
+      c1                                      | == Indexed - Add ==
+                                              |   idx = 65
+                                              | -> cache-control: private
+      c0                                      | == Indexed - Add ==
+                                              |   idx = 64
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:21 GMT
+      bf                                      | == Indexed - Add ==
+                                              |   idx = 63
+                                              | -> location:
+                                              |   https://www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(header_entries_,
+              ElementsAreArray({
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   ":status", "307"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader,
+                                   "cache-control", "private"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader, "date",
+                                   "Mon, 21 Oct 2013 20:13:21 GMT"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+                                   "https://www.example.com"},
+              }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  42) :status: 307
+  //   [  2] (s =  63) location: https://www.example.com
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  4] (s =  52) cache-control: private
+  //         Table size: 222
+
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{":status", "307"},
+                                  {"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.3 Third Response
+  //
+  // Several header fields are evicted from the dynamic table during the
+  // processing of this header list.
+  //
+  // Header list to encode:
+  //
+  //   :status: 200
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:22 GMT
+  //   location: https://www.example.com
+  //   content-encoding: gzip
+  //   set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
+  hpack_block = HpackExampleToStringOrDie(R"(
+      88                                      | == Indexed - Add ==
+                                              |   idx = 8
+                                              | -> :status: 200
+      c1                                      | == Indexed - Add ==
+                                              |   idx = 65
+                                              | -> cache-control: private
+      61                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 33)
+                                              |     date
+      1d                                      |   Literal value (len = 29)
+      4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+      2032 303a 3133 3a32 3220 474d 54        |  20:13:22 GMT
+                                              | - evict: cache-control:
+                                              |   private
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:22 GMT
+      c0                                      | == Indexed - Add ==
+                                              |   idx = 64
+                                              | -> location:
+                                              |   https://www.example.com
+      5a                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 26)
+                                              |     content-encoding
+      04                                      |   Literal value (len = 4)
+      677a 6970                               | gzip
+                                              | - evict: date: Mon, 21 Oct
+                                              |    2013 20:13:21 GMT
+                                              | -> content-encoding: gzip
+      77                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 55)
+                                              |     set-cookie
+      38                                      |   Literal value (len = 56)
+      666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO
+      5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU;
+      206d 6178 2d61 6765 3d33 3630 303b 2076 |  max-age=3600; v
+      6572 7369 6f6e 3d31                     | ersion=1
+                                              | - evict: location:
+                                              |   https://www.example.com
+                                              | - evict: :status: 307
+                                              | -> set-cookie: foo=ASDJKHQ
+                                              |   KBZXOQWEOPIUAXQWEOIU; ma
+                                              |   x-age=3600; version=1
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":status", "200"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, "cache-control",
+                           "private"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "date",
+                           "Mon, 21 Oct 2013 20:13:22 GMT"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+                           "https://www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                           "content-encoding", "gzip"},
+          HpackHeaderEntry{
+              HpackEntryType::kIndexedLiteralHeader, "set-cookie",
+              "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
+  //                    max-age=3600; version=1
+  //   [  2] (s =  52) content-encoding: gzip
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT
+  //         Table size: 215
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"set-cookie",
+        "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+       {"content-encoding", "gzip"},
+       {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}}));
+  ASSERT_EQ(215u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.6: Response Examples with Huffman Coding.
+// This section shows the same examples as the previous section but uses Huffman
+// encoding for the literal values. The HTTP/2 setting parameter
+// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing some
+// evictions to occur. The eviction mechanism uses the length of the decoded
+// literal values, so the same evictions occur as in the previous section.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.6
+TEST_P(HpackDecoderTest, C6_ResponseExamplesWithHuffmanEncoding) {
+  set_header_table_size_limit(256);
+
+  // C.5.1 First Response
+  //
+  // Header list to encode:
+  //
+  //   :status: 302
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+  Http2String hpack_block = HpackExampleToStringOrDie(R"(
+      48                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 8)
+                                              |     :status
+      03                                      |   Literal value (len = 3)
+      3330 32                                 | 302
+                                              | -> :status: 302
+      58                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 24)
+                                              |     cache-control
+      07                                      |   Literal value (len = 7)
+      7072 6976 6174 65                       | private
+                                              | -> cache-control: private
+      61                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 33)
+                                              |     date
+      1d                                      |   Literal value (len = 29)
+      4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+      2032 303a 3133 3a32 3120 474d 54        |  20:13:21 GMT
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:21 GMT
+      6e                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 46)
+                                              |     location
+      17                                      |   Literal value (len = 23)
+      6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam
+      706c 652e 636f 6d                       | ple.com
+                                              | -> location:
+                                              |   https://www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(header_entries_,
+              ElementsAreArray({
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   ":status", "302"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "cache-control", "private"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   "location", "https://www.example.com"},
+              }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  63) location: https://www.example.com
+  //   [  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  3] (s =  52) cache-control: private
+  //   [  4] (s =  42) :status: 302
+  //         Table size: 222
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"},
+                                  {":status", "302"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.2 Second Response
+  //
+  // The (":status", "302") header field is evicted from the dynamic table to
+  // free space to allow adding the (":status", "307") header field.
+  //
+  // Header list to encode:
+  //
+  //   :status: 307
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   location: https://www.example.com
+  hpack_block = HpackExampleToStringOrDie(R"(
+      48                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 8)
+                                              |     :status
+      03                                      |   Literal value (len = 3)
+      3330 37                                 | 307
+                                              | - evict: :status: 302
+                                              | -> :status: 307
+      c1                                      | == Indexed - Add ==
+                                              |   idx = 65
+                                              | -> cache-control: private
+      c0                                      | == Indexed - Add ==
+                                              |   idx = 64
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:21 GMT
+      bf                                      | == Indexed - Add ==
+                                              |   idx = 63
+                                              | -> location:
+                                              |   https://www.example.com
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(header_entries_,
+              ElementsAreArray({
+                  HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                                   ":status", "307"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader,
+                                   "cache-control", "private"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader, "date",
+                                   "Mon, 21 Oct 2013 20:13:21 GMT"},
+                  HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+                                   "https://www.example.com"},
+              }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  42) :status: 307
+  //   [  2] (s =  63) location: https://www.example.com
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT
+  //   [  4] (s =  52) cache-control: private
+  //         Table size: 222
+  ASSERT_TRUE(
+      VerifyDynamicTableContents({{":status", "307"},
+                                  {"location", "https://www.example.com"},
+                                  {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+                                  {"cache-control", "private"}}));
+  ASSERT_EQ(222u, current_header_table_size());
+
+  // C.5.3 Third Response
+  //
+  // Several header fields are evicted from the dynamic table during the
+  // processing of this header list.
+  //
+  // Header list to encode:
+  //
+  //   :status: 200
+  //   cache-control: private
+  //   date: Mon, 21 Oct 2013 20:13:22 GMT
+  //   location: https://www.example.com
+  //   content-encoding: gzip
+  //   set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
+  hpack_block = HpackExampleToStringOrDie(R"(
+      88                                      | == Indexed - Add ==
+                                              |   idx = 8
+                                              | -> :status: 200
+      c1                                      | == Indexed - Add ==
+                                              |   idx = 65
+                                              | -> cache-control: private
+      61                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 33)
+                                              |     date
+      1d                                      |   Literal value (len = 29)
+      4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+      2032 303a 3133 3a32 3220 474d 54        |  20:13:22 GMT
+                                              | - evict: cache-control:
+                                              |   private
+                                              | -> date: Mon, 21 Oct 2013
+                                              |   20:13:22 GMT
+      c0                                      | == Indexed - Add ==
+                                              |   idx = 64
+                                              | -> location:
+                                              |   https://www.example.com
+      5a                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 26)
+                                              |     content-encoding
+      04                                      |   Literal value (len = 4)
+      677a 6970                               | gzip
+                                              | - evict: date: Mon, 21 Oct
+                                              |    2013 20:13:21 GMT
+                                              | -> content-encoding: gzip
+      77                                      | == Literal indexed ==
+                                              |   Indexed name (idx = 55)
+                                              |     set-cookie
+      38                                      |   Literal value (len = 56)
+      666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO
+      5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU;
+      206d 6178 2d61 6765 3d33 3630 303b 2076 |  max-age=3600; v
+      6572 7369 6f6e 3d31                     | ersion=1
+                                              | - evict: location:
+                                              |   https://www.example.com
+                                              | - evict: :status: 307
+                                              | -> set-cookie: foo=ASDJKHQ
+                                              |   KBZXOQWEOPIUAXQWEOIU; ma
+                                              |   x-age=3600; version=1
+  )");
+  EXPECT_TRUE(DecodeBlock(hpack_block));
+  ASSERT_THAT(
+      header_entries_,
+      ElementsAreArray({
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":status", "200"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, "cache-control",
+                           "private"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "date",
+                           "Mon, 21 Oct 2013 20:13:22 GMT"},
+          HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+                           "https://www.example.com"},
+          HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+                           "content-encoding", "gzip"},
+          HpackHeaderEntry{
+              HpackEntryType::kIndexedLiteralHeader, "set-cookie",
+              "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+      }));
+
+  // Dynamic Table (after decoding):
+  //
+  //   [  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
+  //                    max-age=3600; version=1
+  //   [  2] (s =  52) content-encoding: gzip
+  //   [  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT
+  //         Table size: 215
+  ASSERT_TRUE(VerifyDynamicTableContents(
+      {{"set-cookie",
+        "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+       {"content-encoding", "gzip"},
+       {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}}));
+  ASSERT_EQ(215u, current_header_table_size());
+}
+
+// Confirm that the table size can be changed, but at most twice.
+TEST_P(HpackDecoderTest, ProcessesOptionalTableSizeUpdates) {
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  // One update allowed.
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(3000);
+    EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+    EXPECT_EQ(3000u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+    EXPECT_TRUE(header_entries_.empty());
+  }
+  // Two updates allowed.
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(2000);
+    hbb.AppendDynamicTableSizeUpdate(2500);
+    EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+    EXPECT_EQ(2500u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+    EXPECT_TRUE(header_entries_.empty());
+  }
+  // A third update in the same HPACK block is rejected, so the final
+  // size is 1000, not 500.
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(1500);
+    hbb.AppendDynamicTableSizeUpdate(1000);
+    hbb.AppendDynamicTableSizeUpdate(500);
+    EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+    EXPECT_EQ(1u, error_messages_.size());
+    EXPECT_THAT(error_messages_[0], HasSubstr("size update not allowed"));
+    EXPECT_EQ(1000u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+    EXPECT_TRUE(header_entries_.empty());
+  }
+  // An error has been detected, so calls to HpackDecoder::DecodeFragment
+  // should return immediately.
+  DecodeBuffer db("\x80");
+  EXPECT_FALSE(decoder_.DecodeFragment(&db));
+  EXPECT_EQ(0u, db.Offset());
+  EXPECT_EQ(1u, error_messages_.size());
+}
+
+// Confirm that the table size can be changed when required, but at most twice.
+TEST_P(HpackDecoderTest, ProcessesRequiredTableSizeUpdate) {
+  // One update required, two allowed, one provided, followed by a header.
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  decoder_.ApplyHeaderTableSizeSetting(2048);
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(1024);
+    hbb.AppendIndexedHeader(4);  // :path: /
+    EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+    EXPECT_THAT(header_entries_,
+                ElementsAreArray({HpackHeaderEntry{
+                    HpackEntryType::kIndexedHeader, ":path", "/"}}));
+    EXPECT_EQ(1024u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+  }
+  // One update required, two allowed, two provided, followed by a header.
+  decoder_.ApplyHeaderTableSizeSetting(1000);
+  decoder_.ApplyHeaderTableSizeSetting(1500);
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(500);
+    hbb.AppendDynamicTableSizeUpdate(1250);
+    hbb.AppendIndexedHeader(5);  // :path: /index.html
+    EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+    EXPECT_THAT(header_entries_,
+                ElementsAreArray({HpackHeaderEntry{
+                    HpackEntryType::kIndexedHeader, ":path", "/index.html"}}));
+    EXPECT_EQ(1250u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+  }
+  // One update required, two allowed, three provided, followed by a header.
+  // The third update is rejected, so the final size is 1000, not 500.
+  decoder_.ApplyHeaderTableSizeSetting(500);
+  decoder_.ApplyHeaderTableSizeSetting(1000);
+  {
+    HpackBlockBuilder hbb;
+    hbb.AppendDynamicTableSizeUpdate(200);
+    hbb.AppendDynamicTableSizeUpdate(700);
+    hbb.AppendDynamicTableSizeUpdate(900);
+    hbb.AppendIndexedHeader(5);  // Not decoded.
+    EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+    EXPECT_FALSE(saw_end_);
+    EXPECT_EQ(1u, error_messages_.size());
+    EXPECT_THAT(error_messages_[0], HasSubstr("size update not allowed"));
+    EXPECT_EQ(700u, header_table_size_limit());
+    EXPECT_EQ(0u, current_header_table_size());
+    EXPECT_TRUE(header_entries_.empty());
+  }
+  // Now that an error has been detected, StartDecodingBlock should return
+  // false.
+  EXPECT_FALSE(decoder_.StartDecodingBlock());
+}
+
+// Confirm that required size updates are validated.
+TEST_P(HpackDecoderTest, InvalidRequiredSizeUpdate) {
+  // Require a size update, but provide one that isn't small enough (must be
+  // zero or one, in this case).
+  decoder_.ApplyHeaderTableSizeSetting(1);
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  HpackBlockBuilder hbb;
+  hbb.AppendDynamicTableSizeUpdate(2);
+  EXPECT_TRUE(decoder_.StartDecodingBlock());
+  DecodeBuffer db(hbb.buffer());
+  EXPECT_FALSE(decoder_.DecodeFragment(&db));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0], HasSubstr("above low water mark"));
+  EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+            header_table_size_limit());
+}
+
+// Confirm that required size updates are indeed required before the end.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeEnd) {
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  EXPECT_FALSE(DecodeBlock(""));
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0],
+              HasSubstr("Missing dynamic table size update"));
+  EXPECT_FALSE(saw_end_);
+}
+
+// Confirm that required size updates are indeed required before an
+// indexed header.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeader) {
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  HpackBlockBuilder hbb;
+  hbb.AppendIndexedHeader(1);
+  EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0],
+              HasSubstr("Missing dynamic table size update"));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that required size updates are indeed required before an indexed
+// header name.
+// TODO(jamessynge): Move some of these to hpack_decoder_state_test.cc.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeaderName) {
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  HpackBlockBuilder hbb;
+  hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 2,
+                                     false, "PUT");
+  EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0],
+              HasSubstr("Missing dynamic table size update"));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that required size updates are indeed required before a literal
+// header name.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeLiteralName) {
+  decoder_.ApplyHeaderTableSizeSetting(1024);
+  HpackBlockBuilder hbb;
+  hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+                                false, "name", false, "some data.");
+  EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0],
+              HasSubstr("Missing dynamic table size update"));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that an excessively long varint is detected, in this case an
+// index of 127, but with lots of additional high-order 0 bits provided,
+// too many to be allowed.
+TEST_P(HpackDecoderTest, InvalidIndexedHeaderVarint) {
+  EXPECT_TRUE(decoder_.StartDecodingBlock());
+  DecodeBuffer db("\xff\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x00");
+  EXPECT_FALSE(decoder_.DecodeFragment(&db));
+  EXPECT_TRUE(decoder_.error_detected());
+  EXPECT_FALSE(saw_end_);
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0], HasSubstr("malformed"));
+  EXPECT_TRUE(header_entries_.empty());
+  // Now that an error has been detected, EndDecodingBlock should not succeed.
+  EXPECT_FALSE(decoder_.EndDecodingBlock());
+}
+
+// Confirm that an invalid index into the tables is detected, in this case an
+// index of 0.
+TEST_P(HpackDecoderTest, InvalidIndex) {
+  EXPECT_TRUE(decoder_.StartDecodingBlock());
+  DecodeBuffer db("\x80");
+  EXPECT_FALSE(decoder_.DecodeFragment(&db));
+  EXPECT_TRUE(decoder_.error_detected());
+  EXPECT_FALSE(saw_end_);
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0], HasSubstr("Invalid index"));
+  EXPECT_TRUE(header_entries_.empty());
+  // Now that an error has been detected, EndDecodingBlock should not succeed.
+  EXPECT_FALSE(decoder_.EndDecodingBlock());
+}
+
+// Confirm that EndDecodingBlock detects a truncated HPACK block.
+TEST_P(HpackDecoderTest, TruncatedBlock) {
+  HpackBlockBuilder hbb;
+  hbb.AppendDynamicTableSizeUpdate(3000);
+  EXPECT_EQ(3u, hbb.size());
+  hbb.AppendDynamicTableSizeUpdate(4000);
+  EXPECT_EQ(6u, hbb.size());
+  // Decodes this block if the whole thing is provided.
+  EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+  EXPECT_EQ(4000u, header_table_size_limit());
+  // Multiple times even.
+  EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+  EXPECT_EQ(4000u, header_table_size_limit());
+  // But not if the block is truncated.
+  EXPECT_FALSE(DecodeBlock(hbb.buffer().substr(0, hbb.size() - 1)));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0], HasSubstr("truncated"));
+  // The first update was decoded.
+  EXPECT_EQ(3000u, header_table_size_limit());
+  EXPECT_EQ(0u, current_header_table_size());
+  EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that an oversized string is detected, ending decoding.
+TEST_P(HpackDecoderTest, OversizeStringDetected) {
+  HpackBlockBuilder hbb;
+  hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+                                false, "name", false, "some data.");
+  hbb.AppendLiteralNameAndValue(HpackEntryType::kUnindexedLiteralHeader, false,
+                                "name2", false, "longer data");
+
+  // Normally able to decode this block.
+  EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+  EXPECT_THAT(header_entries_,
+              ElementsAreArray(
+                  {HpackHeaderEntry{HpackEntryType::kNeverIndexedLiteralHeader,
+                                    "name", "some data."},
+                   HpackHeaderEntry{HpackEntryType::kUnindexedLiteralHeader,
+                                    "name2", "longer data"}}));
+
+  // But not if the maximum size of strings is less than the longest string.
+  decoder_.set_max_string_size_bytes(10);
+  EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+  EXPECT_THAT(
+      header_entries_,
+      ElementsAreArray({HpackHeaderEntry{
+          HpackEntryType::kNeverIndexedLiteralHeader, "name", "some data."}}));
+  EXPECT_FALSE(saw_end_);
+  EXPECT_EQ(1u, error_messages_.size());
+  EXPECT_THAT(error_messages_[0], HasSubstr("too long"));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_collector.cc b/http2/hpack/decoder/hpack_entry_collector.cc
new file mode 100644
index 0000000..9d17465
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_collector.cc
@@ -0,0 +1,301 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+const HpackEntryType kInvalidHeaderType = static_cast<HpackEntryType>(99);
+const size_t kInvalidIndex = 99999999;
+
+}  // namespace
+
+HpackEntryCollector::HpackEntryCollector() {
+  Clear();
+}
+
+HpackEntryCollector::HpackEntryCollector(const HpackEntryCollector& other) =
+    default;
+
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
+                                         size_t index_or_size)
+    : header_type_(type), index_(index_or_size), started_(true), ended_(true) {}
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
+                                         size_t index,
+                                         bool value_huffman,
+                                         const Http2String& value)
+    : header_type_(type),
+      index_(index),
+      value_(value, value_huffman),
+      started_(true),
+      ended_(true) {}
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
+                                         bool name_huffman,
+                                         const Http2String& name,
+                                         bool value_huffman,
+                                         const Http2String& value)
+    : header_type_(type),
+      index_(0),
+      name_(name, name_huffman),
+      value_(value, value_huffman),
+      started_(true),
+      ended_(true) {}
+
+HpackEntryCollector::~HpackEntryCollector() = default;
+
+void HpackEntryCollector::OnIndexedHeader(size_t index) {
+  ASSERT_FALSE(started_);
+  ASSERT_TRUE(IsClear()) << ToString();
+  Init(HpackEntryType::kIndexedHeader, index);
+  ended_ = true;
+}
+void HpackEntryCollector::OnStartLiteralHeader(HpackEntryType header_type,
+                                               size_t maybe_name_index) {
+  ASSERT_FALSE(started_);
+  ASSERT_TRUE(IsClear()) << ToString();
+  Init(header_type, maybe_name_index);
+}
+void HpackEntryCollector::OnNameStart(bool huffman_encoded, size_t len) {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  ASSERT_FALSE(IsClear());
+  ASSERT_TRUE(LiteralNameExpected()) << ToString();
+  name_.OnStringStart(huffman_encoded, len);
+}
+void HpackEntryCollector::OnNameData(const char* data, size_t len) {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  ASSERT_TRUE(LiteralNameExpected()) << ToString();
+  ASSERT_TRUE(name_.IsInProgress());
+  name_.OnStringData(data, len);
+}
+void HpackEntryCollector::OnNameEnd() {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  ASSERT_TRUE(LiteralNameExpected()) << ToString();
+  ASSERT_TRUE(name_.IsInProgress());
+  name_.OnStringEnd();
+}
+void HpackEntryCollector::OnValueStart(bool huffman_encoded, size_t len) {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  if (LiteralNameExpected()) {
+    ASSERT_TRUE(name_.HasEnded());
+  }
+  ASSERT_TRUE(LiteralValueExpected()) << ToString();
+  ASSERT_TRUE(value_.IsClear()) << value_.ToString();
+  value_.OnStringStart(huffman_encoded, len);
+}
+void HpackEntryCollector::OnValueData(const char* data, size_t len) {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  ASSERT_TRUE(LiteralValueExpected()) << ToString();
+  ASSERT_TRUE(value_.IsInProgress());
+  value_.OnStringData(data, len);
+}
+void HpackEntryCollector::OnValueEnd() {
+  ASSERT_TRUE(started_);
+  ASSERT_FALSE(ended_);
+  ASSERT_TRUE(LiteralValueExpected()) << ToString();
+  ASSERT_TRUE(value_.IsInProgress());
+  value_.OnStringEnd();
+  ended_ = true;
+}
+void HpackEntryCollector::OnDynamicTableSizeUpdate(size_t size) {
+  ASSERT_FALSE(started_);
+  ASSERT_TRUE(IsClear()) << ToString();
+  Init(HpackEntryType::kDynamicTableSizeUpdate, size);
+  ended_ = true;
+}
+
+void HpackEntryCollector::Clear() {
+  header_type_ = kInvalidHeaderType;
+  index_ = kInvalidIndex;
+  name_.Clear();
+  value_.Clear();
+  started_ = ended_ = false;
+}
+bool HpackEntryCollector::IsClear() const {
+  return header_type_ == kInvalidHeaderType && index_ == kInvalidIndex &&
+         name_.IsClear() && value_.IsClear() && !started_ && !ended_;
+}
+bool HpackEntryCollector::IsComplete() const {
+  return started_ && ended_;
+}
+bool HpackEntryCollector::LiteralNameExpected() const {
+  switch (header_type_) {
+    case HpackEntryType::kIndexedLiteralHeader:
+    case HpackEntryType::kUnindexedLiteralHeader:
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      return index_ == 0;
+    default:
+      return false;
+  }
+}
+bool HpackEntryCollector::LiteralValueExpected() const {
+  switch (header_type_) {
+    case HpackEntryType::kIndexedLiteralHeader:
+    case HpackEntryType::kUnindexedLiteralHeader:
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      return true;
+    default:
+      return false;
+  }
+}
+AssertionResult HpackEntryCollector::ValidateIndexedHeader(
+    size_t expected_index) const {
+  VERIFY_TRUE(started_);
+  VERIFY_TRUE(ended_);
+  VERIFY_EQ(HpackEntryType::kIndexedHeader, header_type_);
+  VERIFY_EQ(expected_index, index_);
+  return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateLiteralValueHeader(
+    HpackEntryType expected_type,
+    size_t expected_index,
+    bool expected_value_huffman,
+    Http2StringPiece expected_value) const {
+  VERIFY_TRUE(started_);
+  VERIFY_TRUE(ended_);
+  VERIFY_EQ(expected_type, header_type_);
+  VERIFY_NE(0u, expected_index);
+  VERIFY_EQ(expected_index, index_);
+  VERIFY_TRUE(name_.IsClear());
+  VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman));
+  return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateLiteralNameValueHeader(
+    HpackEntryType expected_type,
+    bool expected_name_huffman,
+    Http2StringPiece expected_name,
+    bool expected_value_huffman,
+    Http2StringPiece expected_value) const {
+  VERIFY_TRUE(started_);
+  VERIFY_TRUE(ended_);
+  VERIFY_EQ(expected_type, header_type_);
+  VERIFY_EQ(0u, index_);
+  VERIFY_SUCCESS(name_.Collected(expected_name, expected_name_huffman));
+  VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman));
+  return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateDynamicTableSizeUpdate(
+    size_t size) const {
+  VERIFY_TRUE(started_);
+  VERIFY_TRUE(ended_);
+  VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, header_type_);
+  VERIFY_EQ(index_, size);
+  return ::testing::AssertionSuccess();
+}
+
+void HpackEntryCollector::AppendToHpackBlockBuilder(
+    HpackBlockBuilder* hbb) const {
+  ASSERT_TRUE(started_ && ended_) << *this;
+  switch (header_type_) {
+    case HpackEntryType::kIndexedHeader:
+      hbb->AppendIndexedHeader(index_);
+      return;
+
+    case HpackEntryType::kDynamicTableSizeUpdate:
+      hbb->AppendDynamicTableSizeUpdate(index_);
+      return;
+
+    case HpackEntryType::kIndexedLiteralHeader:
+    case HpackEntryType::kUnindexedLiteralHeader:
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      ASSERT_TRUE(value_.HasEnded()) << *this;
+      if (index_ != 0) {
+        CHECK(name_.IsClear());
+        hbb->AppendNameIndexAndLiteralValue(header_type_, index_,
+                                            value_.huffman_encoded, value_.s);
+      } else {
+        CHECK(name_.HasEnded()) << *this;
+        hbb->AppendLiteralNameAndValue(header_type_, name_.huffman_encoded,
+                                       name_.s, value_.huffman_encoded,
+                                       value_.s);
+      }
+      return;
+
+    default:
+      ADD_FAILURE() << *this;
+  }
+}
+
+Http2String HpackEntryCollector::ToString() const {
+  Http2String result("Type=");
+  switch (header_type_) {
+    case HpackEntryType::kIndexedHeader:
+      result += "IndexedHeader";
+      break;
+    case HpackEntryType::kDynamicTableSizeUpdate:
+      result += "DynamicTableSizeUpdate";
+      break;
+    case HpackEntryType::kIndexedLiteralHeader:
+      result += "IndexedLiteralHeader";
+      break;
+    case HpackEntryType::kUnindexedLiteralHeader:
+      result += "UnindexedLiteralHeader";
+      break;
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      result += "NeverIndexedLiteralHeader";
+      break;
+    default:
+      if (header_type_ == kInvalidHeaderType) {
+        result += "<unset>";
+      } else {
+        Http2StrAppend(&result, header_type_);
+      }
+  }
+  if (index_ != 0) {
+    Http2StrAppend(&result, " Index=", index_);
+  }
+  if (!name_.IsClear()) {
+    Http2StrAppend(&result, " Name", name_.ToString());
+  }
+  if (!value_.IsClear()) {
+    Http2StrAppend(&result, " Value", value_.ToString());
+  }
+  if (!started_) {
+    EXPECT_FALSE(ended_);
+    Http2StrAppend(&result, " !started");
+  } else if (!ended_) {
+    Http2StrAppend(&result, " !ended");
+  } else {
+    Http2StrAppend(&result, " Complete");
+  }
+  return result;
+}
+
+void HpackEntryCollector::Init(HpackEntryType type, size_t maybe_index) {
+  ASSERT_TRUE(IsClear()) << ToString();
+  header_type_ = type;
+  index_ = maybe_index;
+  started_ = true;
+}
+
+bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b) {
+  return a.name() == b.name() && a.value() == b.value() &&
+         a.index() == b.index() && a.header_type() == b.header_type() &&
+         a.started() == b.started() && a.ended() == b.ended();
+}
+bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b) {
+  return !(a == b);
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v) {
+  return out << v.ToString();
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_collector.h b/http2/hpack/decoder/hpack_entry_collector.h
new file mode 100644
index 0000000..c2c5fe5
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_collector.h
@@ -0,0 +1,155 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_
+
+// HpackEntryCollector records calls to HpackEntryDecoderListener in support
+// of tests of HpackEntryDecoder, or which use it. Can only record the callbacks
+// for the decoding of a single entry; call Clear() between decoding successive
+// entries or use a distinct HpackEntryCollector for each entry.
+
+#include <stddef.h>
+
+#include <iosfwd>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+class HpackEntryCollector : public HpackEntryDecoderListener {
+ public:
+  HpackEntryCollector();
+  HpackEntryCollector(const HpackEntryCollector& other);
+
+  // These next three constructors are intended for use in tests that create
+  // an HpackEntryCollector "manually", and then compare it against another
+  // that is populated via calls to the HpackEntryDecoderListener methods.
+  HpackEntryCollector(HpackEntryType type, size_t index_or_size);
+  HpackEntryCollector(HpackEntryType type,
+                      size_t index,
+                      bool value_huffman,
+                      const Http2String& value);
+  HpackEntryCollector(HpackEntryType type,
+                      bool name_huffman,
+                      const Http2String& name,
+                      bool value_huffman,
+                      const Http2String& value);
+
+  ~HpackEntryCollector() override;
+
+  // Methods defined by HpackEntryDecoderListener.
+  void OnIndexedHeader(size_t index) override;
+  void OnStartLiteralHeader(HpackEntryType header_type,
+                            size_t maybe_name_index) override;
+  void OnNameStart(bool huffman_encoded, size_t len) override;
+  void OnNameData(const char* data, size_t len) override;
+  void OnNameEnd() override;
+  void OnValueStart(bool huffman_encoded, size_t len) override;
+  void OnValueData(const char* data, size_t len) override;
+  void OnValueEnd() override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+
+  // Clears the fields of the collector so that it is ready to start collecting
+  // another HPACK block entry.
+  void Clear();
+
+  // Is the collector ready to start collecting another HPACK block entry.
+  bool IsClear() const;
+
+  // Has a complete entry been collected?
+  bool IsComplete() const;
+
+  // Based on the HpackEntryType, is a literal name expected?
+  bool LiteralNameExpected() const;
+
+  // Based on the HpackEntryType, is a literal value expected?
+  bool LiteralValueExpected() const;
+
+  // Returns success if collected an Indexed Header (i.e. OnIndexedHeader was
+  // called).
+  ::testing::AssertionResult ValidateIndexedHeader(size_t expected_index) const;
+
+  // Returns success if collected a Header with an indexed name and literal
+  // value (i.e. OnStartLiteralHeader was called with a non-zero index for
+  // the name, which must match expected_index).
+  ::testing::AssertionResult ValidateLiteralValueHeader(
+      HpackEntryType expected_type,
+      size_t expected_index,
+      bool expected_value_huffman,
+      Http2StringPiece expected_value) const;
+
+  // Returns success if collected a Header with an literal name and literal
+  // value.
+  ::testing::AssertionResult ValidateLiteralNameValueHeader(
+      HpackEntryType expected_type,
+      bool expected_name_huffman,
+      Http2StringPiece expected_name,
+      bool expected_value_huffman,
+      Http2StringPiece expected_value) const;
+
+  // Returns success if collected a Dynamic Table Size Update,
+  // with the specified size.
+  ::testing::AssertionResult ValidateDynamicTableSizeUpdate(
+      size_t expected_size) const;
+
+  void set_header_type(HpackEntryType v) { header_type_ = v; }
+  HpackEntryType header_type() const { return header_type_; }
+
+  void set_index(size_t v) { index_ = v; }
+  size_t index() const { return index_; }
+
+  void set_name(const HpackStringCollector& v) { name_ = v; }
+  const HpackStringCollector& name() const { return name_; }
+
+  void set_value(const HpackStringCollector& v) { value_ = v; }
+  const HpackStringCollector& value() const { return value_; }
+
+  void set_started(bool v) { started_ = v; }
+  bool started() const { return started_; }
+
+  void set_ended(bool v) { ended_ = v; }
+  bool ended() const { return ended_; }
+
+  void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const;
+
+  // Returns a debug string.
+  Http2String ToString() const;
+
+ private:
+  void Init(HpackEntryType type, size_t maybe_index);
+
+  HpackEntryType header_type_;
+  size_t index_;
+
+  HpackStringCollector name_;
+  HpackStringCollector value_;
+
+  // True if has received a call to an HpackEntryDecoderListener method
+  // indicating the start of decoding an HPACK entry; for example,
+  // OnIndexedHeader set it true, but OnNameStart does not change it.
+  bool started_ = false;
+
+  // True if has received a call to an HpackEntryDecoderListener method
+  // indicating the end of decoding an HPACK entry; for example,
+  // OnIndexedHeader and OnValueEnd both set it true, but OnNameEnd does
+  // not change it.
+  bool ended_ = false;
+};
+
+bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b);
+bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b);
+std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_
diff --git a/http2/hpack/decoder/hpack_entry_decoder.cc b/http2/hpack/decoder/hpack_entry_decoder.cc
new file mode 100644
index 0000000..89562a9
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_decoder.cc
@@ -0,0 +1,267 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h"
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+namespace {
+// Converts calls from HpackStringDecoder when decoding a header name into the
+// appropriate HpackEntryDecoderListener::OnName* calls.
+class NameDecoderListener {
+ public:
+  explicit NameDecoderListener(HpackEntryDecoderListener* listener)
+      : listener_(listener) {}
+  bool OnStringStart(bool huffman_encoded, size_t len) {
+    listener_->OnNameStart(huffman_encoded, len);
+    return true;
+  }
+  void OnStringData(const char* data, size_t len) {
+    listener_->OnNameData(data, len);
+  }
+  void OnStringEnd() { listener_->OnNameEnd(); }
+
+ private:
+  HpackEntryDecoderListener* listener_;
+};
+
+// Converts calls from HpackStringDecoder when decoding a header value into
+// the appropriate HpackEntryDecoderListener::OnValue* calls.
+class ValueDecoderListener {
+ public:
+  explicit ValueDecoderListener(HpackEntryDecoderListener* listener)
+      : listener_(listener) {}
+  bool OnStringStart(bool huffman_encoded, size_t len) {
+    listener_->OnValueStart(huffman_encoded, len);
+    return true;
+  }
+  void OnStringData(const char* data, size_t len) {
+    listener_->OnValueData(data, len);
+  }
+  void OnStringEnd() { listener_->OnValueEnd(); }
+
+ private:
+  HpackEntryDecoderListener* listener_;
+};
+}  // namespace
+
+DecodeStatus HpackEntryDecoder::Start(DecodeBuffer* db,
+                                      HpackEntryDecoderListener* listener) {
+  DCHECK(db != nullptr);
+  DCHECK(listener != nullptr);
+  DCHECK(db->HasData());
+  DecodeStatus status = entry_type_decoder_.Start(db);
+  switch (status) {
+    case DecodeStatus::kDecodeDone:
+      // The type of the entry and its varint fit into the current decode
+      // buffer.
+      if (entry_type_decoder_.entry_type() == HpackEntryType::kIndexedHeader) {
+        // The entry consists solely of the entry type and varint.
+        // This is by far the most common case in practice.
+        listener->OnIndexedHeader(entry_type_decoder_.varint());
+        return DecodeStatus::kDecodeDone;
+      }
+      state_ = EntryDecoderState::kDecodedType;
+      return Resume(db, listener);
+    case DecodeStatus::kDecodeInProgress:
+      // Hit the end of the decode buffer before fully decoding
+      // the entry type and varint.
+      DCHECK_EQ(0u, db->Remaining());
+      state_ = EntryDecoderState::kResumeDecodingType;
+      return status;
+    case DecodeStatus::kDecodeError:
+      // The varint must have been invalid (too long).
+      return status;
+  }
+
+  HTTP2_BUG << "Unreachable";
+  return DecodeStatus::kDecodeError;
+}
+
+DecodeStatus HpackEntryDecoder::Resume(DecodeBuffer* db,
+                                       HpackEntryDecoderListener* listener) {
+  DCHECK(db != nullptr);
+  DCHECK(listener != nullptr);
+
+  DecodeStatus status;
+
+  do {
+    switch (state_) {
+      case EntryDecoderState::kResumeDecodingType:
+        // entry_type_decoder_ returned kDecodeInProgress when last called.
+        DVLOG(1) << "kResumeDecodingType: db->Remaining=" << db->Remaining();
+        status = entry_type_decoder_.Resume(db);
+        if (status != DecodeStatus::kDecodeDone) {
+          return status;
+        }
+        state_ = EntryDecoderState::kDecodedType;
+        HTTP2_FALLTHROUGH;
+
+      case EntryDecoderState::kDecodedType:
+        // entry_type_decoder_ returned kDecodeDone, now need to decide how
+        // to proceed.
+        DVLOG(1) << "kDecodedType: db->Remaining=" << db->Remaining();
+        if (DispatchOnType(listener)) {
+          // All done.
+          return DecodeStatus::kDecodeDone;
+        }
+        continue;
+
+      case EntryDecoderState::kStartDecodingName:
+        DVLOG(1) << "kStartDecodingName: db->Remaining=" << db->Remaining();
+        {
+          NameDecoderListener ncb(listener);
+          status = string_decoder_.Start(db, &ncb);
+        }
+        if (status != DecodeStatus::kDecodeDone) {
+          // On the assumption that the status is kDecodeInProgress, set
+          // state_ accordingly; unnecessary if status is kDecodeError, but
+          // that will only happen if the varint encoding the name's length
+          // is too long.
+          state_ = EntryDecoderState::kResumeDecodingName;
+          return status;
+        }
+        state_ = EntryDecoderState::kStartDecodingValue;
+        HTTP2_FALLTHROUGH;
+
+      case EntryDecoderState::kStartDecodingValue:
+        DVLOG(1) << "kStartDecodingValue: db->Remaining=" << db->Remaining();
+        {
+          ValueDecoderListener vcb(listener);
+          status = string_decoder_.Start(db, &vcb);
+        }
+        if (status == DecodeStatus::kDecodeDone) {
+          // Done with decoding the literal value, so we've reached the
+          // end of the header entry.
+          return status;
+        }
+        // On the assumption that the status is kDecodeInProgress, set
+        // state_ accordingly; unnecessary if status is kDecodeError, but
+        // that will only happen if the varint encoding the value's length
+        // is too long.
+        state_ = EntryDecoderState::kResumeDecodingValue;
+        return status;
+
+      case EntryDecoderState::kResumeDecodingName:
+        // The literal name was split across decode buffers.
+        DVLOG(1) << "kResumeDecodingName: db->Remaining=" << db->Remaining();
+        {
+          NameDecoderListener ncb(listener);
+          status = string_decoder_.Resume(db, &ncb);
+        }
+        if (status != DecodeStatus::kDecodeDone) {
+          // On the assumption that the status is kDecodeInProgress, set
+          // state_ accordingly; unnecessary if status is kDecodeError, but
+          // that will only happen if the varint encoding the name's length
+          // is too long.
+          state_ = EntryDecoderState::kResumeDecodingName;
+          return status;
+        }
+        state_ = EntryDecoderState::kStartDecodingValue;
+        break;
+
+      case EntryDecoderState::kResumeDecodingValue:
+        // The literal value was split across decode buffers.
+        DVLOG(1) << "kResumeDecodingValue: db->Remaining=" << db->Remaining();
+        {
+          ValueDecoderListener vcb(listener);
+          status = string_decoder_.Resume(db, &vcb);
+        }
+        if (status == DecodeStatus::kDecodeDone) {
+          // Done with decoding the value, therefore the entry as a whole.
+          return status;
+        }
+        // On the assumption that the status is kDecodeInProgress, set
+        // state_ accordingly; unnecessary if status is kDecodeError, but
+        // that will only happen if the varint encoding the value's length
+        // is too long.
+        state_ = EntryDecoderState::kResumeDecodingValue;
+        return status;
+    }
+  } while (true);
+}
+
+bool HpackEntryDecoder::DispatchOnType(HpackEntryDecoderListener* listener) {
+  const HpackEntryType entry_type = entry_type_decoder_.entry_type();
+  const uint32_t varint = entry_type_decoder_.varint();
+  switch (entry_type) {
+    case HpackEntryType::kIndexedHeader:
+      // The entry consists solely of the entry type and varint. See:
+      // http://httpwg.org/specs/rfc7541.html#indexed.header.representation
+      listener->OnIndexedHeader(varint);
+      return true;
+
+    case HpackEntryType::kIndexedLiteralHeader:
+    case HpackEntryType::kUnindexedLiteralHeader:
+    case HpackEntryType::kNeverIndexedLiteralHeader:
+      // The entry has a literal value, and if the varint is zero also has a
+      // literal name preceding the value. See:
+      // http://httpwg.org/specs/rfc7541.html#literal.header.representation
+      listener->OnStartLiteralHeader(entry_type, varint);
+      if (varint == 0) {
+        state_ = EntryDecoderState::kStartDecodingName;
+      } else {
+        state_ = EntryDecoderState::kStartDecodingValue;
+      }
+      return false;
+
+    case HpackEntryType::kDynamicTableSizeUpdate:
+      // The entry consists solely of the entry type and varint. FWIW, I've
+      // never seen this type of entry in production (primarily browser
+      // traffic) so if you're designing an HPACK successor someday, consider
+      // dropping it or giving it a much longer prefix. See:
+      // http://httpwg.org/specs/rfc7541.html#encoding.context.update
+      listener->OnDynamicTableSizeUpdate(varint);
+      return true;
+  }
+
+  HTTP2_BUG << "Unreachable, entry_type=" << entry_type;
+  return true;
+}
+
+void HpackEntryDecoder::OutputDebugString(std::ostream& out) const {
+  out << "HpackEntryDecoder(state=" << state_ << ", " << entry_type_decoder_
+      << ", " << string_decoder_ << ")";
+}
+
+Http2String HpackEntryDecoder::DebugString() const {
+  std::stringstream s;
+  s << *this;
+  return s.str();
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackEntryDecoder& v) {
+  v.OutputDebugString(out);
+  return out;
+}
+
+std::ostream& operator<<(std::ostream& out,
+                         HpackEntryDecoder::EntryDecoderState state) {
+  typedef HpackEntryDecoder::EntryDecoderState EntryDecoderState;
+  switch (state) {
+    case EntryDecoderState::kResumeDecodingType:
+      return out << "kResumeDecodingType";
+    case EntryDecoderState::kDecodedType:
+      return out << "kDecodedType";
+    case EntryDecoderState::kStartDecodingName:
+      return out << "kStartDecodingName";
+    case EntryDecoderState::kResumeDecodingName:
+      return out << "kResumeDecodingName";
+    case EntryDecoderState::kStartDecodingValue:
+      return out << "kStartDecodingValue";
+    case EntryDecoderState::kResumeDecodingValue:
+      return out << "kResumeDecodingValue";
+  }
+  return out << static_cast<int>(state);
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_decoder.h b/http2/hpack/decoder/hpack_entry_decoder.h
new file mode 100644
index 0000000..fe59d96
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_decoder.h
@@ -0,0 +1,85 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_
+
+// HpackEntryDecoder decodes a single HPACK entry (i.e. one header or one
+// dynamic table size update), in a resumable fashion. The first call, Start(),
+// must provide a non-empty decode buffer. Continue with calls to Resume() if
+// Start, and any subsequent calls to Resume, returns kDecodeInProgress.
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoder {
+ public:
+  enum class EntryDecoderState {
+    // Have started decoding the type/varint, but didn't finish on the previous
+    // attempt.  Next state is kResumeDecodingType or kDecodedType.
+    kResumeDecodingType,
+
+    // Have just finished decoding the type/varint. Final state if the type is
+    // kIndexedHeader or kDynamicTableSizeUpdate. Otherwise, the next state is
+    // kStartDecodingName (if the varint is 0), else kStartDecodingValue.
+    kDecodedType,
+
+    // Ready to start decoding the literal name of a header entry. Next state
+    // is kResumeDecodingName (if the name is split across decode buffers),
+    // else kStartDecodingValue.
+    kStartDecodingName,
+
+    // Resume decoding the literal name of a header that is split across decode
+    // buffers.
+    kResumeDecodingName,
+
+    // Ready to start decoding the literal value of a header entry. Final state
+    // if the value string is entirely in the decode buffer, else the next state
+    // is kResumeDecodingValue.
+    kStartDecodingValue,
+
+    // Resume decoding the literal value of a header that is split across decode
+    // buffers.
+    kResumeDecodingValue,
+  };
+
+  // Only call when the decode buffer has data (i.e. HpackBlockDecoder must
+  // not call until there is data).
+  DecodeStatus Start(DecodeBuffer* db, HpackEntryDecoderListener* listener);
+
+  // Only call Resume if the previous call (Start or Resume) returned
+  // kDecodeInProgress; Resume is also called from Start when it has succeeded
+  // in decoding the entry type and its varint.
+  DecodeStatus Resume(DecodeBuffer* db, HpackEntryDecoderListener* listener);
+
+  Http2String DebugString() const;
+  void OutputDebugString(std::ostream& out) const;
+
+ private:
+  // Implements handling state kDecodedType.
+  bool DispatchOnType(HpackEntryDecoderListener* listener);
+
+  HpackEntryTypeDecoder entry_type_decoder_;
+  HpackStringDecoder string_decoder_;
+  EntryDecoderState state_ = EntryDecoderState();
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const HpackEntryDecoder& v);
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(
+    std::ostream& out,
+    HpackEntryDecoder::EntryDecoderState state);
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_
diff --git a/http2/hpack/decoder/hpack_entry_decoder_listener.cc b/http2/hpack/decoder/hpack_entry_decoder_listener.cc
new file mode 100644
index 0000000..b783b15
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_decoder_listener.cc
@@ -0,0 +1,81 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+
+#include "base/logging.h"
+
+namespace http2 {
+
+void HpackEntryDecoderVLoggingListener::OnIndexedHeader(size_t index) {
+  VLOG(1) << "OnIndexedHeader, index=" << index;
+  if (wrapped_) {
+    wrapped_->OnIndexedHeader(index);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnStartLiteralHeader(
+    HpackEntryType entry_type,
+    size_t maybe_name_index) {
+  VLOG(1) << "OnStartLiteralHeader: entry_type=" << entry_type
+          << ", maybe_name_index=" << maybe_name_index;
+  if (wrapped_) {
+    wrapped_->OnStartLiteralHeader(entry_type, maybe_name_index);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnNameStart(bool huffman_encoded,
+                                                    size_t len) {
+  VLOG(1) << "OnNameStart: H=" << huffman_encoded << ", len=" << len;
+  if (wrapped_) {
+    wrapped_->OnNameStart(huffman_encoded, len);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnNameData(const char* data,
+                                                   size_t len) {
+  VLOG(1) << "OnNameData: len=" << len;
+  if (wrapped_) {
+    wrapped_->OnNameData(data, len);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnNameEnd() {
+  VLOG(1) << "OnNameEnd";
+  if (wrapped_) {
+    wrapped_->OnNameEnd();
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnValueStart(bool huffman_encoded,
+                                                     size_t len) {
+  VLOG(1) << "OnValueStart: H=" << huffman_encoded << ", len=" << len;
+  if (wrapped_) {
+    wrapped_->OnValueStart(huffman_encoded, len);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnValueData(const char* data,
+                                                    size_t len) {
+  VLOG(1) << "OnValueData: len=" << len;
+  if (wrapped_) {
+    wrapped_->OnValueData(data, len);
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnValueEnd() {
+  VLOG(1) << "OnValueEnd";
+  if (wrapped_) {
+    wrapped_->OnValueEnd();
+  }
+}
+
+void HpackEntryDecoderVLoggingListener::OnDynamicTableSizeUpdate(size_t size) {
+  VLOG(1) << "OnDynamicTableSizeUpdate: size=" << size;
+  if (wrapped_) {
+    wrapped_->OnDynamicTableSizeUpdate(size);
+  }
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_decoder_listener.h b/http2/hpack/decoder/hpack_entry_decoder_listener.h
new file mode 100644
index 0000000..fd11f59
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_decoder_listener.h
@@ -0,0 +1,110 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_
+
+// Defines HpackEntryDecoderListener, the base class of listeners that
+// HpackEntryDecoder calls. Also defines HpackEntryDecoderVLoggingListener
+// which logs before calling another HpackEntryDecoderListener implementation.
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoderListener {
+ public:
+  virtual ~HpackEntryDecoderListener() {}
+
+  // Called when an indexed header (i.e. one in the static or dynamic table) has
+  // been decoded from an HPACK block. index is supposed to be non-zero, but
+  // that has not been checked by the caller.
+  virtual void OnIndexedHeader(size_t index) = 0;
+
+  // Called when the start of a header with a literal value, and maybe a literal
+  // name, has been decoded. maybe_name_index is zero if the header has a
+  // literal name, else it is a reference into the static or dynamic table, from
+  // which the name should be determined. When the name is literal, the next
+  // call will be to OnNameStart; else it will be to OnValueStart. entry_type
+  // indicates whether the peer has added the entry to its dynamic table, and
+  // whether a proxy is permitted to do so when forwarding the entry.
+  virtual void OnStartLiteralHeader(HpackEntryType entry_type,
+                                    size_t maybe_name_index) = 0;
+
+  // Called when the encoding (Huffman compressed or plain text) and the encoded
+  // length of a literal name has been decoded. OnNameData will be called next,
+  // and repeatedly until the sum of lengths passed to OnNameData is len.
+  virtual void OnNameStart(bool huffman_encoded, size_t len) = 0;
+
+  // Called when len bytes of an encoded header name have been decoded.
+  virtual void OnNameData(const char* data, size_t len) = 0;
+
+  // Called after the entire name has been passed to OnNameData.
+  // OnValueStart will be called next.
+  virtual void OnNameEnd() = 0;
+
+  // Called when the encoding (Huffman compressed or plain text) and the encoded
+  // length of a literal value has been decoded. OnValueData will be called
+  // next, and repeatedly until the sum of lengths passed to OnValueData is len.
+  virtual void OnValueStart(bool huffman_encoded, size_t len) = 0;
+
+  // Called when len bytes of an encoded header value have been decoded.
+  virtual void OnValueData(const char* data, size_t len) = 0;
+
+  // Called after the entire value has been passed to OnValueData, marking the
+  // end of a header entry with a literal value, and maybe a literal name.
+  virtual void OnValueEnd() = 0;
+
+  // Called when an update to the size of the peer's dynamic table has been
+  // decoded.
+  virtual void OnDynamicTableSizeUpdate(size_t size) = 0;
+};
+
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoderVLoggingListener
+    : public HpackEntryDecoderListener {
+ public:
+  HpackEntryDecoderVLoggingListener() : wrapped_(nullptr) {}
+  explicit HpackEntryDecoderVLoggingListener(HpackEntryDecoderListener* wrapped)
+      : wrapped_(wrapped) {}
+  ~HpackEntryDecoderVLoggingListener() override {}
+
+  void OnIndexedHeader(size_t index) override;
+  void OnStartLiteralHeader(HpackEntryType entry_type,
+                            size_t maybe_name_index) override;
+  void OnNameStart(bool huffman_encoded, size_t len) override;
+  void OnNameData(const char* data, size_t len) override;
+  void OnNameEnd() override;
+  void OnValueStart(bool huffman_encoded, size_t len) override;
+  void OnValueData(const char* data, size_t len) override;
+  void OnValueEnd() override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+
+ private:
+  HpackEntryDecoderListener* const wrapped_;
+};
+
+// A no-op implementation of HpackEntryDecoderListener.
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoderNoOpListener
+    : public HpackEntryDecoderListener {
+ public:
+  ~HpackEntryDecoderNoOpListener() override {}
+
+  void OnIndexedHeader(size_t index) override {}
+  void OnStartLiteralHeader(HpackEntryType entry_type,
+                            size_t maybe_name_index) override {}
+  void OnNameStart(bool huffman_encoded, size_t len) override {}
+  void OnNameData(const char* data, size_t len) override {}
+  void OnNameEnd() override {}
+  void OnValueStart(bool huffman_encoded, size_t len) override {}
+  void OnValueData(const char* data, size_t len) override {}
+  void OnValueEnd() override {}
+  void OnDynamicTableSizeUpdate(size_t size) override {}
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_
diff --git a/http2/hpack/decoder/hpack_entry_decoder_test.cc b/http2/hpack/decoder/hpack_entry_decoder_test.cc
new file mode 100644
index 0000000..7249bd5
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_decoder_test.cc
@@ -0,0 +1,211 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h"
+
+// Tests of HpackEntryDecoder.
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+class HpackEntryDecoderTest : public RandomDecoderTest {
+ protected:
+  HpackEntryDecoderTest() : listener_(&collector_) {}
+
+  DecodeStatus StartDecoding(DecodeBuffer* b) override {
+    collector_.Clear();
+    return decoder_.Start(b, &listener_);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+    return decoder_.Resume(b, &listener_);
+  }
+
+  AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db,
+                                               const Validator& validator) {
+    // StartDecoding, above, requires the DecodeBuffer be non-empty so that it
+    // can call Start with the prefix byte.
+    bool return_non_zero_on_first = true;
+    return RandomDecoderTest::DecodeAndValidateSeveralWays(
+        db, return_non_zero_on_first, validator);
+  }
+
+  AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb,
+                                               const Validator& validator) {
+    DecodeBuffer db(hbb.buffer());
+    return DecodeAndValidateSeveralWays(&db, validator);
+  }
+
+  HpackEntryDecoder decoder_;
+  HpackEntryCollector collector_;
+  HpackEntryDecoderVLoggingListener listener_;
+};
+
+TEST_F(HpackEntryDecoderTest, IndexedHeader_Literals) {
+  {
+    const char input[] = {'\x82'};  // == Index 2 ==
+    DecodeBuffer b(input);
+    auto do_check = [this]() {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(2));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+  collector_.Clear();
+  {
+    const char input[] = {'\xfe'};  // == Index 126 ==
+    DecodeBuffer b(input);
+    auto do_check = [this]() {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(126));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+  collector_.Clear();
+  {
+    const char input[] = {'\xff', '\x00'};  // == Index 127 ==
+    DecodeBuffer b(input);
+    auto do_check = [this]() {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(127));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+}
+
+TEST_F(HpackEntryDecoderTest, IndexedHeader_Various) {
+  // Indices chosen to hit encoding and table boundaries.
+  for (const uint32_t ndx : {1, 2, 61, 62, 63, 126, 127, 254, 255, 256}) {
+    HpackBlockBuilder hbb;
+    hbb.AppendIndexedHeader(ndx);
+
+    auto do_check = [this, ndx]() {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(ndx));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+}
+
+TEST_F(HpackEntryDecoderTest, IndexedLiteralValue_Literal) {
+  const char input[] =
+      "\x7f"            // == Literal indexed, name index 0x40 ==
+      "\x01"            // 2nd byte of name index (0x01 + 0x3f == 0x40)
+      "\x0d"            // Value length (13)
+      "custom-header";  // Value
+  DecodeBuffer b(input, sizeof input - 1);
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralValueHeader(
+        HpackEntryType::kIndexedLiteralHeader, 0x40, false, "custom-header"));
+  };
+  EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+TEST_F(HpackEntryDecoderTest, IndexedLiteralNameValue_Literal) {
+  const char input[] =
+      "\x40"            // == Literal indexed ==
+      "\x0a"            // Name length (10)
+      "custom-key"      // Name
+      "\x0d"            // Value length (13)
+      "custom-header";  // Value
+
+  DecodeBuffer b(input, sizeof input - 1);
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralNameValueHeader(
+        HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false,
+        "custom-header"));
+  };
+  EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+TEST_F(HpackEntryDecoderTest, DynamicTableSizeUpdate_Literal) {
+  // Size update, length 31.
+  const char input[] = "\x3f\x00";
+  DecodeBuffer b(input, 2);
+  auto do_check = [this]() {
+    VERIFY_AND_RETURN_SUCCESS(collector_.ValidateDynamicTableSizeUpdate(31));
+  };
+  EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+  EXPECT_TRUE(do_check());
+}
+
+class HpackLiteralEntryDecoderTest
+    : public HpackEntryDecoderTest,
+      public ::testing::WithParamInterface<HpackEntryType> {
+ protected:
+  HpackLiteralEntryDecoderTest() : entry_type_(GetParam()) {}
+
+  const HpackEntryType entry_type_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+    AllLiteralTypes,
+    HpackLiteralEntryDecoderTest,
+    testing::Values(HpackEntryType::kIndexedLiteralHeader,
+                    HpackEntryType::kUnindexedLiteralHeader,
+                    HpackEntryType::kNeverIndexedLiteralHeader));
+
+TEST_P(HpackLiteralEntryDecoderTest, RandNameIndexAndLiteralValue) {
+  for (int n = 0; n < 10; n++) {
+    const uint32_t ndx = 1 + Random().Rand8();
+    const bool value_is_huffman_encoded = (n % 2) == 0;
+    const Http2String value = Random().RandString(Random().Rand8());
+    HpackBlockBuilder hbb;
+    hbb.AppendNameIndexAndLiteralValue(entry_type_, ndx,
+                                       value_is_huffman_encoded, value);
+    auto do_check = [this, ndx, value_is_huffman_encoded,
+                     value]() -> AssertionResult {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralValueHeader(
+          entry_type_, ndx, value_is_huffman_encoded, value));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+}
+
+TEST_P(HpackLiteralEntryDecoderTest, RandLiteralNameAndValue) {
+  for (int n = 0; n < 10; n++) {
+    const bool name_is_huffman_encoded = (n & 1) == 0;
+    const int name_len = 1 + Random().Rand8();
+    const Http2String name = Random().RandString(name_len);
+    const bool value_is_huffman_encoded = (n & 2) == 0;
+    const int value_len = Random().Skewed(10);
+    const Http2String value = Random().RandString(value_len);
+    HpackBlockBuilder hbb;
+    hbb.AppendLiteralNameAndValue(entry_type_, name_is_huffman_encoded, name,
+                                  value_is_huffman_encoded, value);
+    auto do_check = [this, name_is_huffman_encoded, name,
+                     value_is_huffman_encoded, value]() -> AssertionResult {
+      VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralNameValueHeader(
+          entry_type_, name_is_huffman_encoded, name, value_is_huffman_encoded,
+          value));
+    };
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+    EXPECT_TRUE(do_check());
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_type_decoder.cc b/http2/hpack/decoder/hpack_entry_type_decoder.cc
new file mode 100644
index 0000000..121e3d0
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_type_decoder.cc
@@ -0,0 +1,358 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+Http2String HpackEntryTypeDecoder::DebugString() const {
+  return Http2StrCat(
+      "HpackEntryTypeDecoder(varint_decoder=", varint_decoder_.DebugString(),
+      ", entry_type=", entry_type_, ")");
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackEntryTypeDecoder& v) {
+  return out << v.DebugString();
+}
+
+// This ridiculous looking function turned out to be the winner in benchmarking
+// of several very different alternative implementations. It would be even
+// faster (~7%) if inlined in the header file, but I'm not sure if that is
+// worth doing... yet.
+// TODO(jamessynge): Benchmark again at a higher level (e.g. at least at the
+// full HTTP/2 decoder level, but preferably still higher) to determine if the
+// alternatives that take less code/data space are preferable in that situation.
+DecodeStatus HpackEntryTypeDecoder::Start(DecodeBuffer* db) {
+  DCHECK(db != nullptr);
+  DCHECK(db->HasData());
+
+  // The high four bits (nibble) of first byte of the entry determine the type
+  // of the entry, and may also be the initial bits of the varint that
+  // represents an index or table size. Note the use of the word 'initial'
+  // rather than 'high'; the HPACK encoding of varints is not in network
+  // order (i.e. not big-endian, the high-order byte isn't first), nor in
+  // little-endian order. See:
+  // http://httpwg.org/specs/rfc7541.html#integer.representation
+  uint8_t byte = db->DecodeUInt8();
+  switch (byte) {
+    case 0b00000000:
+    case 0b00000001:
+    case 0b00000010:
+    case 0b00000011:
+    case 0b00000100:
+    case 0b00000101:
+    case 0b00000110:
+    case 0b00000111:
+    case 0b00001000:
+    case 0b00001001:
+    case 0b00001010:
+    case 0b00001011:
+    case 0b00001100:
+    case 0b00001101:
+    case 0b00001110:
+      // The low 4 bits of |byte| are the initial bits of the varint.
+      // One of those bits is 0, so the varint is only one byte long.
+      entry_type_ = HpackEntryType::kUnindexedLiteralHeader;
+      varint_decoder_.set_value(byte);
+      return DecodeStatus::kDecodeDone;
+
+    case 0b00001111:
+      // The low 4 bits of |byte| are the initial bits of the varint. All 4
+      // are 1, so the varint extends into another byte.
+      entry_type_ = HpackEntryType::kUnindexedLiteralHeader;
+      return varint_decoder_.StartExtended(4, db);
+
+    case 0b00010000:
+    case 0b00010001:
+    case 0b00010010:
+    case 0b00010011:
+    case 0b00010100:
+    case 0b00010101:
+    case 0b00010110:
+    case 0b00010111:
+    case 0b00011000:
+    case 0b00011001:
+    case 0b00011010:
+    case 0b00011011:
+    case 0b00011100:
+    case 0b00011101:
+    case 0b00011110:
+      // The low 4 bits of |byte| are the initial bits of the varint.
+      // One of those bits is 0, so the varint is only one byte long.
+      entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader;
+      varint_decoder_.set_value(byte & 0x0f);
+      return DecodeStatus::kDecodeDone;
+
+    case 0b00011111:
+      // The low 4 bits of |byte| are the initial bits of the varint.
+      // All of those bits are 1, so the varint extends into another byte.
+      entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader;
+      return varint_decoder_.StartExtended(4, db);
+
+    case 0b00100000:
+    case 0b00100001:
+    case 0b00100010:
+    case 0b00100011:
+    case 0b00100100:
+    case 0b00100101:
+    case 0b00100110:
+    case 0b00100111:
+    case 0b00101000:
+    case 0b00101001:
+    case 0b00101010:
+    case 0b00101011:
+    case 0b00101100:
+    case 0b00101101:
+    case 0b00101110:
+    case 0b00101111:
+    case 0b00110000:
+    case 0b00110001:
+    case 0b00110010:
+    case 0b00110011:
+    case 0b00110100:
+    case 0b00110101:
+    case 0b00110110:
+    case 0b00110111:
+    case 0b00111000:
+    case 0b00111001:
+    case 0b00111010:
+    case 0b00111011:
+    case 0b00111100:
+    case 0b00111101:
+    case 0b00111110:
+      entry_type_ = HpackEntryType::kDynamicTableSizeUpdate;
+      // The low 5 bits of |byte| are the initial bits of the varint.
+      // One of those bits is 0, so the varint is only one byte long.
+      varint_decoder_.set_value(byte & 0x01f);
+      return DecodeStatus::kDecodeDone;
+
+    case 0b00111111:
+      entry_type_ = HpackEntryType::kDynamicTableSizeUpdate;
+      // The low 5 bits of |byte| are the initial bits of the varint.
+      // All of those bits are 1, so the varint extends into another byte.
+      return varint_decoder_.StartExtended(5, db);
+
+    case 0b01000000:
+    case 0b01000001:
+    case 0b01000010:
+    case 0b01000011:
+    case 0b01000100:
+    case 0b01000101:
+    case 0b01000110:
+    case 0b01000111:
+    case 0b01001000:
+    case 0b01001001:
+    case 0b01001010:
+    case 0b01001011:
+    case 0b01001100:
+    case 0b01001101:
+    case 0b01001110:
+    case 0b01001111:
+    case 0b01010000:
+    case 0b01010001:
+    case 0b01010010:
+    case 0b01010011:
+    case 0b01010100:
+    case 0b01010101:
+    case 0b01010110:
+    case 0b01010111:
+    case 0b01011000:
+    case 0b01011001:
+    case 0b01011010:
+    case 0b01011011:
+    case 0b01011100:
+    case 0b01011101:
+    case 0b01011110:
+    case 0b01011111:
+    case 0b01100000:
+    case 0b01100001:
+    case 0b01100010:
+    case 0b01100011:
+    case 0b01100100:
+    case 0b01100101:
+    case 0b01100110:
+    case 0b01100111:
+    case 0b01101000:
+    case 0b01101001:
+    case 0b01101010:
+    case 0b01101011:
+    case 0b01101100:
+    case 0b01101101:
+    case 0b01101110:
+    case 0b01101111:
+    case 0b01110000:
+    case 0b01110001:
+    case 0b01110010:
+    case 0b01110011:
+    case 0b01110100:
+    case 0b01110101:
+    case 0b01110110:
+    case 0b01110111:
+    case 0b01111000:
+    case 0b01111001:
+    case 0b01111010:
+    case 0b01111011:
+    case 0b01111100:
+    case 0b01111101:
+    case 0b01111110:
+      entry_type_ = HpackEntryType::kIndexedLiteralHeader;
+      // The low 6 bits of |byte| are the initial bits of the varint.
+      // One of those bits is 0, so the varint is only one byte long.
+      varint_decoder_.set_value(byte & 0x03f);
+      return DecodeStatus::kDecodeDone;
+
+    case 0b01111111:
+      entry_type_ = HpackEntryType::kIndexedLiteralHeader;
+      // The low 6 bits of |byte| are the initial bits of the varint.
+      // All of those bits are 1, so the varint extends into another byte.
+      return varint_decoder_.StartExtended(6, db);
+
+    case 0b10000000:
+    case 0b10000001:
+    case 0b10000010:
+    case 0b10000011:
+    case 0b10000100:
+    case 0b10000101:
+    case 0b10000110:
+    case 0b10000111:
+    case 0b10001000:
+    case 0b10001001:
+    case 0b10001010:
+    case 0b10001011:
+    case 0b10001100:
+    case 0b10001101:
+    case 0b10001110:
+    case 0b10001111:
+    case 0b10010000:
+    case 0b10010001:
+    case 0b10010010:
+    case 0b10010011:
+    case 0b10010100:
+    case 0b10010101:
+    case 0b10010110:
+    case 0b10010111:
+    case 0b10011000:
+    case 0b10011001:
+    case 0b10011010:
+    case 0b10011011:
+    case 0b10011100:
+    case 0b10011101:
+    case 0b10011110:
+    case 0b10011111:
+    case 0b10100000:
+    case 0b10100001:
+    case 0b10100010:
+    case 0b10100011:
+    case 0b10100100:
+    case 0b10100101:
+    case 0b10100110:
+    case 0b10100111:
+    case 0b10101000:
+    case 0b10101001:
+    case 0b10101010:
+    case 0b10101011:
+    case 0b10101100:
+    case 0b10101101:
+    case 0b10101110:
+    case 0b10101111:
+    case 0b10110000:
+    case 0b10110001:
+    case 0b10110010:
+    case 0b10110011:
+    case 0b10110100:
+    case 0b10110101:
+    case 0b10110110:
+    case 0b10110111:
+    case 0b10111000:
+    case 0b10111001:
+    case 0b10111010:
+    case 0b10111011:
+    case 0b10111100:
+    case 0b10111101:
+    case 0b10111110:
+    case 0b10111111:
+    case 0b11000000:
+    case 0b11000001:
+    case 0b11000010:
+    case 0b11000011:
+    case 0b11000100:
+    case 0b11000101:
+    case 0b11000110:
+    case 0b11000111:
+    case 0b11001000:
+    case 0b11001001:
+    case 0b11001010:
+    case 0b11001011:
+    case 0b11001100:
+    case 0b11001101:
+    case 0b11001110:
+    case 0b11001111:
+    case 0b11010000:
+    case 0b11010001:
+    case 0b11010010:
+    case 0b11010011:
+    case 0b11010100:
+    case 0b11010101:
+    case 0b11010110:
+    case 0b11010111:
+    case 0b11011000:
+    case 0b11011001:
+    case 0b11011010:
+    case 0b11011011:
+    case 0b11011100:
+    case 0b11011101:
+    case 0b11011110:
+    case 0b11011111:
+    case 0b11100000:
+    case 0b11100001:
+    case 0b11100010:
+    case 0b11100011:
+    case 0b11100100:
+    case 0b11100101:
+    case 0b11100110:
+    case 0b11100111:
+    case 0b11101000:
+    case 0b11101001:
+    case 0b11101010:
+    case 0b11101011:
+    case 0b11101100:
+    case 0b11101101:
+    case 0b11101110:
+    case 0b11101111:
+    case 0b11110000:
+    case 0b11110001:
+    case 0b11110010:
+    case 0b11110011:
+    case 0b11110100:
+    case 0b11110101:
+    case 0b11110110:
+    case 0b11110111:
+    case 0b11111000:
+    case 0b11111001:
+    case 0b11111010:
+    case 0b11111011:
+    case 0b11111100:
+    case 0b11111101:
+    case 0b11111110:
+      entry_type_ = HpackEntryType::kIndexedHeader;
+      // The low 7 bits of |byte| are the initial bits of the varint.
+      // One of those bits is 0, so the varint is only one byte long.
+      varint_decoder_.set_value(byte & 0x07f);
+      return DecodeStatus::kDecodeDone;
+
+    case 0b11111111:
+      entry_type_ = HpackEntryType::kIndexedHeader;
+      // The low 7 bits of |byte| are the initial bits of the varint.
+      // All of those bits are 1, so the varint extends into another byte.
+      return varint_decoder_.StartExtended(7, db);
+  }
+  HTTP2_BUG << "Unreachable, byte=" << std::hex << static_cast<uint32_t>(byte);
+  return DecodeStatus::kDecodeError;
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_type_decoder.h b/http2/hpack/decoder/hpack_entry_type_decoder.h
new file mode 100644
index 0000000..1c0f2ac
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_type_decoder.h
@@ -0,0 +1,57 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_
+
+// Decodes the type of an HPACK entry, and the variable length integer whose
+// prefix is in the low-order bits of the same byte, "below" the type bits.
+// The integer represents an index into static or dynamic table, which may be
+// zero, or is the new size limit of the dynamic table.
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackEntryTypeDecoder {
+ public:
+  // Only call when the decode buffer has data (i.e. HpackEntryDecoder must
+  // not call until there is data).
+  DecodeStatus Start(DecodeBuffer* db);
+
+  // Only call Resume if the previous call (Start or Resume) returned
+  // DecodeStatus::kDecodeInProgress.
+  DecodeStatus Resume(DecodeBuffer* db) { return varint_decoder_.Resume(db); }
+
+  // Returns the decoded entry type. Only call if the preceding call to Start
+  // or Resume returned kDecodeDone.
+  HpackEntryType entry_type() const { return entry_type_; }
+
+  // Returns the decoded variable length integer. Only call if the
+  // preceding call to Start or Resume returned kDecodeDone.
+  uint32_t varint() const { return varint_decoder_.value(); }
+
+  Http2String DebugString() const;
+
+ private:
+  HpackVarintDecoder varint_decoder_;
+
+  // This field is initialized just to keep ASAN happy about reading it
+  // from DebugString().
+  HpackEntryType entry_type_ = HpackEntryType::kIndexedHeader;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const HpackEntryTypeDecoder& v);
+
+}  // namespace http2
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_
diff --git a/http2/hpack/decoder/hpack_entry_type_decoder_test.cc b/http2/hpack/decoder/hpack_entry_type_decoder_test.cc
new file mode 100644
index 0000000..afff7cc
--- /dev/null
+++ b/http2/hpack/decoder/hpack_entry_type_decoder_test.cc
@@ -0,0 +1,87 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+const bool kReturnNonZeroOnFirst = true;
+
+class HpackEntryTypeDecoderTest : public RandomDecoderTest {
+ protected:
+  DecodeStatus StartDecoding(DecodeBuffer* b) override {
+    CHECK_LT(0u, b->Remaining());
+    return decoder_.Start(b);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+    return decoder_.Resume(b);
+  }
+
+  HpackEntryTypeDecoder decoder_;
+};
+
+TEST_F(HpackEntryTypeDecoderTest, DynamicTableSizeUpdate) {
+  for (uint32_t size = 0; size < 1000 * 1000; size += 256) {
+    HpackBlockBuilder bb;
+    bb.AppendDynamicTableSizeUpdate(size);
+    DecodeBuffer db(bb.buffer());
+    auto validator = [size, this]() -> AssertionResult {
+      VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, decoder_.entry_type());
+      VERIFY_EQ(size, decoder_.varint());
+      return AssertionSuccess();
+    };
+    EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst,
+                                             ValidateDoneAndEmpty(validator)))
+        << "\nentry_type=kDynamicTableSizeUpdate, size=" << size;
+    // Run the validator again to make sure that DecodeAndValidateSeveralWays
+    // did the right thing.
+    EXPECT_TRUE(validator());
+  }
+}
+
+TEST_F(HpackEntryTypeDecoderTest, HeaderWithIndex) {
+  std::vector<HpackEntryType> entry_types = {
+      HpackEntryType::kIndexedHeader,
+      HpackEntryType::kIndexedLiteralHeader,
+      HpackEntryType::kUnindexedLiteralHeader,
+      HpackEntryType::kNeverIndexedLiteralHeader,
+  };
+  for (const HpackEntryType entry_type : entry_types) {
+    const uint32_t first = entry_type == HpackEntryType::kIndexedHeader ? 1 : 0;
+    for (uint32_t index = first; index < 1000; ++index) {
+      HpackBlockBuilder bb;
+      bb.AppendEntryTypeAndVarint(entry_type, index);
+      DecodeBuffer db(bb.buffer());
+      auto validator = [entry_type, index, this]() -> AssertionResult {
+        VERIFY_EQ(entry_type, decoder_.entry_type());
+        VERIFY_EQ(index, decoder_.varint());
+        return AssertionSuccess();
+      };
+      EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst,
+                                               ValidateDoneAndEmpty(validator)))
+          << "\nentry_type=" << entry_type << ", index=" << index;
+      // Run the validator again to make sure that DecodeAndValidateSeveralWays
+      // did the right thing.
+      EXPECT_TRUE(validator());
+    }
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_string_collector.cc b/http2/hpack/decoder/hpack_string_collector.cc
new file mode 100644
index 0000000..247ce9c
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_collector.cc
@@ -0,0 +1,123 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <ostream>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+std::ostream& operator<<(std::ostream& out,
+                         HpackStringCollector::CollectorState v) {
+  switch (v) {
+    case HpackStringCollector::CollectorState::kGenesis:
+      return out << "kGenesis";
+    case HpackStringCollector::CollectorState::kStarted:
+      return out << "kStarted";
+    case HpackStringCollector::CollectorState::kEnded:
+      return out << "kEnded";
+  }
+  return out << "UnknownCollectorState";
+}
+
+}  // namespace
+
+HpackStringCollector::HpackStringCollector() {
+  Clear();
+}
+
+HpackStringCollector::HpackStringCollector(const Http2String& str, bool huffman)
+    : s(str), len(str.size()), huffman_encoded(huffman), state(kEnded) {}
+
+void HpackStringCollector::Clear() {
+  s = "";
+  len = 0;
+  huffman_encoded = false;
+  state = kGenesis;
+}
+
+bool HpackStringCollector::IsClear() const {
+  return s.empty() && len == 0 && huffman_encoded == false && state == kGenesis;
+}
+
+bool HpackStringCollector::IsInProgress() const {
+  return state == kStarted;
+}
+
+bool HpackStringCollector::HasEnded() const {
+  return state == kEnded;
+}
+
+void HpackStringCollector::OnStringStart(bool huffman, size_t length) {
+  EXPECT_TRUE(IsClear()) << ToString();
+  state = kStarted;
+  huffman_encoded = huffman;
+  len = length;
+}
+
+void HpackStringCollector::OnStringData(const char* data, size_t length) {
+  Http2StringPiece sp(data, length);
+  EXPECT_TRUE(IsInProgress()) << ToString();
+  EXPECT_LE(sp.size(), len) << ToString();
+  Http2StrAppend(&s, sp);
+  EXPECT_LE(s.size(), len) << ToString();
+}
+
+void HpackStringCollector::OnStringEnd() {
+  EXPECT_TRUE(IsInProgress()) << ToString();
+  EXPECT_EQ(s.size(), len) << ToString();
+  state = kEnded;
+}
+
+::testing::AssertionResult HpackStringCollector::Collected(
+    Http2StringPiece str,
+    bool is_huffman_encoded) const {
+  VERIFY_TRUE(HasEnded());
+  VERIFY_EQ(str.size(), len);
+  VERIFY_EQ(is_huffman_encoded, huffman_encoded);
+  VERIFY_EQ(str, s);
+  return ::testing::AssertionSuccess();
+}
+
+Http2String HpackStringCollector::ToString() const {
+  std::stringstream ss;
+  ss << *this;
+  return ss.str();
+}
+
+bool operator==(const HpackStringCollector& a, const HpackStringCollector& b) {
+  return a.s == b.s && a.len == b.len &&
+         a.huffman_encoded == b.huffman_encoded && a.state == b.state;
+}
+
+bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b) {
+  return !(a == b);
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v) {
+  out << "HpackStringCollector(state=" << v.state;
+  if (v.state == HpackStringCollector::kGenesis) {
+    return out << ")";
+  }
+  if (v.huffman_encoded) {
+    out << ", Huffman Encoded";
+  }
+  out << ", Length=" << v.len;
+  if (!v.s.empty() && v.len != v.s.size()) {
+    out << " (" << v.s.size() << ")";
+  }
+  return out << ", String=\"" << Http2HexEscape(v.s) << "\")";
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_string_collector.h b/http2/hpack/decoder/hpack_string_collector.h
new file mode 100644
index 0000000..76be13b
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_collector.h
@@ -0,0 +1,63 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_
+
+// Supports tests of decoding HPACK strings.
+
+#include <stddef.h>
+
+#include <iosfwd>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+// Records the callbacks associated with a decoding a string; must
+// call Clear() between decoding successive strings.
+struct HpackStringCollector : public HpackStringDecoderListener {
+  enum CollectorState {
+    kGenesis,
+    kStarted,
+    kEnded,
+  };
+
+  HpackStringCollector();
+  HpackStringCollector(const Http2String& str, bool huffman);
+
+  void Clear();
+  bool IsClear() const;
+  bool IsInProgress() const;
+  bool HasEnded() const;
+
+  void OnStringStart(bool huffman, size_t length) override;
+  void OnStringData(const char* data, size_t length) override;
+  void OnStringEnd() override;
+
+  ::testing::AssertionResult Collected(Http2StringPiece str,
+                                       bool is_huffman_encoded) const;
+
+  Http2String ToString() const;
+
+  Http2String s;
+  size_t len;
+  bool huffman_encoded;
+  CollectorState state;
+};
+
+bool operator==(const HpackStringCollector& a, const HpackStringCollector& b);
+
+bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b);
+
+std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v);
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_
diff --git a/http2/hpack/decoder/hpack_string_decoder.cc b/http2/hpack/decoder/hpack_string_decoder.cc
new file mode 100644
index 0000000..0b9eb59
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_decoder.cc
@@ -0,0 +1,35 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h"
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+Http2String HpackStringDecoder::DebugString() const {
+  return Http2StrCat("HpackStringDecoder(state=", StateToString(state_),
+                     ", length=", length_decoder_.DebugString(),
+                     ", remaining=", remaining_,
+                     ", huffman=", huffman_encoded_ ? "true)" : "false)");
+}
+
+// static
+Http2String HpackStringDecoder::StateToString(StringDecoderState v) {
+  switch (v) {
+    case kStartDecodingLength:
+      return "kStartDecodingLength";
+    case kDecodingString:
+      return "kDecodingString";
+    case kResumeDecodingLength:
+      return "kResumeDecodingLength";
+  }
+  return Http2StrCat("UNKNOWN_STATE(", static_cast<uint32_t>(v), ")");
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackStringDecoder& v) {
+  return out << v.DebugString();
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_string_decoder.h b/http2/hpack/decoder/hpack_string_decoder.h
new file mode 100644
index 0000000..8ec0169
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_decoder.h
@@ -0,0 +1,208 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
+
+// HpackStringDecoder decodes strings encoded per the HPACK spec; this does
+// not mean decompressing Huffman encoded strings, just identifying the length,
+// encoding and contents for a listener.
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <cstdint>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+// Decodes a single string in an HPACK header entry. The high order bit of
+// the first byte of the length is the H (Huffman) bit indicating whether
+// the value is Huffman encoded, and the remainder of the byte is the first
+// 7 bits of an HPACK varint.
+//
+// Call Start() to begin decoding; if it returns kDecodeInProgress, then call
+// Resume() when more input is available, repeating until kDecodeInProgress is
+// not returned. If kDecodeDone or kDecodeError is returned, then Resume() must
+// not be called until Start() has been called to start decoding a new string.
+class HTTP2_EXPORT_PRIVATE HpackStringDecoder {
+ public:
+  enum StringDecoderState {
+    kStartDecodingLength,
+    kDecodingString,
+    kResumeDecodingLength,
+  };
+
+  template <class Listener>
+  DecodeStatus Start(DecodeBuffer* db, Listener* cb) {
+    // Fast decode path is used if the string is under 127 bytes and the
+    // entire length of the string is in the decode buffer. More than 83% of
+    // string lengths are encoded in just one byte.
+    if (db->HasData() && (*db->cursor() & 0x7f) != 0x7f) {
+      // The string is short.
+      uint8_t h_and_prefix = db->DecodeUInt8();
+      uint8_t length = h_and_prefix & 0x7f;
+      bool huffman_encoded = (h_and_prefix & 0x80) == 0x80;
+      cb->OnStringStart(huffman_encoded, length);
+      if (length <= db->Remaining()) {
+        // Yeah, we've got the whole thing in the decode buffer.
+        // Ideally this will be the common case. Note that we don't
+        // update any of the member variables in this path.
+        cb->OnStringData(db->cursor(), length);
+        db->AdvanceCursor(length);
+        cb->OnStringEnd();
+        return DecodeStatus::kDecodeDone;
+      }
+      // Not all in the buffer.
+      huffman_encoded_ = huffman_encoded;
+      remaining_ = length;
+      // Call Resume to decode the string body, which is only partially
+      // in the decode buffer (or not at all).
+      state_ = kDecodingString;
+      return Resume(db, cb);
+    }
+    // Call Resume to decode the string length, which is either not in
+    // the decode buffer, or spans multiple bytes.
+    state_ = kStartDecodingLength;
+    return Resume(db, cb);
+  }
+
+  template <class Listener>
+  DecodeStatus Resume(DecodeBuffer* db, Listener* cb) {
+    DecodeStatus status;
+    while (true) {
+      switch (state_) {
+        case kStartDecodingLength:
+          DVLOG(2) << "kStartDecodingLength: db->Remaining=" << db->Remaining();
+          if (!StartDecodingLength(db, cb, &status)) {
+            // The length is split across decode buffers.
+            return status;
+          }
+          // We've finished decoding the length, which spanned one or more
+          // bytes. Approximately 17% of strings have a length that is greater
+          // than 126 bytes, and thus the length is encoded in more than one
+          // byte, and so doesn't get the benefit of the optimization in
+          // Start() for single byte lengths. But, we still expect that most
+          // of such strings will be contained entirely in a single decode
+          // buffer, and hence this fall through skips another trip through the
+          // switch above and more importantly skips setting the state_ variable
+          // again in those cases where we don't need it.
+          HTTP2_FALLTHROUGH;
+
+        case kDecodingString:
+          DVLOG(2) << "kDecodingString: db->Remaining=" << db->Remaining()
+                   << "    remaining_=" << remaining_;
+          return DecodeString(db, cb);
+
+        case kResumeDecodingLength:
+          DVLOG(2) << "kResumeDecodingLength: db->Remaining="
+                   << db->Remaining();
+          if (!ResumeDecodingLength(db, cb, &status)) {
+            return status;
+          }
+      }
+    }
+  }
+
+  Http2String DebugString() const;
+
+ private:
+  static Http2String StateToString(StringDecoderState v);
+
+  // Returns true if the length is fully decoded and the listener wants the
+  // decoding to continue, false otherwise; status is set to the status from
+  // the varint decoder.
+  // If the length is not fully decoded, case state_ is set appropriately
+  // for the next call to Resume.
+  template <class Listener>
+  bool StartDecodingLength(DecodeBuffer* db,
+                           Listener* cb,
+                           DecodeStatus* status) {
+    if (db->Empty()) {
+      *status = DecodeStatus::kDecodeInProgress;
+      state_ = kStartDecodingLength;
+      return false;
+    }
+    uint8_t h_and_prefix = db->DecodeUInt8();
+    huffman_encoded_ = (h_and_prefix & 0x80) == 0x80;
+    *status = length_decoder_.Start(h_and_prefix, 7, db);
+    if (*status == DecodeStatus::kDecodeDone) {
+      OnStringStart(cb, status);
+      return true;
+    }
+    // Set the state to cover the DecodeStatus::kDecodeInProgress case.
+    // Won't be needed if the status is kDecodeError.
+    state_ = kResumeDecodingLength;
+    return false;
+  }
+
+  // Returns true if the length is fully decoded and the listener wants the
+  // decoding to continue, false otherwise; status is set to the status from
+  // the varint decoder; state_ is updated when fully decoded.
+  // If the length is not fully decoded, case state_ is set appropriately
+  // for the next call to Resume.
+  template <class Listener>
+  bool ResumeDecodingLength(DecodeBuffer* db,
+                            Listener* cb,
+                            DecodeStatus* status) {
+    DCHECK_EQ(state_, kResumeDecodingLength);
+    *status = length_decoder_.Resume(db);
+    if (*status == DecodeStatus::kDecodeDone) {
+      state_ = kDecodingString;
+      OnStringStart(cb, status);
+      return true;
+    }
+    return false;
+  }
+
+  // Returns true if the listener wants the decoding to continue, and
+  // false otherwise, in which case status set.
+  template <class Listener>
+  void OnStringStart(Listener* cb, DecodeStatus* status) {
+    remaining_ = length_decoder_.value();
+    // Make callback so consumer knows what is coming.
+    cb->OnStringStart(huffman_encoded_, remaining_);
+  }
+
+  // Passes the available portion of the string to the listener, and signals
+  // the end of the string when it is reached. Returns kDecodeDone or
+  // kDecodeInProgress as appropriate.
+  template <class Listener>
+  DecodeStatus DecodeString(DecodeBuffer* db, Listener* cb) {
+    size_t len = std::min(remaining_, db->Remaining());
+    if (len > 0) {
+      cb->OnStringData(db->cursor(), len);
+      db->AdvanceCursor(len);
+      remaining_ -= len;
+    }
+    if (remaining_ == 0) {
+      cb->OnStringEnd();
+      return DecodeStatus::kDecodeDone;
+    }
+    state_ = kDecodingString;
+    return DecodeStatus::kDecodeInProgress;
+  }
+
+  HpackVarintDecoder length_decoder_;
+
+  // These fields are initialized just to keep ASAN happy about reading
+  // them from DebugString().
+  size_t remaining_ = 0;
+  StringDecoderState state_ = kStartDecodingLength;
+  bool huffman_encoded_ = false;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+                                              const HpackStringDecoder& v);
+
+}  // namespace http2
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
diff --git a/http2/hpack/decoder/hpack_string_decoder_listener.cc b/http2/hpack/decoder/hpack_string_decoder_listener.cc
new file mode 100644
index 0000000..e0fbc65
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_decoder_listener.cc
@@ -0,0 +1,36 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h"
+
+#include "base/logging.h"
+
+namespace http2 {
+namespace test {
+
+void HpackStringDecoderVLoggingListener::OnStringStart(bool huffman_encoded,
+                                                       size_t len) {
+  VLOG(1) << "OnStringStart: H=" << huffman_encoded << ", len=" << len;
+  if (wrapped_) {
+    wrapped_->OnStringStart(huffman_encoded, len);
+  }
+}
+
+void HpackStringDecoderVLoggingListener::OnStringData(const char* data,
+                                                      size_t len) {
+  VLOG(1) << "OnStringData: len=" << len;
+  if (wrapped_) {
+    return wrapped_->OnStringData(data, len);
+  }
+}
+
+void HpackStringDecoderVLoggingListener::OnStringEnd() {
+  VLOG(1) << "OnStringEnd";
+  if (wrapped_) {
+    return wrapped_->OnStringEnd();
+  }
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_string_decoder_listener.h b/http2/hpack/decoder/hpack_string_decoder_listener.h
new file mode 100644
index 0000000..35a0417
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_decoder_listener.h
@@ -0,0 +1,62 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_
+
+// Defines HpackStringDecoderListener which defines the methods required by an
+// HpackStringDecoder. Also defines HpackStringDecoderVLoggingListener which
+// logs before calling another HpackStringDecoderListener implementation.
+// For now these are only used by tests, so placed in the test namespace.
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+
+// HpackStringDecoder methods require a listener that implements the methods
+// below, but it is NOT necessary to extend this class because the methods
+// are templates.
+class HTTP2_EXPORT_PRIVATE HpackStringDecoderListener {
+ public:
+  virtual ~HpackStringDecoderListener() {}
+
+  // Called at the start of decoding an HPACK string. The encoded length of the
+  // string is |len| bytes, which may be zero. The string is Huffman encoded
+  // if huffman_encoded is true, else it is plain text (i.e. the encoded length
+  // is then the plain text length).
+  virtual void OnStringStart(bool huffman_encoded, size_t len) = 0;
+
+  // Called when some data is available, or once when the string length is zero
+  // (to simplify the decoder, it doesn't have a special case for len==0).
+  virtual void OnStringData(const char* data, size_t len) = 0;
+
+  // Called after OnStringData has provided all of the encoded bytes of the
+  // string.
+  virtual void OnStringEnd() = 0;
+};
+
+class HTTP2_EXPORT_PRIVATE HpackStringDecoderVLoggingListener
+    : public HpackStringDecoderListener {
+ public:
+  HpackStringDecoderVLoggingListener() : wrapped_(nullptr) {}
+  explicit HpackStringDecoderVLoggingListener(
+      HpackStringDecoderListener* wrapped)
+      : wrapped_(wrapped) {}
+  ~HpackStringDecoderVLoggingListener() override {}
+
+  void OnStringStart(bool huffman_encoded, size_t len) override;
+  void OnStringData(const char* data, size_t len) override;
+  void OnStringEnd() override;
+
+ private:
+  HpackStringDecoderListener* const wrapped_;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_
diff --git a/http2/hpack/decoder/hpack_string_decoder_test.cc b/http2/hpack/decoder/hpack_string_decoder_test.cc
new file mode 100644
index 0000000..349b649
--- /dev/null
+++ b/http2/hpack/decoder/hpack_string_decoder_test.cc
@@ -0,0 +1,155 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h"
+
+// Tests of HpackStringDecoder.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+const bool kMayReturnZeroOnFirst = false;
+const bool kCompressed = true;
+const bool kUncompressed = false;
+
+class HpackStringDecoderTest : public RandomDecoderTest {
+ protected:
+  HpackStringDecoderTest() : listener_(&collector_) {}
+
+  DecodeStatus StartDecoding(DecodeBuffer* b) override {
+    ++start_decoding_calls_;
+    collector_.Clear();
+    return decoder_.Start(b, &listener_);
+  }
+
+  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+    // Provides coverage of DebugString and StateToString.
+    // Not validating output.
+    VLOG(1) << decoder_.DebugString();
+    VLOG(2) << collector_;
+    return decoder_.Resume(b, &listener_);
+  }
+
+  AssertionResult Collected(Http2StringPiece s, bool huffman_encoded) {
+    VLOG(1) << collector_;
+    return collector_.Collected(s, huffman_encoded);
+  }
+
+  // expected_str is a Http2String rather than a const Http2String& or
+  // Http2StringPiece so that the lambda makes a copy of the string, and thus
+  // the string to be passed to Collected outlives the call to MakeValidator.
+  Validator MakeValidator(const Http2String& expected_str,
+                          bool expected_huffman) {
+    return
+        [expected_str, expected_huffman, this](
+            const DecodeBuffer& input, DecodeStatus status) -> AssertionResult {
+          AssertionResult result = Collected(expected_str, expected_huffman);
+          if (result) {
+            VERIFY_EQ(collector_,
+                      HpackStringCollector(expected_str, expected_huffman));
+          } else {
+            VERIFY_NE(collector_,
+                      HpackStringCollector(expected_str, expected_huffman));
+          }
+          VLOG(2) << collector_.ToString();
+          collector_.Clear();
+          VLOG(2) << collector_;
+          return result;
+        };
+  }
+
+  HpackStringDecoder decoder_;
+  HpackStringCollector collector_;
+  HpackStringDecoderVLoggingListener listener_;
+  size_t start_decoding_calls_ = 0;
+};
+
+TEST_F(HpackStringDecoderTest, DecodeEmptyString) {
+  {
+    Validator validator = ValidateDoneAndEmpty(MakeValidator("", kCompressed));
+    const char kData[] = {'\x80'};
+    DecodeBuffer b(kData);
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+  }
+  {
+    // Make sure it stops after decoding the empty string.
+    Validator validator =
+        ValidateDoneAndOffset(1, MakeValidator("", kUncompressed));
+    const char kData[] = {'\x00', '\xff'};
+    DecodeBuffer b(kData);
+    EXPECT_EQ(2u, b.Remaining());
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+    EXPECT_EQ(1u, b.Remaining());
+  }
+}
+
+TEST_F(HpackStringDecoderTest, DecodeShortString) {
+  {
+    // Make sure it stops after decoding the non-empty string.
+    Validator validator =
+        ValidateDoneAndOffset(11, MakeValidator("start end.", kCompressed));
+    const char kData[] = "\x8astart end.Don't peek at this.";
+    DecodeBuffer b(kData);
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+  }
+  {
+    Validator validator =
+        ValidateDoneAndOffset(11, MakeValidator("start end.", kUncompressed));
+    Http2StringPiece data("\x0astart end.");
+    DecodeBuffer b(data);
+    EXPECT_TRUE(
+        DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+  }
+}
+
+TEST_F(HpackStringDecoderTest, DecodeLongStrings) {
+  Http2String name = Random().RandString(1024);
+  Http2String value = Random().RandString(65536);
+  HpackBlockBuilder hbb;
+
+  hbb.AppendString(false, name);
+  uint32_t offset_after_name = hbb.size();
+  EXPECT_EQ(3 + name.size(), offset_after_name);
+
+  hbb.AppendString(true, value);
+  uint32_t offset_after_value = hbb.size();
+  EXPECT_EQ(3 + name.size() + 4 + value.size(), offset_after_value);
+
+  DecodeBuffer b(hbb.buffer());
+
+  // Decode the name...
+  EXPECT_TRUE(DecodeAndValidateSeveralWays(
+      &b, kMayReturnZeroOnFirst,
+      ValidateDoneAndOffset(offset_after_name,
+                            MakeValidator(name, kUncompressed))));
+  EXPECT_EQ(offset_after_name, b.Offset());
+  EXPECT_EQ(offset_after_value - offset_after_name, b.Remaining());
+
+  // Decode the value...
+  EXPECT_TRUE(DecodeAndValidateSeveralWays(
+      &b, kMayReturnZeroOnFirst,
+      ValidateDoneAndOffset(offset_after_value - offset_after_name,
+                            MakeValidator(value, kCompressed))));
+  EXPECT_EQ(offset_after_value, b.Offset());
+  EXPECT_EQ(0u, b.Remaining());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_whole_entry_buffer.cc b/http2/hpack/decoder/hpack_whole_entry_buffer.cc
new file mode 100644
index 0000000..b3e29b1
--- /dev/null
+++ b/http2/hpack/decoder/hpack_whole_entry_buffer.cc
@@ -0,0 +1,139 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+HpackWholeEntryBuffer::HpackWholeEntryBuffer(HpackWholeEntryListener* listener,
+                                             size_t max_string_size_bytes)
+    : max_string_size_bytes_(max_string_size_bytes) {
+  set_listener(listener);
+}
+HpackWholeEntryBuffer::~HpackWholeEntryBuffer() = default;
+
+void HpackWholeEntryBuffer::set_listener(HpackWholeEntryListener* listener) {
+  listener_ = HTTP2_DIE_IF_NULL(listener);
+}
+
+void HpackWholeEntryBuffer::set_max_string_size_bytes(
+    size_t max_string_size_bytes) {
+  max_string_size_bytes_ = max_string_size_bytes;
+}
+
+void HpackWholeEntryBuffer::BufferStringsIfUnbuffered() {
+  name_.BufferStringIfUnbuffered();
+  value_.BufferStringIfUnbuffered();
+}
+
+size_t HpackWholeEntryBuffer::EstimateMemoryUsage() const {
+  return Http2EstimateMemoryUsage(name_) + Http2EstimateMemoryUsage(value_);
+}
+
+void HpackWholeEntryBuffer::OnIndexedHeader(size_t index) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnIndexedHeader: index=" << index;
+  listener_->OnIndexedHeader(index);
+}
+
+void HpackWholeEntryBuffer::OnStartLiteralHeader(HpackEntryType entry_type,
+                                                 size_t maybe_name_index) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnStartLiteralHeader: entry_type="
+           << entry_type << ",  maybe_name_index=" << maybe_name_index;
+  entry_type_ = entry_type;
+  maybe_name_index_ = maybe_name_index;
+}
+
+void HpackWholeEntryBuffer::OnNameStart(bool huffman_encoded, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnNameStart: huffman_encoded="
+           << (huffman_encoded ? "true" : "false") << ",  len=" << len;
+  DCHECK_EQ(maybe_name_index_, 0u);
+  if (!error_detected_) {
+    if (len > max_string_size_bytes_) {
+      DVLOG(1) << "Name length (" << len << ") is longer than permitted ("
+               << max_string_size_bytes_ << ")";
+      ReportError("HPACK entry name size is too long.");
+      return;
+    }
+    name_.OnStart(huffman_encoded, len);
+  }
+}
+
+void HpackWholeEntryBuffer::OnNameData(const char* data, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnNameData: len=" << len << " data:\n"
+           << Http2HexDump(Http2StringPiece(data, len));
+  DCHECK_EQ(maybe_name_index_, 0u);
+  if (!error_detected_ && !name_.OnData(data, len)) {
+    ReportError("Error decoding HPACK entry name.");
+  }
+}
+
+void HpackWholeEntryBuffer::OnNameEnd() {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnNameEnd";
+  DCHECK_EQ(maybe_name_index_, 0u);
+  if (!error_detected_ && !name_.OnEnd()) {
+    ReportError("Error decoding HPACK entry name.");
+  }
+}
+
+void HpackWholeEntryBuffer::OnValueStart(bool huffman_encoded, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnValueStart: huffman_encoded="
+           << (huffman_encoded ? "true" : "false") << ",  len=" << len;
+  if (!error_detected_) {
+    if (len > max_string_size_bytes_) {
+      DVLOG(1) << "Value length (" << len << ") is longer than permitted ("
+               << max_string_size_bytes_ << ")";
+      ReportError("HPACK entry value size is too long.");
+      return;
+    }
+    value_.OnStart(huffman_encoded, len);
+  }
+}
+
+void HpackWholeEntryBuffer::OnValueData(const char* data, size_t len) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnValueData: len=" << len << " data:\n"
+           << Http2HexDump(Http2StringPiece(data, len));
+  if (!error_detected_ && !value_.OnData(data, len)) {
+    ReportError("Error decoding HPACK entry value.");
+  }
+}
+
+void HpackWholeEntryBuffer::OnValueEnd() {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnValueEnd";
+  if (error_detected_) {
+    return;
+  }
+  if (!value_.OnEnd()) {
+    ReportError("Error decoding HPACK entry value.");
+    return;
+  }
+  if (maybe_name_index_ == 0) {
+    listener_->OnLiteralNameAndValue(entry_type_, &name_, &value_);
+    name_.Reset();
+  } else {
+    listener_->OnNameIndexAndLiteralValue(entry_type_, maybe_name_index_,
+                                          &value_);
+  }
+  value_.Reset();
+}
+
+void HpackWholeEntryBuffer::OnDynamicTableSizeUpdate(size_t size) {
+  DVLOG(2) << "HpackWholeEntryBuffer::OnDynamicTableSizeUpdate: size=" << size;
+  listener_->OnDynamicTableSizeUpdate(size);
+}
+
+void HpackWholeEntryBuffer::ReportError(Http2StringPiece error_message) {
+  if (!error_detected_) {
+    DVLOG(1) << "HpackWholeEntryBuffer::ReportError: " << error_message;
+    error_detected_ = true;
+    listener_->OnHpackDecodeError(error_message);
+    listener_ = HpackWholeEntryNoOpListener::NoOpListener();
+  }
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_whole_entry_buffer.h b/http2/hpack/decoder/hpack_whole_entry_buffer.h
new file mode 100644
index 0000000..61bf583
--- /dev/null
+++ b/http2/hpack/decoder/hpack_whole_entry_buffer.h
@@ -0,0 +1,104 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
+
+// HpackWholeEntryBuffer isolates a listener from the fact that an entry may
+// be split across multiple input buffers, providing one callback per entry.
+// HpackWholeEntryBuffer requires that the HpackEntryDecoderListener be made in
+// the correct order, which is tested by hpack_entry_decoder_test.cc.
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+// TODO(jamessynge): Consider renaming HpackEntryDecoderListener to
+// HpackEntryPartsListener or HpackEntryFragmentsListener.
+class HTTP2_EXPORT_PRIVATE HpackWholeEntryBuffer
+    : public HpackEntryDecoderListener {
+ public:
+  // max_string_size specifies the maximum size of an on-the-wire string (name
+  // or value, plain or Huffman encoded) that will be accepted. See sections
+  // 5.1 and 5.2 of RFC 7541. This is a defense against OOM attacks; HTTP/2
+  // allows a decoder to enforce any limit of the size of the header lists
+  // that it is willing decode, including less than the MAX_HEADER_LIST_SIZE
+  // setting, a setting that is initially unlimited. For example, we might
+  // choose to send a MAX_HEADER_LIST_SIZE of 64KB, and to use that same value
+  // as the upper bound for individual strings.
+  HpackWholeEntryBuffer(HpackWholeEntryListener* listener,
+                        size_t max_string_size);
+  ~HpackWholeEntryBuffer() override;
+
+  HpackWholeEntryBuffer(const HpackWholeEntryBuffer&) = delete;
+  HpackWholeEntryBuffer& operator=(const HpackWholeEntryBuffer&) = delete;
+
+  // Set the listener to be notified when a whole entry has been decoded.
+  // The listener may be changed at any time.
+  void set_listener(HpackWholeEntryListener* listener);
+
+  // Set how much encoded data this decoder is willing to buffer.
+  // TODO(jamessynge): Come up with consistent semantics for this protection
+  // across the various decoders; e.g. should it be for a single string or
+  // a single header entry?
+  void set_max_string_size_bytes(size_t max_string_size_bytes);
+
+  // Ensure that decoded strings pointed to by the HpackDecoderStringBuffer
+  // instances name_ and value_ are buffered, which allows any underlying
+  // transport buffer to be freed or reused without overwriting the decoded
+  // strings. This is needed only when an HPACK entry is split across transport
+  // buffers. See HpackDecoder::DecodeFragment.
+  void BufferStringsIfUnbuffered();
+
+  // Was an error detected? After an error has been detected and reported,
+  // no further callbacks will be made to the listener.
+  bool error_detected() const { return error_detected_; }
+
+  // Returns the estimate of dynamically allocated memory in bytes.
+  size_t EstimateMemoryUsage() const;
+
+  // Implement the HpackEntryDecoderListener methods.
+
+  void OnIndexedHeader(size_t index) override;
+  void OnStartLiteralHeader(HpackEntryType entry_type,
+                            size_t maybe_name_index) override;
+  void OnNameStart(bool huffman_encoded, size_t len) override;
+  void OnNameData(const char* data, size_t len) override;
+  void OnNameEnd() override;
+  void OnValueStart(bool huffman_encoded, size_t len) override;
+  void OnValueData(const char* data, size_t len) override;
+  void OnValueEnd() override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+
+ private:
+  void ReportError(Http2StringPiece error_message);
+
+  HpackWholeEntryListener* listener_;
+  HpackDecoderStringBuffer name_, value_;
+
+  // max_string_size_bytes_ specifies the maximum allowed size of an on-the-wire
+  // string. Larger strings will be reported as errors to the listener; the
+  // endpoint should treat these as COMPRESSION errors, which are CONNECTION
+  // level errors.
+  size_t max_string_size_bytes_;
+
+  // The name index (or zero) of the current header entry with a literal value.
+  size_t maybe_name_index_;
+
+  // The type of the current header entry (with literals) that is being decoded.
+  HpackEntryType entry_type_;
+
+  bool error_detected_ = false;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
diff --git a/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc b/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc
new file mode 100644
index 0000000..75b281c
--- /dev/null
+++ b/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc
@@ -0,0 +1,206 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h"
+
+// Tests of HpackWholeEntryBuffer: does it buffer correctly, and does it
+// detect Huffman decoding errors and oversize string errors?
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+using ::testing::InSequence;
+using ::testing::Property;
+using ::testing::StrictMock;
+
+namespace http2 {
+namespace test {
+namespace {
+
+constexpr size_t kMaxStringSize = 20;
+
+class MockHpackWholeEntryListener : public HpackWholeEntryListener {
+ public:
+  ~MockHpackWholeEntryListener() override = default;
+
+  MOCK_METHOD1(OnIndexedHeader, void(size_t index));
+  MOCK_METHOD3(OnNameIndexAndLiteralValue,
+               void(HpackEntryType entry_type,
+                    size_t name_index,
+                    HpackDecoderStringBuffer* value_buffer));
+  MOCK_METHOD3(OnLiteralNameAndValue,
+               void(HpackEntryType entry_type,
+                    HpackDecoderStringBuffer* name_buffer,
+                    HpackDecoderStringBuffer* value_buffer));
+  MOCK_METHOD1(OnDynamicTableSizeUpdate, void(size_t size));
+  MOCK_METHOD1(OnHpackDecodeError, void(Http2StringPiece error_message));
+};
+
+class HpackWholeEntryBufferTest : public ::testing::Test {
+ protected:
+  HpackWholeEntryBufferTest() : entry_buffer_(&listener_, kMaxStringSize) {}
+  ~HpackWholeEntryBufferTest() override = default;
+
+  StrictMock<MockHpackWholeEntryListener> listener_;
+  HpackWholeEntryBuffer entry_buffer_;
+};
+
+// OnIndexedHeader is an immediate pass through.
+TEST_F(HpackWholeEntryBufferTest, OnIndexedHeader) {
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(17));
+    entry_buffer_.OnIndexedHeader(17);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(62));
+    entry_buffer_.OnIndexedHeader(62);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(62));
+    entry_buffer_.OnIndexedHeader(62);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnIndexedHeader(128));
+    entry_buffer_.OnIndexedHeader(128);
+  }
+  StrictMock<MockHpackWholeEntryListener> listener2;
+  entry_buffer_.set_listener(&listener2);
+  {
+    InSequence seq;
+    EXPECT_CALL(listener2, OnIndexedHeader(100));
+    entry_buffer_.OnIndexedHeader(100);
+  }
+}
+
+// OnDynamicTableSizeUpdate is an immediate pass through.
+TEST_F(HpackWholeEntryBufferTest, OnDynamicTableSizeUpdate) {
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(4096));
+    entry_buffer_.OnDynamicTableSizeUpdate(4096);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(0));
+    entry_buffer_.OnDynamicTableSizeUpdate(0);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024));
+    entry_buffer_.OnDynamicTableSizeUpdate(1024);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024));
+    entry_buffer_.OnDynamicTableSizeUpdate(1024);
+  }
+  StrictMock<MockHpackWholeEntryListener> listener2;
+  entry_buffer_.set_listener(&listener2);
+  {
+    InSequence seq;
+    EXPECT_CALL(listener2, OnDynamicTableSizeUpdate(0));
+    entry_buffer_.OnDynamicTableSizeUpdate(0);
+  }
+}
+
+TEST_F(HpackWholeEntryBufferTest, OnNameIndexAndLiteralValue) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader,
+                                     123);
+  entry_buffer_.OnValueStart(false, 10);
+  entry_buffer_.OnValueData("some data.", 10);
+
+  // Force the value to be buffered.
+  entry_buffer_.BufferStringsIfUnbuffered();
+
+  EXPECT_CALL(
+      listener_,
+      OnNameIndexAndLiteralValue(
+          HpackEntryType::kNeverIndexedLiteralHeader, 123,
+          AllOf(Property(&HpackDecoderStringBuffer::str, "some data."),
+                Property(&HpackDecoderStringBuffer::BufferedLength, 10))));
+
+  entry_buffer_.OnValueEnd();
+}
+
+TEST_F(HpackWholeEntryBufferTest, OnLiteralNameAndValue) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0);
+  // Force the name to be buffered by delivering it in two pieces.
+  entry_buffer_.OnNameStart(false, 9);
+  entry_buffer_.OnNameData("some-", 5);
+  entry_buffer_.OnNameData("name", 4);
+  entry_buffer_.OnNameEnd();
+  entry_buffer_.OnValueStart(false, 12);
+  entry_buffer_.OnValueData("Header Value", 12);
+
+  EXPECT_CALL(
+      listener_,
+      OnLiteralNameAndValue(
+          HpackEntryType::kIndexedLiteralHeader,
+          AllOf(Property(&HpackDecoderStringBuffer::str, "some-name"),
+                Property(&HpackDecoderStringBuffer::BufferedLength, 9)),
+          AllOf(Property(&HpackDecoderStringBuffer::str, "Header Value"),
+                Property(&HpackDecoderStringBuffer::BufferedLength, 0))));
+
+  entry_buffer_.OnValueEnd();
+}
+
+// Verify that a name longer than the allowed size generates an error.
+TEST_F(HpackWholeEntryBufferTest, NameTooLong) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0);
+  EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry name")));
+  entry_buffer_.OnNameStart(false, kMaxStringSize + 1);
+}
+
+// Verify that a name longer than the allowed size generates an error.
+TEST_F(HpackWholeEntryBufferTest, ValueTooLong) {
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 1);
+  EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry value")));
+  entry_buffer_.OnValueStart(false, kMaxStringSize + 1);
+}
+
+// Verify that a Huffman encoded name with an explicit EOS generates an error
+// for an explicit EOS.
+TEST_F(HpackWholeEntryBufferTest, NameHuffmanError) {
+  const char data[] = "\xff\xff\xff";
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kUnindexedLiteralHeader,
+                                     0);
+  entry_buffer_.OnNameStart(true, 4);
+  entry_buffer_.OnNameData(data, 3);
+
+  EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry name")));
+
+  entry_buffer_.OnNameData(data, 1);
+
+  // After an error is reported, the listener is not called again.
+  EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(8096)).Times(0);
+  entry_buffer_.OnDynamicTableSizeUpdate(8096);
+}
+
+// Verify that a Huffman encoded value that isn't properly terminated with
+// a partial EOS symbol generates an error.
+TEST_F(HpackWholeEntryBufferTest, ValueeHuffmanError) {
+  const char data[] = "\x00\x00\x00";
+  entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader,
+                                     61);
+  entry_buffer_.OnValueStart(true, 3);
+  entry_buffer_.OnValueData(data, 3);
+
+  EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry value")));
+
+  entry_buffer_.OnValueEnd();
+
+  // After an error is reported, the listener is not called again.
+  EXPECT_CALL(listener_, OnIndexedHeader(17)).Times(0);
+  entry_buffer_.OnIndexedHeader(17);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_whole_entry_listener.cc b/http2/hpack/decoder/hpack_whole_entry_listener.cc
new file mode 100644
index 0000000..b92e64a
--- /dev/null
+++ b/http2/hpack/decoder/hpack_whole_entry_listener.cc
@@ -0,0 +1,33 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h"
+
+namespace http2 {
+
+HpackWholeEntryListener::~HpackWholeEntryListener() = default;
+
+HpackWholeEntryNoOpListener::~HpackWholeEntryNoOpListener() = default;
+
+void HpackWholeEntryNoOpListener::OnIndexedHeader(size_t index) {}
+void HpackWholeEntryNoOpListener::OnNameIndexAndLiteralValue(
+    HpackEntryType entry_type,
+    size_t name_index,
+    HpackDecoderStringBuffer* value_buffer) {}
+void HpackWholeEntryNoOpListener::OnLiteralNameAndValue(
+    HpackEntryType entry_type,
+    HpackDecoderStringBuffer* name_buffer,
+    HpackDecoderStringBuffer* value_buffer) {}
+void HpackWholeEntryNoOpListener::OnDynamicTableSizeUpdate(size_t size) {}
+void HpackWholeEntryNoOpListener::OnHpackDecodeError(
+    Http2StringPiece error_message) {}
+
+// static
+HpackWholeEntryNoOpListener* HpackWholeEntryNoOpListener::NoOpListener() {
+  static HpackWholeEntryNoOpListener* static_instance =
+      new HpackWholeEntryNoOpListener();
+  return static_instance;
+}
+
+}  // namespace http2
diff --git a/http2/hpack/decoder/hpack_whole_entry_listener.h b/http2/hpack/decoder/hpack_whole_entry_listener.h
new file mode 100644
index 0000000..2e559ce
--- /dev/null
+++ b/http2/hpack/decoder/hpack_whole_entry_listener.h
@@ -0,0 +1,80 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Defines HpackWholeEntryListener, the base class of listeners for decoded
+// complete HPACK entries, as opposed to HpackEntryDecoderListener which
+// receives multiple callbacks for some single entries.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackWholeEntryListener {
+ public:
+  virtual ~HpackWholeEntryListener();
+
+  // Called when an indexed header (i.e. one in the static or dynamic table) has
+  // been decoded from an HPACK block. index is supposed to be non-zero, but
+  // that has not been checked by the caller.
+  virtual void OnIndexedHeader(size_t index) = 0;
+
+  // Called when a header entry with a name index and literal value has
+  // been fully decoded from an HPACK block. name_index is NOT zero.
+  // entry_type will be kIndexedLiteralHeader, kUnindexedLiteralHeader, or
+  // kNeverIndexedLiteralHeader.
+  virtual void OnNameIndexAndLiteralValue(
+      HpackEntryType entry_type,
+      size_t name_index,
+      HpackDecoderStringBuffer* value_buffer) = 0;
+
+  // Called when a header entry with a literal name and literal value
+  // has been fully decoded from an HPACK block. entry_type will be
+  // kIndexedLiteralHeader, kUnindexedLiteralHeader, or
+  // kNeverIndexedLiteralHeader.
+  virtual void OnLiteralNameAndValue(
+      HpackEntryType entry_type,
+      HpackDecoderStringBuffer* name_buffer,
+      HpackDecoderStringBuffer* value_buffer) = 0;
+
+  // Called when an update to the size of the peer's dynamic table has been
+  // decoded.
+  virtual void OnDynamicTableSizeUpdate(size_t size) = 0;
+
+  // OnHpackDecodeError is called if an error is detected while decoding.
+  // error_message may be used in a GOAWAY frame as the Opaque Data.
+  virtual void OnHpackDecodeError(Http2StringPiece error_message) = 0;
+};
+
+// A no-op implementation of HpackWholeEntryDecoderListener, useful for ignoring
+// callbacks once an error is detected.
+class HpackWholeEntryNoOpListener : public HpackWholeEntryListener {
+ public:
+  ~HpackWholeEntryNoOpListener() override;
+
+  void OnIndexedHeader(size_t index) override;
+  void OnNameIndexAndLiteralValue(
+      HpackEntryType entry_type,
+      size_t name_index,
+      HpackDecoderStringBuffer* value_buffer) override;
+  void OnLiteralNameAndValue(HpackEntryType entry_type,
+                             HpackDecoderStringBuffer* name_buffer,
+                             HpackDecoderStringBuffer* value_buffer) override;
+  void OnDynamicTableSizeUpdate(size_t size) override;
+  void OnHpackDecodeError(Http2StringPiece error_message) override;
+
+  // Returns a listener that ignores all the calls.
+  static HpackWholeEntryNoOpListener* NoOpListener();
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_