Project import generated by Copybara.

PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/core/qpack/fuzzer/qpack_decoder_fuzzer.cc b/quic/core/qpack/fuzzer/qpack_decoder_fuzzer.cc
new file mode 100644
index 0000000..cb66b27
--- /dev/null
+++ b/quic/core/qpack/fuzzer/qpack_decoder_fuzzer.cc
@@ -0,0 +1,43 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_fuzzed_data_provider.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+namespace test {
+
+// This fuzzer exercises QpackDecoder.  It should be able to cover all possible
+// code paths.  There is no point in encoding QpackDecoder's output to turn this
+// into a roundtrip test, because the same header list can be encoded in many
+// different ways, so the output could not be expected to match the original
+// input.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  NoOpHeadersHandler handler;
+  QuicFuzzedDataProvider provider(data, size);
+
+  // Process up to 64 kB fragments at a time.  Too small upper bound might not
+  // provide enough coverage, too large would make fuzzing less efficient.
+  auto fragment_size_generator =
+      std::bind(&QuicFuzzedDataProvider::ConsumeIntegralInRange<uint16_t>,
+                &provider, 1, std::numeric_limits<uint16_t>::max());
+
+  NoopEncoderStreamErrorDelegate encoder_stream_error_delegate;
+  NoopDecoderStreamSenderDelegate decoder_stream_sender_delegate;
+  QpackDecode(&encoder_stream_error_delegate, &decoder_stream_sender_delegate,
+              &handler, fragment_size_generator,
+              provider.ConsumeRemainingBytesAsString());
+
+  return 0;
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/fuzzer/qpack_encoder_stream_receiver_fuzzer.cc b/quic/core/qpack/fuzzer/qpack_encoder_stream_receiver_fuzzer.cc
new file mode 100644
index 0000000..980889d
--- /dev/null
+++ b/quic/core/qpack/fuzzer/qpack_encoder_stream_receiver_fuzzer.cc
@@ -0,0 +1,67 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_receiver.h"
+
+#include <cstddef>
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_fuzzed_data_provider.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+// A QpackEncoderStreamReceiver::Delegate implementation that ignores all
+// decoded instructions but keeps track of whether an error has been detected.
+class NoOpDelegate : public QpackEncoderStreamReceiver::Delegate {
+ public:
+  NoOpDelegate() : error_detected_(false) {}
+  ~NoOpDelegate() override = default;
+
+  void OnInsertWithNameReference(bool is_static,
+                                 uint64_t name_index,
+                                 QuicStringPiece value) override {}
+  void OnInsertWithoutNameReference(QuicStringPiece name,
+                                    QuicStringPiece value) override {}
+  void OnDuplicate(uint64_t index) override {}
+  void OnSetDynamicTableCapacity(uint64_t capacity) override {}
+  void OnErrorDetected(QuicStringPiece error_message) override {
+    error_detected_ = true;
+  }
+
+  bool error_detected() const { return error_detected_; }
+
+ private:
+  bool error_detected_;
+};
+
+}  // namespace
+
+// This fuzzer exercises QpackEncoderStreamReceiver.
+// Note that since string literals may be encoded with or without Huffman
+// encoding, one could not expect identical encoded data if the decoded
+// instructions were fed into QpackEncoderStreamSender.  Therefore there is no
+// point in extending this fuzzer into a round-trip test.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  NoOpDelegate delegate;
+  QpackEncoderStreamReceiver receiver(&delegate);
+
+  QuicFuzzedDataProvider provider(data, size);
+
+  while (!delegate.error_detected() && provider.remaining_bytes() != 0) {
+    // Process up to 64 kB fragments at a time.  Too small upper bound might not
+    // provide enough coverage, too large might make fuzzing too inefficient.
+    size_t fragment_size = provider.ConsumeIntegralInRange<uint16_t>(
+        1, std::numeric_limits<uint16_t>::max());
+    receiver.Decode(provider.ConsumeRandomLengthString(fragment_size));
+  }
+
+  return 0;
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/fuzzer/qpack_encoder_stream_sender_fuzzer.cc b/quic/core/qpack/fuzzer/qpack_encoder_stream_sender_fuzzer.cc
new file mode 100644
index 0000000..501a69f
--- /dev/null
+++ b/quic/core/qpack/fuzzer/qpack_encoder_stream_sender_fuzzer.cc
@@ -0,0 +1,70 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_fuzzed_data_provider.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+namespace test {
+
+// This fuzzer exercises QpackEncoderStreamSender.
+// TODO(bnc): Encoded data could be fed into QpackEncoderStreamReceiver and
+// decoded instructions directly compared to input.  Figure out how to get gMock
+// enabled for cc_fuzz_target target types.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  NoopEncoderStreamSenderDelegate delegate;
+  QpackEncoderStreamSender sender(&delegate);
+
+  QuicFuzzedDataProvider provider(data, size);
+  // Limit string literal length to 2 kB for efficiency.
+  const uint16_t kMaxStringLength = 2048;
+
+  while (provider.remaining_bytes() != 0) {
+    switch (provider.ConsumeIntegral<uint8_t>() % 4) {
+      case 0: {
+        bool is_static = provider.ConsumeBool();
+        uint64_t name_index = provider.ConsumeIntegral<uint64_t>();
+        uint16_t value_length =
+            provider.ConsumeIntegralInRange<uint16_t>(0, kMaxStringLength);
+        QuicString value = provider.ConsumeRandomLengthString(value_length);
+
+        sender.SendInsertWithNameReference(is_static, name_index, value);
+        break;
+      }
+      case 1: {
+        uint16_t name_length =
+            provider.ConsumeIntegralInRange<uint16_t>(0, kMaxStringLength);
+        QuicString name = provider.ConsumeRandomLengthString(name_length);
+        uint16_t value_length =
+            provider.ConsumeIntegralInRange<uint16_t>(0, kMaxStringLength);
+        QuicString value = provider.ConsumeRandomLengthString(value_length);
+        sender.SendInsertWithoutNameReference(name, value);
+        break;
+      }
+      case 2: {
+        uint64_t index = provider.ConsumeIntegral<uint64_t>();
+        sender.SendDuplicate(index);
+        break;
+      }
+      case 3: {
+        uint64_t capacity = provider.ConsumeIntegral<uint64_t>();
+        sender.SendSetDynamicTableCapacity(capacity);
+        break;
+      }
+    }
+  }
+
+  return 0;
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc b/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
new file mode 100644
index 0000000..7f75a0f
--- /dev/null
+++ b/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
@@ -0,0 +1,152 @@
+// 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 <cstddef>
+#include <cstdint>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_fuzzed_data_provider.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+
+namespace quic {
+namespace test {
+
+// This fuzzer exercises QpackEncoder and QpackDecoder.  It should be able to
+// cover all possible code paths of QpackEncoder.  However, since the resulting
+// header block is always valid and is encoded in a particular way, this fuzzer
+// is not expected to cover all code paths of QpackDecoder.  On the other hand,
+// encoding then decoding is expected to result in the original header list, and
+// this fuzzer checks for that.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  QuicFuzzedDataProvider provider(data, size);
+
+  // Build test header list.
+  spdy::SpdyHeaderBlock header_list;
+  uint8_t header_count = provider.ConsumeIntegral<uint8_t>();
+  for (uint8_t header_index = 0; header_index < header_count; ++header_index) {
+    if (provider.remaining_bytes() == 0) {
+      // Do not add more headers if there is no more fuzzer data.
+      break;
+    }
+
+    QuicString name;
+    QuicString value;
+    switch (provider.ConsumeIntegral<uint8_t>()) {
+      case 0:
+        // Static table entry with no header value.
+        name = ":authority";
+        break;
+      case 1:
+        // Static table entry with no header value, using non-empty header
+        // value.
+        name = ":authority";
+        value = "www.example.org";
+        break;
+      case 2:
+        // Static table entry with header value, using that header value.
+        name = ":accept-encoding";
+        value = "gzip, deflate";
+        break;
+      case 3:
+        // Static table entry with header value, using empty header value.
+        name = ":accept-encoding";
+        break;
+      case 4:
+        // Static table entry with header value, using different, non-empty
+        // header value.
+        name = ":accept-encoding";
+        value = "brotli";
+        break;
+      case 5:
+        // Header name that has multiple entries in the static table,
+        // using header value from one of them.
+        name = ":method";
+        value = "GET";
+        break;
+      case 6:
+        // Header name that has multiple entries in the static table,
+        // using empty header value.
+        name = ":method";
+        break;
+      case 7:
+        // Header name that has multiple entries in the static table,
+        // using different, non-empty header value.
+        name = ":method";
+        value = "CONNECT";
+        break;
+      case 8:
+        // Header name not in the static table, empty header value.
+        name = "foo";
+        value = "";
+        break;
+      case 9:
+        // Header name not in the static table, non-empty fixed header value.
+        name = "foo";
+        value = "bar";
+        break;
+      case 10:
+        // Header name not in the static table, fuzzed header value.
+        name = "foo";
+        value = provider.ConsumeRandomLengthString(128);
+        break;
+      case 11:
+        // Another header name not in the static table, empty header value.
+        name = "bar";
+        value = "";
+        break;
+      case 12:
+        // Another header name not in the static table, non-empty fixed header
+        // value.
+        name = "bar";
+        value = "baz";
+        break;
+      case 13:
+        // Another header name not in the static table, fuzzed header value.
+        name = "bar";
+        value = provider.ConsumeRandomLengthString(128);
+        break;
+      default:
+        // Fuzzed header name and header value.
+        name = provider.ConsumeRandomLengthString(128);
+        value = provider.ConsumeRandomLengthString(128);
+    }
+
+    header_list.AppendValueOrAddHeader(name, value);
+  }
+
+  // Process up to 64 kB fragments at a time.  Too small upper bound might not
+  // provide enough coverage, too large would make fuzzing less efficient.
+  auto fragment_size_generator =
+      std::bind(&QuicFuzzedDataProvider::ConsumeIntegralInRange<uint16_t>,
+                &provider, 1, std::numeric_limits<uint16_t>::max());
+
+  // Encode header list.
+  NoopDecoderStreamErrorDelegate decoder_stream_error_delegate;
+  NoopEncoderStreamSenderDelegate encoder_stream_sender_delegate;
+  QuicString encoded_header_block = QpackEncode(
+      &decoder_stream_error_delegate, &encoder_stream_sender_delegate,
+      fragment_size_generator, &header_list);
+
+  // Decode header block.
+  TestHeadersHandler handler;
+  NoopEncoderStreamErrorDelegate encoder_stream_error_delegate;
+  NoopDecoderStreamSenderDelegate decoder_stream_sender_delegate;
+  QpackDecode(&encoder_stream_error_delegate, &decoder_stream_sender_delegate,
+              &handler, fragment_size_generator, encoded_header_block);
+
+  // Since header block has been produced by encoding a header list, it must be
+  // valid.
+  CHECK(handler.decoding_completed());
+  CHECK(!handler.decoding_error_detected());
+
+  // Compare resulting header list to original.
+  CHECK(header_list == handler.ReleaseHeaderList());
+
+  return 0;
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/offline/qpack_offline_decoder.cc b/quic/core/qpack/offline/qpack_offline_decoder.cc
new file mode 100644
index 0000000..2b02edd
--- /dev/null
+++ b/quic/core/qpack/offline/qpack_offline_decoder.cc
@@ -0,0 +1,273 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/offline/qpack_offline_decoder.h"
+
+#include <cstdint>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_endian.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_file_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+namespace quic {
+
+QpackOfflineDecoder::QpackOfflineDecoder()
+    : encoder_stream_error_detected_(false),
+      decoder_(this, &decoder_stream_sender_delegate_) {}
+
+bool QpackOfflineDecoder::DecodeAndVerifyOfflineData(
+    QuicStringPiece input_filename,
+    QuicStringPiece expected_headers_filename) {
+  if (!ParseInputFilename(input_filename)) {
+    QUIC_LOG(ERROR) << "Error parsing input filename " << input_filename;
+    return false;
+  }
+
+  if (!DecodeHeaderBlocksFromFile(input_filename)) {
+    QUIC_LOG(ERROR) << "Error decoding header blocks in " << input_filename;
+    return false;
+  }
+
+  if (!VerifyDecodedHeaderLists(expected_headers_filename)) {
+    QUIC_LOG(ERROR) << "Header lists decoded from " << input_filename
+                    << " to not match expected headers parsed from "
+                    << expected_headers_filename;
+    return false;
+  }
+
+  return true;
+}
+
+void QpackOfflineDecoder::OnEncoderStreamError(QuicStringPiece error_message) {
+  QUIC_LOG(ERROR) << "Encoder stream error: " << error_message;
+  encoder_stream_error_detected_ = true;
+}
+
+bool QpackOfflineDecoder::ParseInputFilename(QuicStringPiece input_filename) {
+  auto pieces = QuicTextUtils::Split(input_filename, '.');
+
+  if (pieces.size() < 3) {
+    QUIC_LOG(ERROR) << "Not enough fields in input filename " << input_filename;
+    return false;
+  }
+
+  auto piece_it = pieces.rbegin();
+
+  // Acknowledgement mode: 1 for immediate, 0 for none.
+  bool immediate_acknowledgement = false;
+  if (*piece_it == "0") {
+    immediate_acknowledgement = false;
+  } else if (*piece_it == "1") {
+    immediate_acknowledgement = true;
+  } else {
+    QUIC_LOG(ERROR)
+        << "Header acknowledgement field must be 0 or 1 in input filename "
+        << input_filename;
+    return false;
+  }
+
+  ++piece_it;
+
+  // Maximum allowed number of blocked streams.
+  uint64_t max_blocked_streams = 0;
+  if (!QuicTextUtils::StringToUint64(*piece_it, &max_blocked_streams)) {
+    QUIC_LOG(ERROR) << "Error parsing part of input filename \"" << *piece_it
+                    << "\" as an integer.";
+    return false;
+  }
+
+  if (max_blocked_streams > 0) {
+    // TODO(bnc): Implement blocked streams.
+    QUIC_LOG(ERROR) << "Blocked streams not implemented.";
+    return false;
+  }
+
+  ++piece_it;
+
+  // Dynamic Table Size in bytes
+  uint64_t dynamic_table_size = 0;
+  if (!QuicTextUtils::StringToUint64(*piece_it, &dynamic_table_size)) {
+    QUIC_LOG(ERROR) << "Error parsing part of input filename \"" << *piece_it
+                    << "\" as an integer.";
+    return false;
+  }
+
+  decoder_.SetMaximumDynamicTableCapacity(dynamic_table_size);
+
+  return true;
+}
+
+bool QpackOfflineDecoder::DecodeHeaderBlocksFromFile(
+    QuicStringPiece input_filename) {
+  // Store data in |input_data_storage|; use a QuicStringPiece to efficiently
+  // keep track of remaining portion yet to be decoded.
+  QuicString input_data_storage;
+  ReadFileContents(input_filename, &input_data_storage);
+  QuicStringPiece input_data(input_data_storage);
+
+  while (!input_data.empty()) {
+    if (input_data.size() < sizeof(uint64_t) + sizeof(uint32_t)) {
+      QUIC_LOG(ERROR) << "Unexpected end of input file.";
+      return false;
+    }
+
+    uint64_t stream_id = QuicEndian::NetToHost64(
+        *reinterpret_cast<const uint64_t*>(input_data.data()));
+    input_data = input_data.substr(sizeof(uint64_t));
+
+    uint32_t length = QuicEndian::NetToHost32(
+        *reinterpret_cast<const uint32_t*>(input_data.data()));
+    input_data = input_data.substr(sizeof(uint32_t));
+
+    if (input_data.size() < length) {
+      QUIC_LOG(ERROR) << "Unexpected end of input file.";
+      return false;
+    }
+
+    QuicStringPiece data = input_data.substr(0, length);
+    input_data = input_data.substr(length);
+
+    if (stream_id == 0) {
+      decoder_.DecodeEncoderStreamData(data);
+
+      if (encoder_stream_error_detected_) {
+        QUIC_LOG(ERROR) << "Error detected on encoder stream.";
+        return false;
+      }
+
+      continue;
+    }
+
+    test::TestHeadersHandler headers_handler;
+
+    auto progressive_decoder =
+        decoder_.DecodeHeaderBlock(stream_id, &headers_handler);
+    progressive_decoder->Decode(data);
+    progressive_decoder->EndHeaderBlock();
+
+    if (headers_handler.decoding_error_detected()) {
+      QUIC_LOG(ERROR) << "Decoding error on stream " << stream_id;
+      return false;
+    }
+
+    decoded_header_lists_.push_back(headers_handler.ReleaseHeaderList());
+  }
+
+  return true;
+}
+
+bool QpackOfflineDecoder::VerifyDecodedHeaderLists(
+    QuicStringPiece expected_headers_filename) {
+  // Store data in |expected_headers_data_storage|; use a QuicStringPiece to
+  // efficiently keep track of remaining portion yet to be decoded.
+  QuicString expected_headers_data_storage;
+  ReadFileContents(expected_headers_filename, &expected_headers_data_storage);
+  QuicStringPiece expected_headers_data(expected_headers_data_storage);
+
+  while (!decoded_header_lists_.empty()) {
+    spdy::SpdyHeaderBlock decoded_header_list =
+        std::move(decoded_header_lists_.front());
+    decoded_header_lists_.pop_front();
+
+    spdy::SpdyHeaderBlock expected_header_list;
+    if (!ReadNextExpectedHeaderList(&expected_headers_data,
+                                    &expected_header_list)) {
+      QUIC_LOG(ERROR)
+          << "Error parsing expected header list to match next decoded "
+             "header list.";
+      return false;
+    }
+
+    if (!CompareHeaderBlocks(std::move(decoded_header_list),
+                             std::move(expected_header_list))) {
+      QUIC_LOG(ERROR) << "Decoded header does not match expected header.";
+      return false;
+    }
+  }
+
+  if (!expected_headers_data.empty()) {
+    QUIC_LOG(ERROR)
+        << "Not enough encoded header lists to match expected ones.";
+    return false;
+  }
+
+  return true;
+}
+
+bool QpackOfflineDecoder::ReadNextExpectedHeaderList(
+    QuicStringPiece* expected_headers_data,
+    spdy::SpdyHeaderBlock* expected_header_list) {
+  while (true) {
+    QuicStringPiece::size_type endline = expected_headers_data->find('\n');
+
+    // Even last header list must be followed by an empty line.
+    if (endline == QuicStringPiece::npos) {
+      QUIC_LOG(ERROR) << "Unexpected end of expected header list file.";
+      return false;
+    }
+
+    if (endline == 0) {
+      // Empty line indicates end of header list.
+      *expected_headers_data = expected_headers_data->substr(1);
+      return true;
+    }
+
+    QuicStringPiece header_field = expected_headers_data->substr(0, endline);
+    auto pieces = QuicTextUtils::Split(header_field, '\t');
+
+    if (pieces.size() != 2) {
+      QUIC_LOG(ERROR) << "Header key and value must be separated by TAB.";
+      return false;
+    }
+
+    expected_header_list->AppendValueOrAddHeader(pieces[0], pieces[1]);
+
+    *expected_headers_data = expected_headers_data->substr(endline + 1);
+  }
+}
+
+bool QpackOfflineDecoder::CompareHeaderBlocks(
+    spdy::SpdyHeaderBlock decoded_header_list,
+    spdy::SpdyHeaderBlock expected_header_list) {
+  if (decoded_header_list == expected_header_list) {
+    return true;
+  }
+
+  // The h2o decoder reshuffles the "content-length" header and pseudo-headers,
+  // see
+  // https://github.com/qpackers/qifs/blob/master/encoded/qpack-03/h2o/README.md.
+  // Remove such headers one by one if they match.
+  const char* kContentLength = "content-length";
+  const char* kPseudoHeaderPrefix = ":";
+  for (spdy::SpdyHeaderBlock::iterator decoded_it = decoded_header_list.begin();
+       decoded_it != decoded_header_list.end();) {
+    const QuicStringPiece key = decoded_it->first;
+    if (key != kContentLength &&
+        !QuicTextUtils::StartsWith(key, kPseudoHeaderPrefix)) {
+      ++decoded_it;
+      continue;
+    }
+    spdy::SpdyHeaderBlock::iterator expected_it =
+        expected_header_list.find(key);
+    if (expected_it == expected_header_list.end() ||
+        decoded_it->second != expected_it->second) {
+      ++decoded_it;
+      continue;
+    }
+    // SpdyHeaderBlock does not support erasing by iterator, only by key.
+    ++decoded_it;
+    expected_header_list.erase(key);
+    // This will invalidate |key|.
+    decoded_header_list.erase(key);
+  }
+
+  return decoded_header_list == expected_header_list;
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/offline/qpack_offline_decoder.h b/quic/core/qpack/offline/qpack_offline_decoder.h
new file mode 100644
index 0000000..922fd64
--- /dev/null
+++ b/quic/core/qpack/offline/qpack_offline_decoder.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_OFFLINE_QPACK_OFFLINE_DECODER_H_
+#define QUICHE_QUIC_CORE_QPACK_OFFLINE_QPACK_OFFLINE_DECODER_H_
+
+#include <list>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+
+namespace quic {
+
+// A decoder to read encoded data from a file, decode it, and compare to
+// a list of expected header lists read from another file.  File format is
+// described at
+// https://github.com/quicwg/base-drafts/wiki/QPACK-Offline-Interop.
+class QpackOfflineDecoder : public QpackDecoder::EncoderStreamErrorDelegate {
+ public:
+  QpackOfflineDecoder();
+  ~QpackOfflineDecoder() override = default;
+
+  // Read encoded header blocks and encoder stream data from |input_filename|
+  // and decode them, read expected header lists from
+  // |expected_headers_filename|, and compare decoded header lists to expected
+  // ones.  Returns true if there is an equal number of them and the
+  // corresponding ones match, false otherwise.
+  bool DecodeAndVerifyOfflineData(QuicStringPiece input_filename,
+                                  QuicStringPiece expected_headers_filename);
+
+  // QpackDecoder::EncoderStreamErrorDelegate implementation:
+  void OnEncoderStreamError(QuicStringPiece error_message) override;
+
+ private:
+  // Parse decoder parameters from |input_filename| and set up |decoder_|
+  // accordingly.
+  bool ParseInputFilename(QuicStringPiece input_filename);
+
+  // Read encoded header blocks and encoder stream data from |input_filename|,
+  // pass them to |decoder_| for decoding, and add decoded header lists to
+  // |decoded_header_lists_|.
+  bool DecodeHeaderBlocksFromFile(QuicStringPiece input_filename);
+
+  // Read expected header lists from |expected_headers_filename| and verify
+  // decoded header lists in |decoded_header_lists_| against them.
+  bool VerifyDecodedHeaderLists(QuicStringPiece expected_headers_filename);
+
+  // Parse next header list from |*expected_headers_data| into
+  // |*expected_header_list|, removing consumed data from the beginning of
+  // |*expected_headers_data|.  Returns true on success, false if parsing fails.
+  bool ReadNextExpectedHeaderList(QuicStringPiece* expected_headers_data,
+                                  spdy::SpdyHeaderBlock* expected_header_list);
+
+  // Compare two header lists.  Allow for different orders of certain headers as
+  // described at
+  // https://github.com/qpackers/qifs/blob/master/encoded/qpack-03/h2o/README.md.
+  bool CompareHeaderBlocks(spdy::SpdyHeaderBlock decoded_header_list,
+                           spdy::SpdyHeaderBlock expected_header_list);
+
+  bool encoder_stream_error_detected_;
+  test::NoopDecoderStreamSenderDelegate decoder_stream_sender_delegate_;
+  QpackDecoder decoder_;
+  std::list<spdy::SpdyHeaderBlock> decoded_header_lists_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_OFFLINE_QPACK_OFFLINE_DECODER_H_
diff --git a/quic/core/qpack/offline/qpack_offline_decoder_bin.cc b/quic/core/qpack/offline/qpack_offline_decoder_bin.cc
new file mode 100644
index 0000000..a0319e6
--- /dev/null
+++ b/quic/core/qpack/offline/qpack_offline_decoder_bin.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/offline/qpack_offline_decoder.h"
+
+#include <cstddef>
+#include <iostream>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+int main(int argc, char* argv[]) {
+  const char* usage =
+      "Usage: qpack_offline_decoder input_filename expected_headers_filename "
+      "....";
+  std::vector<quic::QuicString> args =
+      quic::QuicParseCommandLineFlags(usage, argc, argv);
+
+  if (args.size() < 2 || args.size() % 2 != 0) {
+    quic::QuicPrintCommandLineFlagHelp(usage);
+    return 1;
+  }
+
+  size_t i;
+  for (i = 0; 2 * i < args.size(); ++i) {
+    const quic::QuicStringPiece input_filename(args[2 * i]);
+    const quic::QuicStringPiece expected_headers_filename(args[2 * i + 1]);
+
+    // Every file represents a different connection,
+    // therefore every file needs a fresh decoding context.
+    quic::QpackOfflineDecoder decoder;
+    if (!decoder.DecodeAndVerifyOfflineData(input_filename,
+                                            expected_headers_filename)) {
+      return 1;
+    }
+  }
+
+  std::cout << "Successfully verified " << i << " pairs of input files."
+            << std::endl;
+
+  return 0;
+}
diff --git a/quic/core/qpack/qpack_constants.cc b/quic/core/qpack/qpack_constants.cc
new file mode 100644
index 0000000..b9144e0
--- /dev/null
+++ b/quic/core/qpack/qpack_constants.cc
@@ -0,0 +1,200 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_constants.h"
+
+#include <limits>
+
+#include "base/logging.h"
+
+namespace quic {
+
+namespace {
+
+// Validate that
+//  * in each instruction, the bits of |value| that are zero in |mask| are zero;
+//  * every byte matches exactly one opcode.
+void ValidateLangague(const QpackLanguage* language) {
+#ifndef NDEBUG
+  for (const auto* instruction : *language) {
+    DCHECK_EQ(0, instruction->opcode.value & ~instruction->opcode.mask);
+  }
+
+  for (uint8_t byte = 0; byte < std::numeric_limits<uint8_t>::max(); ++byte) {
+    size_t match_count = 0;
+    for (const auto* instruction : *language) {
+      if ((byte & instruction->opcode.mask) == instruction->opcode.value) {
+        ++match_count;
+      }
+    }
+    DCHECK_EQ(1u, match_count) << static_cast<int>(byte);
+  }
+#endif
+}
+
+}  // namespace
+
+bool operator==(const QpackInstructionOpcode& a,
+                const QpackInstructionOpcode& b) {
+  return std::tie(a.value, a.mask) == std::tie(b.value, b.mask);
+}
+
+const QpackInstruction* InsertWithNameReferenceInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b10000000, 0b10000000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode,
+                           {{QpackInstructionFieldType::kSbit, 0b01000000},
+                            {QpackInstructionFieldType::kVarint, 6},
+                            {QpackInstructionFieldType::kValue, 7}}};
+  return instruction;
+}
+
+const QpackInstruction* InsertWithoutNameReferenceInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b01000000, 0b11000000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode,
+                           {{QpackInstructionFieldType::kName, 5},
+                            {QpackInstructionFieldType::kValue, 7}}};
+  return instruction;
+}
+
+const QpackInstruction* DuplicateInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b00000000, 0b11100000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 5}}};
+  return instruction;
+}
+
+const QpackInstruction* SetDynamicTableCapacityInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b00100000, 0b11100000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 5}}};
+  return instruction;
+}
+
+const QpackLanguage* QpackEncoderStreamLanguage() {
+  static const QpackLanguage* const language = new QpackLanguage{
+      InsertWithNameReferenceInstruction(),
+      InsertWithoutNameReferenceInstruction(), DuplicateInstruction(),
+      SetDynamicTableCapacityInstruction()};
+  ValidateLangague(language);
+  return language;
+}
+
+const QpackInstruction* InsertCountIncrementInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b00000000, 0b11000000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 6}}};
+  return instruction;
+}
+
+const QpackInstruction* HeaderAcknowledgementInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b10000000, 0b10000000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 7}}};
+  return instruction;
+}
+
+const QpackInstruction* StreamCancellationInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b01000000, 0b11000000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 6}}};
+  return instruction;
+}
+
+const QpackLanguage* QpackDecoderStreamLanguage() {
+  static const QpackLanguage* const language = new QpackLanguage{
+      InsertCountIncrementInstruction(), HeaderAcknowledgementInstruction(),
+      StreamCancellationInstruction()};
+  ValidateLangague(language);
+  return language;
+}
+
+const QpackInstruction* QpackPrefixInstruction() {
+  // This opcode matches every input.
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b00000000, 0b00000000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode,
+                           {{QpackInstructionFieldType::kVarint, 8},
+                            {QpackInstructionFieldType::kSbit, 0b10000000},
+                            {QpackInstructionFieldType::kVarint2, 7}}};
+  return instruction;
+}
+
+const QpackLanguage* QpackPrefixLanguage() {
+  static const QpackLanguage* const language =
+      new QpackLanguage{QpackPrefixInstruction()};
+  ValidateLangague(language);
+  return language;
+}
+
+const QpackInstruction* QpackIndexedHeaderFieldInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b10000000, 0b10000000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode,
+                           {{QpackInstructionFieldType::kSbit, 0b01000000},
+                            {QpackInstructionFieldType::kVarint, 6}}};
+  return instruction;
+}
+
+const QpackInstruction* QpackIndexedHeaderFieldPostBaseInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b00010000, 0b11110000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 4}}};
+  return instruction;
+}
+
+const QpackInstruction* QpackLiteralHeaderFieldNameReferenceInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b01000000, 0b11000000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode,
+                           {{QpackInstructionFieldType::kSbit, 0b00010000},
+                            {QpackInstructionFieldType::kVarint, 4},
+                            {QpackInstructionFieldType::kValue, 7}}};
+  return instruction;
+}
+
+const QpackInstruction* QpackLiteralHeaderFieldPostBaseInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b00000000, 0b11110000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode,
+                           {{QpackInstructionFieldType::kVarint, 3},
+                            {QpackInstructionFieldType::kValue, 7}}};
+  return instruction;
+}
+
+const QpackInstruction* QpackLiteralHeaderFieldInstruction() {
+  static const QpackInstructionOpcode* const opcode =
+      new QpackInstructionOpcode{0b00100000, 0b11100000};
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{*opcode,
+                           {{QpackInstructionFieldType::kName, 3},
+                            {QpackInstructionFieldType::kValue, 7}}};
+  return instruction;
+}
+
+const QpackLanguage* QpackRequestStreamLanguage() {
+  static const QpackLanguage* const language =
+      new QpackLanguage{QpackIndexedHeaderFieldInstruction(),
+                        QpackIndexedHeaderFieldPostBaseInstruction(),
+                        QpackLiteralHeaderFieldNameReferenceInstruction(),
+                        QpackLiteralHeaderFieldPostBaseInstruction(),
+                        QpackLiteralHeaderFieldInstruction()};
+  ValidateLangague(language);
+  return language;
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_constants.h b/quic/core/qpack/qpack_constants.h
new file mode 100644
index 0000000..7b7eb47
--- /dev/null
+++ b/quic/core/qpack/qpack_constants.h
@@ -0,0 +1,147 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_CONSTANTS_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_CONSTANTS_H_
+
+#include <cstdint>
+#include <tuple>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+// Each instruction is identified with an opcode in the first byte.
+// |mask| determines which bits are part of the opcode.
+// |value| is the value of these bits.  (Other bits in value must be zero.)
+struct QUIC_EXPORT_PRIVATE QpackInstructionOpcode {
+  uint8_t value;
+  uint8_t mask;
+};
+
+bool operator==(const QpackInstructionOpcode& a,
+                const QpackInstructionOpcode& b);
+
+// Possible types of an instruction field.  Decoding a static bit does not
+// consume the current byte.  Decoding an integer or a length-prefixed string
+// literal consumes all bytes containing the field value.
+enum class QpackInstructionFieldType {
+  // A single bit indicating whether the index refers to the static table, or
+  // indicating the sign of Delta Base.  Called "S" bit because both "static"
+  // and "sign" start with the letter "S".
+  kSbit,
+  // An integer encoded with variable length encoding.  This could be an index,
+  // stream ID, maximum size, or Encoded Required Insert Count.
+  kVarint,
+  // A second integer encoded with variable length encoding.  This could be
+  // Delta Base.
+  kVarint2,
+  // A header name or header value encoded as:
+  //   a bit indicating whether it is Huffman encoded;
+  //   the encoded length of the string;
+  //   the header name or value optionally Huffman encoded.
+  kName,
+  kValue
+};
+
+// Each instruction field has a type and a parameter.
+// The meaning of the parameter depends on the field type.
+struct QUIC_EXPORT_PRIVATE QpackInstructionField {
+  QpackInstructionFieldType type;
+  // For a kSbit field, |param| is a mask with exactly one bit set.
+  // For kVarint fields, |param| is the prefix length of the integer encoding.
+  // For kName and kValue fields, |param| is the prefix length of the length of
+  // the string, and the bit immediately preceding the prefix is interpreted as
+  // the Huffman bit.
+  uint8_t param;
+};
+
+using QpackInstructionFields = std::vector<QpackInstructionField>;
+
+// A QPACK instruction consists of an opcode identifying the instruction,
+// followed by a non-empty list of fields.  The last field must be integer or
+// string literal type to guarantee that all bytes of the instruction are
+// consumed.
+struct QUIC_EXPORT_PRIVATE QpackInstruction {
+  QpackInstruction(const QpackInstruction&) = delete;
+  const QpackInstruction& operator=(const QpackInstruction&) = delete;
+
+  QpackInstructionOpcode opcode;
+  QpackInstructionFields fields;
+};
+
+// A language is a collection of instructions.  The order does not matter.
+// Every possible input must match exactly one instruction.
+using QpackLanguage = std::vector<const QpackInstruction*>;
+
+// TODO(bnc): Move this into HpackVarintEncoder.
+// The integer encoder can encode up to 2^64-1, which can take up to 10 bytes
+// (each carrying 7 bits) after the prefix.
+const uint8_t kMaxExtensionBytesForVarintEncoding = 10;
+
+// Wire format defined in
+// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#rfc.section.5
+
+// 5.2 Encoder stream instructions
+
+// 5.2.1 Insert With Name Reference
+const QpackInstruction* InsertWithNameReferenceInstruction();
+
+// 5.2.2 Insert Without Name Reference
+const QpackInstruction* InsertWithoutNameReferenceInstruction();
+
+// 5.2.3 Duplicate
+const QpackInstruction* DuplicateInstruction();
+
+// 5.2.4 Dynamic Table Size Update
+const QpackInstruction* SetDynamicTableCapacityInstruction();
+
+// Encoder stream language.
+const QpackLanguage* QpackEncoderStreamLanguage();
+
+// 5.3 Decoder stream instructions
+
+// 5.3.1 Insert Count Increment
+const QpackInstruction* InsertCountIncrementInstruction();
+
+// 5.3.2 Header Acknowledgement
+const QpackInstruction* HeaderAcknowledgementInstruction();
+
+// 5.3.3 Stream Cancellation
+const QpackInstruction* StreamCancellationInstruction();
+
+// Decoder stream language.
+const QpackLanguage* QpackDecoderStreamLanguage();
+
+// 5.4.1. Header data prefix instructions
+
+const QpackInstruction* QpackPrefixInstruction();
+
+const QpackLanguage* QpackPrefixLanguage();
+
+// 5.4.2. Request and push stream instructions
+
+// 5.4.2.1. Indexed Header Field
+const QpackInstruction* QpackIndexedHeaderFieldInstruction();
+
+// 5.4.2.2. Indexed Header Field With Post-Base Index
+const QpackInstruction* QpackIndexedHeaderFieldPostBaseInstruction();
+
+// 5.4.2.3. Literal Header Field With Name Reference
+const QpackInstruction* QpackLiteralHeaderFieldNameReferenceInstruction();
+
+// 5.4.2.4. Literal Header Field With Post-Base Name Reference
+const QpackInstruction* QpackLiteralHeaderFieldPostBaseInstruction();
+
+// 5.4.2.5. Literal Header Field Without Name Reference
+const QpackInstruction* QpackLiteralHeaderFieldInstruction();
+
+// Request and push stream language.
+const QpackLanguage* QpackRequestStreamLanguage();
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_CONSTANTS_H_
diff --git a/quic/core/qpack/qpack_decoded_headers_accumulator.cc b/quic/core/qpack/qpack_decoded_headers_accumulator.cc
new file mode 100644
index 0000000..ac60fce
--- /dev/null
+++ b/quic/core/qpack/qpack_decoded_headers_accumulator.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2019 The Chromium 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/quic/core/qpack/qpack_decoded_headers_accumulator.h"
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h"
+
+namespace quic {
+
+QpackDecodedHeadersAccumulator::QpackDecodedHeadersAccumulator(
+    QuicStreamId id,
+    QpackDecoder* qpack_decoder)
+    : decoder_(qpack_decoder->DecodeHeaderBlock(id, this)),
+      uncompressed_header_bytes_(0),
+      compressed_header_bytes_(0),
+      error_detected_(false) {
+  quic_header_list_.OnHeaderBlockStart();
+}
+
+void QpackDecodedHeadersAccumulator::OnHeaderDecoded(QuicStringPiece name,
+                                                     QuicStringPiece value) {
+  DCHECK(!error_detected_);
+
+  uncompressed_header_bytes_ += name.size() + value.size();
+  quic_header_list_.OnHeader(name, value);
+}
+
+void QpackDecodedHeadersAccumulator::OnDecodingCompleted() {}
+
+void QpackDecodedHeadersAccumulator::OnDecodingErrorDetected(
+    QuicStringPiece error_message) {
+  DCHECK(!error_detected_);
+
+  error_detected_ = true;
+  // Copy error message to ensure it remains valid for the lifetime of |this|.
+  error_message_.assign(error_message.data(), error_message.size());
+}
+
+bool QpackDecodedHeadersAccumulator::Decode(QuicStringPiece data) {
+  DCHECK(!error_detected_);
+
+  compressed_header_bytes_ += data.size();
+  decoder_->Decode(data);
+
+  return !error_detected_;
+}
+
+bool QpackDecodedHeadersAccumulator::EndHeaderBlock() {
+  DCHECK(!error_detected_);
+
+  decoder_->EndHeaderBlock();
+
+  quic_header_list_.OnHeaderBlockEnd(uncompressed_header_bytes_,
+                                     compressed_header_bytes_);
+
+  return !error_detected_;
+}
+
+const QuicHeaderList& QpackDecodedHeadersAccumulator::quic_header_list() const {
+  DCHECK(!error_detected_);
+  return quic_header_list_;
+}
+
+QuicStringPiece QpackDecodedHeadersAccumulator::error_message() const {
+  DCHECK(error_detected_);
+  return error_message_;
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_decoded_headers_accumulator.h b/quic/core/qpack/qpack_decoded_headers_accumulator.h
new file mode 100644
index 0000000..47d4f63
--- /dev/null
+++ b/quic/core/qpack/qpack_decoded_headers_accumulator.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2019 The Chromium 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_QUIC_CORE_QPACK_QPACK_DECODED_HEADERS_ACCUMULATOR_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_DECODED_HEADERS_ACCUMULATOR_H_
+
+#include <cstddef>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_header_list.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+class QpackDecoder;
+
+// A class that creates and owns a QpackProgressiveDecoder instance, accumulates
+// decoded headers in a QuicHeaderList, and keeps track of uncompressed and
+// compressed size so that it can be passed to QuicHeaderList::EndHeaderBlock().
+class QUIC_EXPORT_PRIVATE QpackDecodedHeadersAccumulator
+    : public QpackProgressiveDecoder::HeadersHandlerInterface {
+ public:
+  QpackDecodedHeadersAccumulator(QuicStreamId id, QpackDecoder* qpack_decoder);
+  virtual ~QpackDecodedHeadersAccumulator() = default;
+
+  // QpackProgressiveDecoder::HeadersHandlerInterface implementation.
+  // These methods should only be called by |decoder_|.
+  void OnHeaderDecoded(QuicStringPiece name, QuicStringPiece value) override;
+  void OnDecodingCompleted() override;
+  void OnDecodingErrorDetected(QuicStringPiece error_message) override;
+
+  // Decode payload data.  Returns true on success, false on error.
+  // Must not be called if an error has been detected.
+  // Must not be called after EndHeaderBlock().
+  bool Decode(QuicStringPiece data);
+
+  // Signal end of HEADERS frame.  Returns true on success, false on error.
+  // Must not be called if an error has been detected.
+  // Must not be called more that once.
+  bool EndHeaderBlock();
+
+  // Returns accumulated header list.
+  const QuicHeaderList& quic_header_list() const;
+
+  // Returns error message.
+  // Must not be called unless an error has been detected.
+  QuicStringPiece error_message() const;
+
+ private:
+  std::unique_ptr<QpackProgressiveDecoder> decoder_;
+  QuicHeaderList quic_header_list_;
+  size_t uncompressed_header_bytes_;
+  size_t compressed_header_bytes_;
+  bool error_detected_;
+  QuicString error_message_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_DECODED_HEADERS_ACCUMULATOR_H_
diff --git a/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc b/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc
new file mode 100644
index 0000000..0c71b19
--- /dev/null
+++ b/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2019 The Chromium 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/quic/core/qpack/qpack_decoded_headers_accumulator.h"
+
+#include <cstring>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+using ::testing::Eq;
+using ::testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+QuicStreamId kTestStreamId = 1;
+
+// Header Acknowledgement decoder stream instruction with stream_id = 1.
+const char* const kHeaderAcknowledgement = "\x81";
+
+}  // anonymous namespace
+
+class QpackDecodedHeadersAccumulatorTest : public QuicTest {
+ protected:
+  QpackDecodedHeadersAccumulatorTest()
+      : qpack_decoder_(&encoder_stream_error_delegate_,
+                       &decoder_stream_sender_delegate_),
+        accumulator_(kTestStreamId, &qpack_decoder_) {}
+
+  NoopEncoderStreamErrorDelegate encoder_stream_error_delegate_;
+  StrictMock<MockDecoderStreamSenderDelegate> decoder_stream_sender_delegate_;
+  QpackDecoder qpack_decoder_;
+  QpackDecodedHeadersAccumulator accumulator_;
+};
+
+// HEADERS frame payload must have a complete Header Block Prefix.
+TEST_F(QpackDecodedHeadersAccumulatorTest, EmptyPayload) {
+  EXPECT_FALSE(accumulator_.EndHeaderBlock());
+  EXPECT_EQ("Incomplete header data prefix.", accumulator_.error_message());
+}
+
+// HEADERS frame payload must have a complete Header Block Prefix.
+TEST_F(QpackDecodedHeadersAccumulatorTest, TruncatedHeaderBlockPrefix) {
+  EXPECT_TRUE(accumulator_.Decode(QuicTextUtils::HexDecode("00")));
+  EXPECT_FALSE(accumulator_.EndHeaderBlock());
+  EXPECT_EQ("Incomplete header data prefix.", accumulator_.error_message());
+}
+
+TEST_F(QpackDecodedHeadersAccumulatorTest, EmptyHeaderList) {
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  EXPECT_TRUE(accumulator_.Decode(QuicTextUtils::HexDecode("0000")));
+  EXPECT_TRUE(accumulator_.EndHeaderBlock());
+
+  EXPECT_TRUE(accumulator_.quic_header_list().empty());
+}
+
+// This payload is the prefix of a valid payload, but EndHeaderBlock() is called
+// before it can be completely decoded.
+TEST_F(QpackDecodedHeadersAccumulatorTest, TruncatedPayload) {
+  EXPECT_TRUE(accumulator_.Decode(QuicTextUtils::HexDecode("00002366")));
+  EXPECT_FALSE(accumulator_.EndHeaderBlock());
+  EXPECT_EQ("Incomplete header block.", accumulator_.error_message());
+}
+
+// This payload is invalid because it refers to a non-existing static entry.
+TEST_F(QpackDecodedHeadersAccumulatorTest, InvalidPayload) {
+  EXPECT_FALSE(accumulator_.Decode(QuicTextUtils::HexDecode("0000ff23ff24")));
+  EXPECT_EQ("Static table entry not found.", accumulator_.error_message());
+}
+
+TEST_F(QpackDecodedHeadersAccumulatorTest, Success) {
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  QuicString encoded_data(QuicTextUtils::HexDecode("000023666f6f03626172"));
+  EXPECT_TRUE(accumulator_.Decode(encoded_data));
+  EXPECT_TRUE(accumulator_.EndHeaderBlock());
+
+  auto header_list = accumulator_.quic_header_list();
+  auto it = header_list.begin();
+  EXPECT_TRUE(it != header_list.end());
+  EXPECT_EQ("foo", it->first);
+  EXPECT_EQ("bar", it->second);
+  ++it;
+  EXPECT_TRUE(it == header_list.end());
+
+  EXPECT_EQ(strlen("foo") + strlen("bar"),
+            header_list.uncompressed_header_bytes());
+  EXPECT_EQ(encoded_data.size(), header_list.compressed_header_bytes());
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_decoder.cc b/quic/core/qpack/qpack_decoder.cc
new file mode 100644
index 0000000..fd0867a
--- /dev/null
+++ b/quic/core/qpack/qpack_decoder.cc
@@ -0,0 +1,141 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h"
+
+#include <limits>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+
+namespace quic {
+
+QpackDecoder::QpackDecoder(
+    EncoderStreamErrorDelegate* encoder_stream_error_delegate,
+    QpackDecoderStreamSender::Delegate* decoder_stream_sender_delegate)
+    : encoder_stream_error_delegate_(encoder_stream_error_delegate),
+      encoder_stream_receiver_(this),
+      decoder_stream_sender_(decoder_stream_sender_delegate) {
+  DCHECK(encoder_stream_error_delegate_);
+  DCHECK(decoder_stream_sender_delegate);
+}
+
+QpackDecoder::~QpackDecoder() {}
+
+void QpackDecoder::SetMaximumDynamicTableCapacity(
+    uint64_t maximum_dynamic_table_capacity) {
+  header_table_.SetMaximumDynamicTableCapacity(maximum_dynamic_table_capacity);
+}
+
+void QpackDecoder::OnStreamReset(QuicStreamId stream_id) {
+  decoder_stream_sender_.SendStreamCancellation(stream_id);
+}
+
+void QpackDecoder::DecodeEncoderStreamData(QuicStringPiece data) {
+  encoder_stream_receiver_.Decode(data);
+}
+
+void QpackDecoder::OnInsertWithNameReference(bool is_static,
+                                             uint64_t name_index,
+                                             QuicStringPiece value) {
+  if (is_static) {
+    auto entry = header_table_.LookupEntry(/* is_static = */ true, name_index);
+    if (!entry) {
+      encoder_stream_error_delegate_->OnEncoderStreamError(
+          "Invalid static table entry.");
+      return;
+    }
+
+    entry = header_table_.InsertEntry(entry->name(), value);
+    if (!entry) {
+      encoder_stream_error_delegate_->OnEncoderStreamError(
+          "Error inserting entry with name reference.");
+    }
+    return;
+  }
+
+  uint64_t absolute_index;
+  if (!EncoderStreamRelativeIndexToAbsoluteIndex(name_index, &absolute_index)) {
+    encoder_stream_error_delegate_->OnEncoderStreamError(
+        "Invalid relative index.");
+    return;
+  }
+
+  const QpackEntry* entry =
+      header_table_.LookupEntry(/* is_static = */ false, absolute_index);
+  if (!entry) {
+    encoder_stream_error_delegate_->OnEncoderStreamError(
+        "Dynamic table entry not found.");
+    return;
+  }
+  entry = header_table_.InsertEntry(entry->name(), value);
+  if (!entry) {
+    encoder_stream_error_delegate_->OnEncoderStreamError(
+        "Error inserting entry with name reference.");
+  }
+}
+
+void QpackDecoder::OnInsertWithoutNameReference(QuicStringPiece name,
+                                                QuicStringPiece value) {
+  const QpackEntry* entry = header_table_.InsertEntry(name, value);
+  if (!entry) {
+    encoder_stream_error_delegate_->OnEncoderStreamError(
+        "Error inserting literal entry.");
+  }
+}
+
+void QpackDecoder::OnDuplicate(uint64_t index) {
+  uint64_t absolute_index;
+  if (!EncoderStreamRelativeIndexToAbsoluteIndex(index, &absolute_index)) {
+    encoder_stream_error_delegate_->OnEncoderStreamError(
+        "Invalid relative index.");
+    return;
+  }
+
+  const QpackEntry* entry =
+      header_table_.LookupEntry(/* is_static = */ false, absolute_index);
+  if (!entry) {
+    encoder_stream_error_delegate_->OnEncoderStreamError(
+        "Dynamic table entry not found.");
+    return;
+  }
+  entry = header_table_.InsertEntry(entry->name(), entry->value());
+  if (!entry) {
+    encoder_stream_error_delegate_->OnEncoderStreamError(
+        "Error inserting duplicate entry.");
+  }
+}
+
+void QpackDecoder::OnSetDynamicTableCapacity(uint64_t capacity) {
+  if (!header_table_.SetDynamicTableCapacity(capacity)) {
+    encoder_stream_error_delegate_->OnEncoderStreamError(
+        "Error updating dynamic table capacity.");
+  }
+}
+
+void QpackDecoder::OnErrorDetected(QuicStringPiece error_message) {
+  encoder_stream_error_delegate_->OnEncoderStreamError(error_message);
+}
+
+bool QpackDecoder::EncoderStreamRelativeIndexToAbsoluteIndex(
+    uint64_t relative_index,
+    uint64_t* absolute_index) const {
+  if (relative_index == std::numeric_limits<uint64_t>::max() ||
+      relative_index + 1 > std::numeric_limits<uint64_t>::max() -
+                               header_table_.inserted_entry_count()) {
+    return false;
+  }
+
+  *absolute_index = header_table_.inserted_entry_count() - relative_index - 1;
+  return true;
+}
+
+std::unique_ptr<QpackProgressiveDecoder> QpackDecoder::DecodeHeaderBlock(
+    QuicStreamId stream_id,
+    QpackProgressiveDecoder::HeadersHandlerInterface* handler) {
+  return QuicMakeUnique<QpackProgressiveDecoder>(
+      stream_id, &header_table_, &decoder_stream_sender_, handler);
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_decoder.h b/quic/core/qpack/qpack_decoder.h
new file mode 100644
index 0000000..c3b28a3
--- /dev/null
+++ b/quic/core/qpack/qpack_decoder.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_receiver.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+// QPACK decoder class.  Exactly one instance should exist per QUIC connection.
+// This class vends a new QpackProgressiveDecoder instance for each new header
+// list to be encoded.
+class QUIC_EXPORT_PRIVATE QpackDecoder
+    : public QpackEncoderStreamReceiver::Delegate {
+ public:
+  // Interface for receiving notification that an error has occurred on the
+  // encoder stream.  This MUST be treated as a connection error of type
+  // HTTP_QPACK_ENCODER_STREAM_ERROR.
+  class QUIC_EXPORT_PRIVATE EncoderStreamErrorDelegate {
+   public:
+    virtual ~EncoderStreamErrorDelegate() {}
+
+    virtual void OnEncoderStreamError(QuicStringPiece error_message) = 0;
+  };
+
+  QpackDecoder(
+      EncoderStreamErrorDelegate* encoder_stream_error_delegate,
+      QpackDecoderStreamSender::Delegate* decoder_stream_sender_delegate);
+  ~QpackDecoder() override;
+
+  // Set maximum capacity of dynamic table.
+  // This method must only be called at most once.
+  void SetMaximumDynamicTableCapacity(uint64_t maximum_dynamic_table_capacity);
+
+  // Signal to the peer's encoder that a stream is reset.  This lets the peer's
+  // encoder know that no more header blocks will be processed on this stream,
+  // therefore references to dynamic table entries shall not prevent their
+  // eviction.
+  // This method should be called regardless of whether a header block is being
+  // decoded on that stream, because a header block might be in flight from the
+  // peer.
+  // This method should be called every time a request or push stream is reset
+  // for any reason: for example, client cancels request, or a decoding error
+  // occurs and HeadersHandlerInterface::OnDecodingErrorDetected() is called.
+  // This method should also be called if the stream is reset by the peer,
+  // because the peer's encoder can only evict entries referenced by header
+  // blocks once it receives acknowledgement from this endpoint that the stream
+  // is reset.
+  // However, this method should not be called if the stream is closed normally
+  // using the FIN bit.
+  void OnStreamReset(QuicStreamId stream_id);
+
+  // Factory method to create a QpackProgressiveDecoder for decoding a header
+  // block.  |handler| must remain valid until the returned
+  // QpackProgressiveDecoder instance is destroyed or the decoder calls
+  // |handler->OnHeaderBlockEnd()|.
+  std::unique_ptr<QpackProgressiveDecoder> DecodeHeaderBlock(
+      QuicStreamId stream_id,
+      QpackProgressiveDecoder::HeadersHandlerInterface* handler);
+
+  // Decode data received on the encoder stream.
+  void DecodeEncoderStreamData(QuicStringPiece data);
+
+  // QpackEncoderStreamReceiver::Delegate implementation
+  void OnInsertWithNameReference(bool is_static,
+                                 uint64_t name_index,
+                                 QuicStringPiece value) override;
+  void OnInsertWithoutNameReference(QuicStringPiece name,
+                                    QuicStringPiece value) override;
+  void OnDuplicate(uint64_t index) override;
+  void OnSetDynamicTableCapacity(uint64_t capacity) override;
+  void OnErrorDetected(QuicStringPiece error_message) override;
+
+ private:
+  // The encoder stream uses relative index (but different from the kind of
+  // relative index used on a request stream).  This method converts relative
+  // index to absolute index (zero based).  It returns true on success, or false
+  // if conversion fails due to overflow/underflow.
+  bool EncoderStreamRelativeIndexToAbsoluteIndex(
+      uint64_t relative_index,
+      uint64_t* absolute_index) const;
+
+  EncoderStreamErrorDelegate* const encoder_stream_error_delegate_;
+  QpackEncoderStreamReceiver encoder_stream_receiver_;
+  QpackDecoderStreamSender decoder_stream_sender_;
+  QpackHeaderTable header_table_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_H_
diff --git a/quic/core/qpack/qpack_decoder_stream_receiver.cc b/quic/core/qpack/qpack_decoder_stream_receiver.cc
new file mode 100644
index 0000000..559ce43
--- /dev/null
+++ b/quic/core/qpack/qpack_decoder_stream_receiver.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_receiver.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/quic/core/qpack/qpack_constants.h"
+
+namespace quic {
+
+QpackDecoderStreamReceiver::QpackDecoderStreamReceiver(Delegate* delegate)
+    : instruction_decoder_(QpackDecoderStreamLanguage(), this),
+      delegate_(delegate),
+      error_detected_(false) {
+  DCHECK(delegate_);
+}
+
+void QpackDecoderStreamReceiver::Decode(QuicStringPiece data) {
+  if (data.empty() || error_detected_) {
+    return;
+  }
+
+  instruction_decoder_.Decode(data);
+}
+
+bool QpackDecoderStreamReceiver::OnInstructionDecoded(
+    const QpackInstruction* instruction) {
+  if (instruction == InsertCountIncrementInstruction()) {
+    delegate_->OnInsertCountIncrement(instruction_decoder_.varint());
+    return true;
+  }
+
+  if (instruction == HeaderAcknowledgementInstruction()) {
+    delegate_->OnHeaderAcknowledgement(instruction_decoder_.varint());
+    return true;
+  }
+
+  DCHECK_EQ(instruction, StreamCancellationInstruction());
+  delegate_->OnStreamCancellation(instruction_decoder_.varint());
+  return true;
+}
+
+void QpackDecoderStreamReceiver::OnError(QuicStringPiece error_message) {
+  DCHECK(!error_detected_);
+
+  error_detected_ = true;
+  delegate_->OnErrorDetected(error_message);
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_decoder_stream_receiver.h b/quic/core/qpack/qpack_decoder_stream_receiver.h
new file mode 100644
index 0000000..61c2773
--- /dev/null
+++ b/quic/core/qpack/qpack_decoder_stream_receiver.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_STREAM_RECEIVER_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_STREAM_RECEIVER_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+// This class decodes data received on the decoder stream,
+// and passes it along to its Delegate.
+class QUIC_EXPORT_PRIVATE QpackDecoderStreamReceiver
+    : public QpackInstructionDecoder::Delegate {
+ public:
+  // An interface for handling instructions decoded from the decoder stream, see
+  // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#rfc.section.5.3
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+
+    // 5.3.1 Insert Count Increment
+    virtual void OnInsertCountIncrement(uint64_t increment) = 0;
+    // 5.3.2 Header Acknowledgement
+    virtual void OnHeaderAcknowledgement(QuicStreamId stream_id) = 0;
+    // 5.3.3 Stream Cancellation
+    virtual void OnStreamCancellation(QuicStreamId stream_id) = 0;
+    // Decoding error
+    virtual void OnErrorDetected(QuicStringPiece error_message) = 0;
+  };
+
+  explicit QpackDecoderStreamReceiver(Delegate* delegate);
+  QpackDecoderStreamReceiver() = delete;
+  QpackDecoderStreamReceiver(const QpackDecoderStreamReceiver&) = delete;
+  QpackDecoderStreamReceiver& operator=(const QpackDecoderStreamReceiver&) =
+      delete;
+
+  // Decode data and call appropriate Delegate method after each decoded
+  // instruction.  Once an error occurs, Delegate::OnErrorDetected() is called,
+  // and all further data is ignored.
+  void Decode(QuicStringPiece data);
+
+  // QpackInstructionDecoder::Delegate implementation.
+  bool OnInstructionDecoded(const QpackInstruction* instruction) override;
+  void OnError(QuicStringPiece error_message) override;
+
+ private:
+  QpackInstructionDecoder instruction_decoder_;
+  Delegate* const delegate_;
+
+  // True if a decoding error has been detected.
+  bool error_detected_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_STREAM_RECEIVER_H_
diff --git a/quic/core/qpack/qpack_decoder_stream_receiver_test.cc b/quic/core/qpack/qpack_decoder_stream_receiver_test.cc
new file mode 100644
index 0000000..fc7225f
--- /dev/null
+++ b/quic/core/qpack/qpack_decoder_stream_receiver_test.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_receiver.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+using testing::Eq;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+class MockDelegate : public QpackDecoderStreamReceiver::Delegate {
+ public:
+  ~MockDelegate() override = default;
+
+  MOCK_METHOD1(OnInsertCountIncrement, void(uint64_t increment));
+  MOCK_METHOD1(OnHeaderAcknowledgement, void(QuicStreamId stream_id));
+  MOCK_METHOD1(OnStreamCancellation, void(QuicStreamId stream_id));
+  MOCK_METHOD1(OnErrorDetected, void(QuicStringPiece error_message));
+};
+
+class QpackDecoderStreamReceiverTest : public QuicTest {
+ protected:
+  QpackDecoderStreamReceiverTest() : stream_(&delegate_) {}
+  ~QpackDecoderStreamReceiverTest() override = default;
+
+  QpackDecoderStreamReceiver stream_;
+  StrictMock<MockDelegate> delegate_;
+};
+
+TEST_F(QpackDecoderStreamReceiverTest, InsertCountIncrement) {
+  EXPECT_CALL(delegate_, OnInsertCountIncrement(0));
+  stream_.Decode(QuicTextUtils::HexDecode("00"));
+
+  EXPECT_CALL(delegate_, OnInsertCountIncrement(10));
+  stream_.Decode(QuicTextUtils::HexDecode("0a"));
+
+  EXPECT_CALL(delegate_, OnInsertCountIncrement(63));
+  stream_.Decode(QuicTextUtils::HexDecode("3f00"));
+
+  EXPECT_CALL(delegate_, OnInsertCountIncrement(200));
+  stream_.Decode(QuicTextUtils::HexDecode("3f8901"));
+
+  EXPECT_CALL(delegate_, OnErrorDetected(Eq("Encoded integer too large.")));
+  stream_.Decode(QuicTextUtils::HexDecode("3fffffffffffffffffffff"));
+}
+
+TEST_F(QpackDecoderStreamReceiverTest, HeaderAcknowledgement) {
+  EXPECT_CALL(delegate_, OnHeaderAcknowledgement(0));
+  stream_.Decode(QuicTextUtils::HexDecode("80"));
+
+  EXPECT_CALL(delegate_, OnHeaderAcknowledgement(37));
+  stream_.Decode(QuicTextUtils::HexDecode("a5"));
+
+  EXPECT_CALL(delegate_, OnHeaderAcknowledgement(127));
+  stream_.Decode(QuicTextUtils::HexDecode("ff00"));
+
+  EXPECT_CALL(delegate_, OnHeaderAcknowledgement(503));
+  stream_.Decode(QuicTextUtils::HexDecode("fff802"));
+
+  EXPECT_CALL(delegate_, OnErrorDetected(Eq("Encoded integer too large.")));
+  stream_.Decode(QuicTextUtils::HexDecode("ffffffffffffffffffffff"));
+}
+
+TEST_F(QpackDecoderStreamReceiverTest, StreamCancellation) {
+  EXPECT_CALL(delegate_, OnStreamCancellation(0));
+  stream_.Decode(QuicTextUtils::HexDecode("40"));
+
+  EXPECT_CALL(delegate_, OnStreamCancellation(19));
+  stream_.Decode(QuicTextUtils::HexDecode("53"));
+
+  EXPECT_CALL(delegate_, OnStreamCancellation(63));
+  stream_.Decode(QuicTextUtils::HexDecode("7f00"));
+
+  EXPECT_CALL(delegate_, OnStreamCancellation(110));
+  stream_.Decode(QuicTextUtils::HexDecode("7f2f"));
+
+  EXPECT_CALL(delegate_, OnErrorDetected(Eq("Encoded integer too large.")));
+  stream_.Decode(QuicTextUtils::HexDecode("7fffffffffffffffffffff"));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_decoder_stream_sender.cc b/quic/core/qpack/qpack_decoder_stream_sender.cc
new file mode 100644
index 0000000..141b64a
--- /dev/null
+++ b/quic/core/qpack/qpack_decoder_stream_sender.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender.h"
+
+#include <cstddef>
+#include <limits>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_constants.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+QpackDecoderStreamSender::QpackDecoderStreamSender(Delegate* delegate)
+    : delegate_(delegate) {
+  DCHECK(delegate_);
+}
+
+void QpackDecoderStreamSender::SendInsertCountIncrement(uint64_t increment) {
+  instruction_encoder_.set_varint(increment);
+
+  instruction_encoder_.Encode(InsertCountIncrementInstruction());
+
+  QuicString output;
+
+  instruction_encoder_.Next(std::numeric_limits<size_t>::max(), &output);
+  DCHECK(!instruction_encoder_.HasNext());
+
+  delegate_->WriteDecoderStreamData(output);
+}
+
+void QpackDecoderStreamSender::SendHeaderAcknowledgement(
+    QuicStreamId stream_id) {
+  instruction_encoder_.set_varint(stream_id);
+
+  instruction_encoder_.Encode(HeaderAcknowledgementInstruction());
+
+  QuicString output;
+
+  instruction_encoder_.Next(std::numeric_limits<size_t>::max(), &output);
+  DCHECK(!instruction_encoder_.HasNext());
+
+  delegate_->WriteDecoderStreamData(output);
+}
+
+void QpackDecoderStreamSender::SendStreamCancellation(QuicStreamId stream_id) {
+  instruction_encoder_.set_varint(stream_id);
+
+  instruction_encoder_.Encode(StreamCancellationInstruction());
+
+  QuicString output;
+
+  instruction_encoder_.Next(std::numeric_limits<size_t>::max(), &output);
+  DCHECK(!instruction_encoder_.HasNext());
+
+  delegate_->WriteDecoderStreamData(output);
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_decoder_stream_sender.h b/quic/core/qpack/qpack_decoder_stream_sender.h
new file mode 100644
index 0000000..a791173
--- /dev/null
+++ b/quic/core/qpack/qpack_decoder_stream_sender.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_STREAM_SENDER_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_STREAM_SENDER_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+// This class serializes (encodes) instructions for transmission on the decoder
+// stream.
+class QUIC_EXPORT_PRIVATE QpackDecoderStreamSender {
+ public:
+  // An interface for handling encoded data.
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+
+    // Encoded |data| is ready to be written on the decoder stream.
+    // WriteDecoderStreamData() is called exactly once for each instruction.
+    // |data| contains the entire encoded instruction and it is guaranteed to be
+    // not empty.
+    virtual void WriteDecoderStreamData(QuicStringPiece data) = 0;
+  };
+
+  explicit QpackDecoderStreamSender(Delegate* delegate);
+  QpackDecoderStreamSender() = delete;
+  QpackDecoderStreamSender(const QpackDecoderStreamSender&) = delete;
+  QpackDecoderStreamSender& operator=(const QpackDecoderStreamSender&) = delete;
+
+  // Methods for sending instructions, see
+  // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#rfc.section.5.3
+
+  // 5.3.1 Insert Count Increment
+  void SendInsertCountIncrement(uint64_t increment);
+  // 5.3.2 Header Acknowledgement
+  void SendHeaderAcknowledgement(QuicStreamId stream_id);
+  // 5.3.3 Stream Cancellation
+  void SendStreamCancellation(QuicStreamId stream_id);
+
+ private:
+  Delegate* const delegate_;
+  QpackInstructionEncoder instruction_encoder_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_STREAM_SENDER_H_
diff --git a/quic/core/qpack/qpack_decoder_stream_sender_test.cc b/quic/core/qpack/qpack_decoder_stream_sender_test.cc
new file mode 100644
index 0000000..cc95759
--- /dev/null
+++ b/quic/core/qpack/qpack_decoder_stream_sender_test.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+using ::testing::Eq;
+using ::testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+class MockSenderDelegate : public QpackDecoderStreamSender::Delegate {
+ public:
+  ~MockSenderDelegate() override = default;
+
+  MOCK_METHOD1(WriteDecoderStreamData, void(QuicStringPiece data));
+};
+
+class QpackDecoderStreamSenderTest : public QuicTest {
+ protected:
+  QpackDecoderStreamSenderTest() : stream_(&delegate_) {}
+  ~QpackDecoderStreamSenderTest() override = default;
+
+  StrictMock<MockSenderDelegate> delegate_;
+  QpackDecoderStreamSender stream_;
+};
+
+TEST_F(QpackDecoderStreamSenderTest, InsertCountIncrement) {
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("00"))));
+  stream_.SendInsertCountIncrement(0);
+
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("0a"))));
+  stream_.SendInsertCountIncrement(10);
+
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("3f00"))));
+  stream_.SendInsertCountIncrement(63);
+
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("3f8901"))));
+  stream_.SendInsertCountIncrement(200);
+}
+
+TEST_F(QpackDecoderStreamSenderTest, HeaderAcknowledgement) {
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("80"))));
+  stream_.SendHeaderAcknowledgement(0);
+
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("a5"))));
+  stream_.SendHeaderAcknowledgement(37);
+
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("ff00"))));
+  stream_.SendHeaderAcknowledgement(127);
+
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("fff802"))));
+  stream_.SendHeaderAcknowledgement(503);
+}
+
+TEST_F(QpackDecoderStreamSenderTest, StreamCancellation) {
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("40"))));
+  stream_.SendStreamCancellation(0);
+
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("53"))));
+  stream_.SendStreamCancellation(19);
+
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("7f00"))));
+  stream_.SendStreamCancellation(63);
+
+  EXPECT_CALL(delegate_,
+              WriteDecoderStreamData(Eq(QuicTextUtils::HexDecode("7f2f"))));
+  stream_.SendStreamCancellation(110);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_decoder_test.cc b/quic/core/qpack/qpack_decoder_test.cc
new file mode 100644
index 0000000..7292c50
--- /dev/null
+++ b/quic/core/qpack/qpack_decoder_test.cc
@@ -0,0 +1,701 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+
+using ::testing::Eq;
+using ::testing::Sequence;
+using ::testing::StrictMock;
+using ::testing::Values;
+
+namespace quic {
+namespace test {
+namespace {
+
+// Header Acknowledgement decoder stream instruction with stream_id = 1.
+const char* const kHeaderAcknowledgement = "\x81";
+
+class QpackDecoderTest : public QuicTestWithParam<FragmentMode> {
+ protected:
+  QpackDecoderTest()
+      : qpack_decoder_(&encoder_stream_error_delegate_,
+                       &decoder_stream_sender_delegate_),
+        fragment_mode_(GetParam()) {
+    qpack_decoder_.SetMaximumDynamicTableCapacity(1024);
+  }
+
+  ~QpackDecoderTest() override = default;
+
+  void DecodeEncoderStreamData(QuicStringPiece data) {
+    qpack_decoder_.DecodeEncoderStreamData(data);
+  }
+
+  void DecodeHeaderBlock(QuicStringPiece data) {
+    auto fragment_size_generator =
+        FragmentModeToFragmentSizeGenerator(fragment_mode_);
+    auto progressive_decoder =
+        qpack_decoder_.DecodeHeaderBlock(/* stream_id = */ 1, &handler_);
+    while (!data.empty()) {
+      size_t fragment_size = std::min(fragment_size_generator(), data.size());
+      progressive_decoder->Decode(data.substr(0, fragment_size));
+      data = data.substr(fragment_size);
+    }
+    progressive_decoder->EndHeaderBlock();
+  }
+
+  StrictMock<MockEncoderStreamErrorDelegate> encoder_stream_error_delegate_;
+  StrictMock<MockDecoderStreamSenderDelegate> decoder_stream_sender_delegate_;
+  StrictMock<MockHeadersHandler> handler_;
+
+ private:
+  QpackDecoder qpack_decoder_;
+  const FragmentMode fragment_mode_;
+};
+
+INSTANTIATE_TEST_SUITE_P(, QpackDecoderTest, Values(FragmentMode::kSingleChunk,
+                                                  FragmentMode::kOctetByOctet));
+
+TEST_P(QpackDecoderTest, NoPrefix) {
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Incomplete header data prefix.")));
+
+  // Header Data Prefix is at least two bytes long.
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("00"));
+}
+
+TEST_P(QpackDecoderTest, EmptyHeaderBlock) {
+  EXPECT_CALL(handler_, OnDecodingCompleted());
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("0000"));
+}
+
+TEST_P(QpackDecoderTest, LiteralEntryEmptyName) {
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(""), Eq("foo")));
+  EXPECT_CALL(handler_, OnDecodingCompleted());
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("00002003666f6f"));
+}
+
+TEST_P(QpackDecoderTest, LiteralEntryEmptyValue) {
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("")));
+  EXPECT_CALL(handler_, OnDecodingCompleted());
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("000023666f6f00"));
+}
+
+TEST_P(QpackDecoderTest, LiteralEntryEmptyNameAndValue) {
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(""), Eq("")));
+  EXPECT_CALL(handler_, OnDecodingCompleted());
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("00002000"));
+}
+
+TEST_P(QpackDecoderTest, SimpleLiteralEntry) {
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar")));
+  EXPECT_CALL(handler_, OnDecodingCompleted());
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("000023666f6f03626172"));
+}
+
+TEST_P(QpackDecoderTest, MultipleLiteralEntries) {
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar")));
+  QuicString str(127, 'a');
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foobaar"), QuicStringPiece(str)));
+  EXPECT_CALL(handler_, OnDecodingCompleted());
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0000"                // prefix
+      "23666f6f03626172"    // foo: bar
+      "2700666f6f62616172"  // 7 octet long header name, the smallest number
+                            // that does not fit on a 3-bit prefix.
+      "7f0061616161616161"  // 127 octet long header value, the smallest number
+      "616161616161616161"  // that does not fit on a 7-bit prefix.
+      "6161616161616161616161616161616161616161616161616161616161616161616161"
+      "6161616161616161616161616161616161616161616161616161616161616161616161"
+      "6161616161616161616161616161616161616161616161616161616161616161616161"
+      "616161616161"));
+}
+
+// Name Length value is too large for varint decoder to decode.
+TEST_P(QpackDecoderTest, NameLenTooLargeForVarintDecoder) {
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Encoded integer too large.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("000027ffffffffffffffffffff"));
+}
+
+// Name Length value can be decoded by varint decoder but exceeds 1 MB limit.
+TEST_P(QpackDecoderTest, NameLenExceedsLimit) {
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("String literal too long.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("000027ffff7f"));
+}
+
+// Value Length value is too large for varint decoder to decode.
+TEST_P(QpackDecoderTest, ValueLenTooLargeForVarintDecoder) {
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Encoded integer too large.")));
+
+  DecodeHeaderBlock(
+      QuicTextUtils::HexDecode("000023666f6f7fffffffffffffffffffff"));
+}
+
+// Value Length value can be decoded by varint decoder but exceeds 1 MB limit.
+TEST_P(QpackDecoderTest, ValueLenExceedsLimit) {
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("String literal too long.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("000023666f6f7fffff7f"));
+}
+
+TEST_P(QpackDecoderTest, IncompleteHeaderBlock) {
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Incomplete header block.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("00002366"));
+}
+
+TEST_P(QpackDecoderTest, HuffmanSimple) {
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("custom-key"), Eq("custom-value")));
+  EXPECT_CALL(handler_, OnDecodingCompleted());
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  DecodeHeaderBlock(
+      QuicTextUtils::HexDecode("00002f0125a849e95ba97d7f8925a849e95bb8e8b4bf"));
+}
+
+TEST_P(QpackDecoderTest, AlternatingHuffmanNonHuffman) {
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("custom-key"), Eq("custom-value")))
+      .Times(4);
+  EXPECT_CALL(handler_, OnDecodingCompleted());
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0000"                        // Prefix.
+      "2f0125a849e95ba97d7f"        // Huffman-encoded name.
+      "8925a849e95bb8e8b4bf"        // Huffman-encoded value.
+      "2703637573746f6d2d6b6579"    // Non-Huffman encoded name.
+      "0c637573746f6d2d76616c7565"  // Non-Huffman encoded value.
+      "2f0125a849e95ba97d7f"        // Huffman-encoded name.
+      "0c637573746f6d2d76616c7565"  // Non-Huffman encoded value.
+      "2703637573746f6d2d6b6579"    // Non-Huffman encoded name.
+      "8925a849e95bb8e8b4bf"));     // Huffman-encoded value.
+}
+
+TEST_P(QpackDecoderTest, HuffmanNameDoesNotHaveEOSPrefix) {
+  EXPECT_CALL(handler_, OnDecodingErrorDetected(QuicStringPiece(
+                            "Error in Huffman-encoded string.")));
+
+  // 'y' ends in 0b0 on the most significant bit of the last byte.
+  // The remaining 7 bits must be a prefix of EOS, which is all 1s.
+  DecodeHeaderBlock(
+      QuicTextUtils::HexDecode("00002f0125a849e95ba97d7e8925a849e95bb8e8b4bf"));
+}
+
+TEST_P(QpackDecoderTest, HuffmanValueDoesNotHaveEOSPrefix) {
+  EXPECT_CALL(handler_, OnDecodingErrorDetected(QuicStringPiece(
+                            "Error in Huffman-encoded string.")));
+
+  // 'e' ends in 0b101, taking up the 3 most significant bits of the last byte.
+  // The remaining 5 bits must be a prefix of EOS, which is all 1s.
+  DecodeHeaderBlock(
+      QuicTextUtils::HexDecode("00002f0125a849e95ba97d7f8925a849e95bb8e8b4be"));
+}
+
+TEST_P(QpackDecoderTest, HuffmanNameEOSPrefixTooLong) {
+  EXPECT_CALL(handler_, OnDecodingErrorDetected(QuicStringPiece(
+                            "Error in Huffman-encoded string.")));
+
+  // The trailing EOS prefix must be at most 7 bits long.  Appending one octet
+  // with value 0xff is invalid, even though 0b111111111111111 (15 bits) is a
+  // prefix of EOS.
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "00002f0225a849e95ba97d7fff8925a849e95bb8e8b4bf"));
+}
+
+TEST_P(QpackDecoderTest, HuffmanValueEOSPrefixTooLong) {
+  EXPECT_CALL(handler_, OnDecodingErrorDetected(QuicStringPiece(
+                            "Error in Huffman-encoded string.")));
+
+  // The trailing EOS prefix must be at most 7 bits long.  Appending one octet
+  // with value 0xff is invalid, even though 0b1111111111111 (13 bits) is a
+  // prefix of EOS.
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "00002f0125a849e95ba97d7f8a25a849e95bb8e8b4bfff"));
+}
+
+TEST_P(QpackDecoderTest, StaticTable) {
+  // A header name that has multiple entries with different values.
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("GET")));
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("POST")));
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("TRACE")));
+
+  // A header name that has a single entry with non-empty value.
+  EXPECT_CALL(handler_,
+              OnHeaderDecoded(Eq("accept-encoding"), Eq("gzip, deflate, br")));
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("accept-encoding"), Eq("compress")));
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("accept-encoding"), Eq("")));
+
+  // A header name that has a single entry with empty value.
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("location"), Eq("")));
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("location"), Eq("foo")));
+
+  EXPECT_CALL(handler_, OnDecodingCompleted());
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0000d1dfccd45f108621e9aec2a11f5c8294e75f000554524143455f1000"));
+}
+
+TEST_P(QpackDecoderTest, TooHighStaticTableIndex) {
+  // This is the last entry in the static table with index 98.
+  EXPECT_CALL(handler_,
+              OnHeaderDecoded(Eq("x-frame-options"), Eq("sameorigin")));
+
+  // Addressing entry 99 should trigger an error.
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Static table entry not found.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("0000ff23ff24"));
+}
+
+TEST_P(QpackDecoderTest, DynamicTable) {
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode(
+      "6294e703626172"  // Add literal entry with name "foo" and value "bar".
+      "80035a5a5a"      // Add entry with name of dynamic table entry index 0
+                        // (relative index) and value "ZZZ".
+      "cf8294e7"        // Add entry with name of static table entry index 15
+                        // and value "foo".
+      "01"));           // Duplicate entry with relative index 1.
+
+  // Now there are four entries in the dynamic table.
+  // Entry 0: "foo", "bar"
+  // Entry 1: "foo", "ZZZ"
+  // Entry 2: ":method", "foo"
+  // Entry 3: "foo", "ZZZ"
+
+  // Use a Sequence to test that mock methods are called in order.
+  Sequence s;
+
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))).InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("foo")))
+      .InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("ZZ"))).InSequence(s);
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)))
+      .InSequence(s);
+  EXPECT_CALL(handler_, OnDecodingCompleted()).InSequence(s);
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0500"  // Required Insert Count 4 and Delta Base 0.
+              // Base is 4 + 0 = 4.
+      "83"    // Dynamic table entry with relative index 3, absolute index 0.
+      "82"    // Dynamic table entry with relative index 2, absolute index 1.
+      "81"    // Dynamic table entry with relative index 1, absolute index 2.
+      "80"    // Dynamic table entry with relative index 0, absolute index 3.
+      "41025a5a"));  // Name of entry 1 (relative index) from dynamic table,
+                     // with value "ZZ".
+
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))).InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("foo")))
+      .InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("ZZ"))).InSequence(s);
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)))
+      .InSequence(s);
+  EXPECT_CALL(handler_, OnDecodingCompleted()).InSequence(s);
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0502"  // Required Insert Count 4 and Delta Base 2.
+              // Base is 4 + 2 = 6.
+      "85"    // Dynamic table entry with relative index 5, absolute index 0.
+      "84"    // Dynamic table entry with relative index 4, absolute index 1.
+      "83"    // Dynamic table entry with relative index 3, absolute index 2.
+      "82"    // Dynamic table entry with relative index 2, absolute index 3.
+      "43025a5a"));  // Name of entry 3 (relative index) from dynamic table,
+                     // with value "ZZ".
+
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))).InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("foo")))
+      .InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ZZZ"))).InSequence(s);
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("ZZ"))).InSequence(s);
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)))
+      .InSequence(s);
+  EXPECT_CALL(handler_, OnDecodingCompleted()).InSequence(s);
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0582"  // Required Insert Count 4 and Delta Base 2 with sign bit set.
+              // Base is 4 - 2 - 1 = 1.
+      "80"    // Dynamic table entry with relative index 0, absolute index 0.
+      "10"    // Dynamic table entry with post-base index 0, absolute index 1.
+      "11"    // Dynamic table entry with post-base index 1, absolute index 2.
+      "12"    // Dynamic table entry with post-base index 2, absolute index 3.
+      "01025a5a"));  // Name of entry 1 (post-base index) from dynamic table,
+                     // with value "ZZ".
+}
+
+TEST_P(QpackDecoderTest, DecreasingDynamicTableCapacityEvictsEntries) {
+  // Add literal entry with name "foo" and value "bar".
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("6294e703626172"));
+
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar")));
+  EXPECT_CALL(handler_, OnDecodingCompleted());
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0200"   // Required Insert Count 1 and Delta Base 0.
+               // Base is 1 + 0 = 1.
+      "80"));  // Dynamic table entry with relative index 0, absolute index 0.
+
+  // Change dynamic table capacity to 32 bytes, smaller than the entry.
+  // This must cause the entry to be evicted.
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("3f01"));
+
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Dynamic table entry not found.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0200"   // Required Insert Count 1 and Delta Base 0.
+               // Base is 1 + 0 = 1.
+      "80"));  // Dynamic table entry with relative index 0, absolute index 0.
+}
+
+TEST_P(QpackDecoderTest, EncoderStreamErrorEntryTooLarge) {
+  EXPECT_CALL(encoder_stream_error_delegate_,
+              OnEncoderStreamError(Eq("Error inserting literal entry.")));
+
+  // Set dynamic table capacity to 34.
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("3f03"));
+  // Add literal entry with name "foo" and value "bar", size is 32 + 3 + 3 = 38.
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("6294e703626172"));
+}
+
+TEST_P(QpackDecoderTest, EncoderStreamErrorInvalidStaticTableEntry) {
+  EXPECT_CALL(encoder_stream_error_delegate_,
+              OnEncoderStreamError(Eq("Invalid static table entry.")));
+
+  // Address invalid static table entry index 99.
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("ff2400"));
+}
+
+TEST_P(QpackDecoderTest, EncoderStreamErrorInvalidDynamicTableEntry) {
+  EXPECT_CALL(encoder_stream_error_delegate_,
+              OnEncoderStreamError(Eq("Dynamic table entry not found.")));
+
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode(
+      "6294e703626172"  // Add literal entry with name "foo" and value "bar".
+      "8100"));  // Address dynamic table entry with relative index 1.  Such
+                 // entry does not exist.  The most recently added and only
+                 // dynamic table entry has relative index 0.
+}
+
+TEST_P(QpackDecoderTest, EncoderStreamErrorDuplicateInvalidEntry) {
+  EXPECT_CALL(encoder_stream_error_delegate_,
+              OnEncoderStreamError(Eq("Dynamic table entry not found.")));
+
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode(
+      "6294e703626172"  // Add literal entry with name "foo" and value "bar".
+      "01"));  // Duplicate dynamic table entry with relative index 1.  Such
+               // entry does not exist.  The most recently added and only
+               // dynamic table entry has relative index 0.
+}
+
+TEST_P(QpackDecoderTest, EncoderStreamErrorTooLargeInteger) {
+  EXPECT_CALL(encoder_stream_error_delegate_,
+              OnEncoderStreamError(Eq("Encoded integer too large.")));
+
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("3fffffffffffffffffffff"));
+}
+
+TEST_P(QpackDecoderTest, InvalidDynamicEntryWhenBaseIsZero) {
+  EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Invalid relative index.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0280"   // Required Insert Count is 1.  Base 1 - 1 - 0 = 0 is explicitly
+               // permitted by the spec.
+      "80"));  // However, addressing entry with relative index 0 would point to
+               // absolute index -1, which is invalid.
+}
+
+TEST_P(QpackDecoderTest, InvalidNegativeBase) {
+  EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Error calculating Base.")));
+
+  // Required Insert Count 1, Delta Base 1 with sign bit set, Base would
+  // be 1 - 1 - 1 = -1, but it is not allowed to be negative.
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("0281"));
+}
+
+TEST_P(QpackDecoderTest, InvalidDynamicEntryByRelativeIndex) {
+  // Add literal entry with name "foo" and value "bar".
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("6294e703626172"));
+
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Dynamic table entry not found.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0500"   // Required Insert Count 4 and Delta Base 0.
+               // Base is 4 + 0 = 4.
+      "82"));  // Indexed Header Field instruction addressing relative index 2.
+               // This is absolute index 1. Such entry does not exist.
+
+  EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Invalid relative index.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0500"   // Required Insert Count 4 and Delta Base 0.
+               // Base is 4 + 0 = 4.
+      "84"));  // Indexed Header Field instruction addressing relative index 4.
+               // This is absolute index -1, which is invalid.
+
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Dynamic table entry not found.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0500"     // Required Insert Count 4 and Delta Base 0.
+                 // Base is 4 + 0 = 4.
+      "4200"));  // Literal Header Field with Name Reference instruction
+                 // addressing relative index 2.  This is absolute index 1. Such
+                 // entry does not exist.
+
+  EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Invalid relative index.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0500"     // Required Insert Count 4 and Delta Base 0.
+                 // Base is 4 + 0 = 4.
+      "4400"));  // Literal Header Field with Name Reference instruction
+                 // addressing relative index 4.  This is absolute index -1,
+                 // which is invalid.
+}
+
+TEST_P(QpackDecoderTest, InvalidDynamicEntryByPostBaseIndex) {
+  // Add literal entry with name "foo" and value "bar".
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("6294e703626172"));
+
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Dynamic table entry not found.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0380"   // Required Insert Count 2 and Delta Base 0 with sign bit set.
+               // Base is 2 - 0 - 1 = 1
+      "10"));  // Indexed Header Field instruction addressing dynamic table
+               // entry with post-base index 0, absolute index 1.  Such entry
+               // does not exist.
+
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Dynamic table entry not found.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0380"     // Required Insert Count 2 and Delta Base 0 with sign bit set.
+                 // Base is 2 - 0 - 1 = 1
+      "0000"));  // Literal Header Field With Name Reference instruction
+                 // addressing dynamic table entry with post-base index 0,
+                 // absolute index 1.  Such entry does not exist.
+}
+
+TEST_P(QpackDecoderTest, TableCapacityMustNotExceedMaximum) {
+  EXPECT_CALL(
+      encoder_stream_error_delegate_,
+      OnEncoderStreamError(Eq("Error updating dynamic table capacity.")));
+
+  // Try to update dynamic table capacity to 2048, which exceeds the maximum.
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("3fe10f"));
+}
+
+TEST_P(QpackDecoderTest, SetMaximumDynamicTableCapacity) {
+  // Update dynamic table capacity to 128, which does not exceed the maximum.
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("3f61"));
+}
+
+TEST_P(QpackDecoderTest, InvalidEncodedRequiredInsertCount) {
+  // Maximum dynamic table capacity is 1024.
+  // MaxEntries is 1024 / 32 = 32.
+  // Required Insert Count is decoded modulo 2 * MaxEntries, that is, modulo 64.
+  // A value of 1 cannot be encoded as 65 even though it has the same remainder.
+  EXPECT_CALL(handler_, OnDecodingErrorDetected(
+                            Eq("Error decoding Required Insert Count.")));
+  DecodeHeaderBlock(QuicTextUtils::HexDecode("4100"));
+}
+
+TEST_P(QpackDecoderTest, WrappedRequiredInsertCount) {
+  // Maximum dynamic table capacity is 1024.
+  // MaxEntries is 1024 / 32 = 32.
+
+  // Add literal entry with name "foo" and a 600 byte long value.  This will fit
+  // in the dynamic table once but not twice.
+  DecodeEncoderStreamData(
+      QuicTextUtils::HexDecode("6294e7"     // Name "foo".
+                               "7fd903"));  // Value length 600.
+  QuicString header_value(600, 'Z');
+  DecodeEncoderStreamData(header_value);
+
+  // Duplicate most recent entry 200 times.
+  DecodeEncoderStreamData(QuicString(200, '\x00'));
+
+  // Now there is only one entry in the dynamic table, with absolute index 200.
+
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq(header_value)));
+  EXPECT_CALL(handler_, OnDecodingCompleted());
+  EXPECT_CALL(decoder_stream_sender_delegate_,
+              WriteDecoderStreamData(Eq(kHeaderAcknowledgement)));
+
+  // Send header block with Required Insert Count = 201.
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0a00"   // Encoded Required Insert Count 10, Required Insert Count 201,
+               // Delta Base 0, Base 201.
+      "80"));  // Emit dynamic table entry with relative index 0.
+}
+
+TEST_P(QpackDecoderTest, NonZeroRequiredInsertCountButNoDynamicEntries) {
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("GET")));
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Required Insert Count too large.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0200"   // Required Insert Count is 1.
+      "d1"));  // But the only instruction references the static table.
+}
+
+TEST_P(QpackDecoderTest, AddressEntryNotAllowedByRequiredInsertCount) {
+  EXPECT_CALL(
+      handler_,
+      OnDecodingErrorDetected(
+          Eq("Absolute Index must be smaller than Required Insert Count.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0201"   // Required Insert Count 1 and Delta Base 1.
+               // Base is 1 + 1 = 2.
+      "80"));  // Indexed Header Field instruction addressing dynamic table
+               // entry with relative index 0, absolute index 1.  This is not
+               // allowed by Required Insert Count.
+
+  EXPECT_CALL(
+      handler_,
+      OnDecodingErrorDetected(
+          Eq("Absolute Index must be smaller than Required Insert Count.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0201"     // Required Insert Count 1 and Delta Base 1.
+                 // Base is 1 + 1 = 2.
+      "4000"));  // Literal Header Field with Name Reference instruction
+                 // addressing dynamic table entry with relative index 0,
+                 // absolute index 1.  This is not allowed by Required Index
+                 // Count.
+
+  EXPECT_CALL(
+      handler_,
+      OnDecodingErrorDetected(
+          Eq("Absolute Index must be smaller than Required Insert Count.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0200"   // Required Insert Count 1 and Delta Base 0.
+               // Base is 1 + 0 = 1.
+      "10"));  // Indexed Header Field with Post-Base Index instruction
+               // addressing dynamic table entry with post-base index 0,
+               // absolute index 1.  This is not allowed by Required Insert
+               // Count.
+
+  EXPECT_CALL(
+      handler_,
+      OnDecodingErrorDetected(
+          Eq("Absolute Index must be smaller than Required Insert Count.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0200"     // Required Insert Count 1 and Delta Base 0.
+                 // Base is 1 + 0 = 1.
+      "0000"));  // Literal Header Field with Post-Base Name Reference
+                 // instruction addressing dynamic table entry with post-base
+                 // index 0, absolute index 1.  This is not allowed by Required
+                 // Index Count.
+}
+
+TEST_P(QpackDecoderTest, PromisedRequiredInsertCountLargerThanActual) {
+  // Add literal entry with name "foo" and value "bar".
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("6294e703626172"));
+  // Duplicate entry.
+  DecodeEncoderStreamData(QuicTextUtils::HexDecode("00"));
+
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar")));
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Required Insert Count too large.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0300"   // Required Insert Count 2 and Delta Base 0.
+               // Base is 2 + 0 = 2.
+      "81"));  // Indexed Header Field instruction addressing dynamic table
+               // entry with relative index 1, absolute index 0.  Header block
+               // requires insert count of 1, even though Required Insert Count
+               // is 2.
+
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("")));
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Required Insert Count too large.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0300"     // Required Insert Count 2 and Delta Base 0.
+                 // Base is 2 + 0 = 2.
+      "4100"));  // Literal Header Field with Name Reference instruction
+                 // addressing dynamic table entry with relative index 1,
+                 // absolute index 0.  Header block requires insert count of 1,
+                 // even though Required Insert Count is 2.
+
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar")));
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Required Insert Count too large.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0481"   // Required Insert Count 3 and Delta Base 1 with sign bit set.
+               // Base is 3 - 1 - 1 = 1.
+      "10"));  // Indexed Header Field with Post-Base Index instruction
+               // addressing dynamic table entry with post-base index 0,
+               // absolute index 1.  Header block requires insert count of 2,
+               // even though Required Insert Count is 3.
+
+  EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("")));
+  EXPECT_CALL(handler_,
+              OnDecodingErrorDetected(Eq("Required Insert Count too large.")));
+
+  DecodeHeaderBlock(QuicTextUtils::HexDecode(
+      "0481"     // Required Insert Count 3 and Delta Base 1 with sign bit set.
+                 // Base is 3 - 1 - 1 = 1.
+      "0000"));  // Literal Header Field with Post-Base Name Reference
+                 // instruction addressing dynamic table entry with post-base
+                 // index 0, absolute index 1.  Header block requires insert
+                 // count of 2, even though Required Insert Count is 3.
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_decoder_test_utils.cc b/quic/core/qpack/qpack_decoder_test_utils.cc
new file mode 100644
index 0000000..e8bbd17
--- /dev/null
+++ b/quic/core/qpack/qpack_decoder_test_utils.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <utility>
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace quic {
+namespace test {
+
+void NoopEncoderStreamErrorDelegate::OnEncoderStreamError(
+    QuicStringPiece error_message) {}
+
+void NoopDecoderStreamSenderDelegate::WriteDecoderStreamData(
+    QuicStringPiece data) {}
+
+TestHeadersHandler::TestHeadersHandler()
+    : decoding_completed_(false), decoding_error_detected_(false) {}
+
+void TestHeadersHandler::OnHeaderDecoded(QuicStringPiece name,
+                                         QuicStringPiece value) {
+  ASSERT_FALSE(decoding_completed_);
+  ASSERT_FALSE(decoding_error_detected_);
+
+  header_list_.AppendValueOrAddHeader(name, value);
+}
+
+void TestHeadersHandler::OnDecodingCompleted() {
+  ASSERT_FALSE(decoding_completed_);
+  ASSERT_FALSE(decoding_error_detected_);
+
+  decoding_completed_ = true;
+}
+
+void TestHeadersHandler::OnDecodingErrorDetected(
+    QuicStringPiece error_message) {
+  ASSERT_FALSE(decoding_completed_);
+  ASSERT_FALSE(decoding_error_detected_);
+
+  decoding_error_detected_ = true;
+}
+
+spdy::SpdyHeaderBlock TestHeadersHandler::ReleaseHeaderList() {
+  DCHECK(decoding_completed_);
+  DCHECK(!decoding_error_detected_);
+
+  return std::move(header_list_);
+}
+
+bool TestHeadersHandler::decoding_completed() const {
+  return decoding_completed_;
+}
+
+bool TestHeadersHandler::decoding_error_detected() const {
+  return decoding_error_detected_;
+}
+
+void QpackDecode(
+    QpackDecoder::EncoderStreamErrorDelegate* encoder_stream_error_delegate,
+    QpackDecoderStreamSender::Delegate* decoder_stream_sender_delegate,
+    QpackProgressiveDecoder::HeadersHandlerInterface* handler,
+    const FragmentSizeGenerator& fragment_size_generator,
+    QuicStringPiece data) {
+  QpackDecoder decoder(encoder_stream_error_delegate,
+                       decoder_stream_sender_delegate);
+  auto progressive_decoder =
+      decoder.DecodeHeaderBlock(/* stream_id = */ 1, handler);
+  while (!data.empty()) {
+    size_t fragment_size = std::min(fragment_size_generator(), data.size());
+    progressive_decoder->Decode(data.substr(0, fragment_size));
+    data = data.substr(fragment_size);
+  }
+  progressive_decoder->EndHeaderBlock();
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_decoder_test_utils.h b/quic/core/qpack/qpack_decoder_test_utils.h
new file mode 100644
index 0000000..ca5b608
--- /dev/null
+++ b/quic/core/qpack/qpack_decoder_test_utils.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_TEST_UTILS_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_TEST_UTILS_H_
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+
+namespace quic {
+namespace test {
+
+// QpackDecoder::EncoderStreamErrorDelegate implementation that does nothing.
+class NoopEncoderStreamErrorDelegate
+    : public QpackDecoder::EncoderStreamErrorDelegate {
+ public:
+  ~NoopEncoderStreamErrorDelegate() override = default;
+
+  void OnEncoderStreamError(QuicStringPiece error_message) override;
+};
+
+// Mock QpackDecoder::EncoderStreamErrorDelegate implementation.
+class MockEncoderStreamErrorDelegate
+    : public QpackDecoder::EncoderStreamErrorDelegate {
+ public:
+  ~MockEncoderStreamErrorDelegate() override = default;
+
+  MOCK_METHOD1(OnEncoderStreamError, void(QuicStringPiece error_message));
+};
+
+// QpackDecoderStreamSender::Delegate implementation that does nothing.
+class NoopDecoderStreamSenderDelegate
+    : public QpackDecoderStreamSender::Delegate {
+ public:
+  ~NoopDecoderStreamSenderDelegate() override = default;
+
+  void WriteDecoderStreamData(QuicStringPiece data) override;
+};
+
+// Mock QpackDecoderStreamSender::Delegate implementation.
+class MockDecoderStreamSenderDelegate
+    : public QpackDecoderStreamSender::Delegate {
+ public:
+  ~MockDecoderStreamSenderDelegate() override = default;
+
+  MOCK_METHOD1(WriteDecoderStreamData, void(QuicStringPiece data));
+};
+
+// HeadersHandlerInterface implementation that collects decoded headers
+// into a SpdyHeaderBlock.
+class TestHeadersHandler
+    : public QpackProgressiveDecoder::HeadersHandlerInterface {
+ public:
+  TestHeadersHandler();
+  ~TestHeadersHandler() override = default;
+
+  // HeadersHandlerInterface implementation:
+  void OnHeaderDecoded(QuicStringPiece name, QuicStringPiece value) override;
+  void OnDecodingCompleted() override;
+  void OnDecodingErrorDetected(QuicStringPiece error_message) override;
+
+  // Release decoded header list.  Must only be called if decoding is complete
+  // and no errors have been detected.
+  spdy::SpdyHeaderBlock ReleaseHeaderList();
+
+  bool decoding_completed() const;
+  bool decoding_error_detected() const;
+
+ private:
+  spdy::SpdyHeaderBlock header_list_;
+  bool decoding_completed_;
+  bool decoding_error_detected_;
+};
+
+class MockHeadersHandler
+    : public QpackProgressiveDecoder::HeadersHandlerInterface {
+ public:
+  MockHeadersHandler() = default;
+  MockHeadersHandler(const MockHeadersHandler&) = delete;
+  MockHeadersHandler& operator=(const MockHeadersHandler&) = delete;
+  ~MockHeadersHandler() override = default;
+
+  MOCK_METHOD2(OnHeaderDecoded,
+               void(QuicStringPiece name, QuicStringPiece value));
+  MOCK_METHOD0(OnDecodingCompleted, void());
+  MOCK_METHOD1(OnDecodingErrorDetected, void(QuicStringPiece error_message));
+};
+
+class NoOpHeadersHandler
+    : public QpackProgressiveDecoder::HeadersHandlerInterface {
+ public:
+  ~NoOpHeadersHandler() override = default;
+
+  void OnHeaderDecoded(QuicStringPiece name, QuicStringPiece value) override {}
+  void OnDecodingCompleted() override {}
+  void OnDecodingErrorDetected(QuicStringPiece error_message) override {}
+};
+
+void QpackDecode(
+    QpackDecoder::EncoderStreamErrorDelegate* encoder_stream_error_delegate,
+    QpackDecoderStreamSender::Delegate* decoder_stream_sender_delegate,
+    QpackProgressiveDecoder::HeadersHandlerInterface* handler,
+    const FragmentSizeGenerator& fragment_size_generator,
+    QuicStringPiece data);
+
+}  // namespace test
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_DECODER_TEST_UTILS_H_
diff --git a/quic/core/qpack/qpack_encoder.cc b/quic/core/qpack/qpack_encoder.cc
new file mode 100644
index 0000000..6019229
--- /dev/null
+++ b/quic/core/qpack/qpack_encoder.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_progressive_encoder.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+QpackEncoder::QpackEncoder(
+    DecoderStreamErrorDelegate* decoder_stream_error_delegate,
+    QpackEncoderStreamSender::Delegate* encoder_stream_sender_delegate)
+    : decoder_stream_error_delegate_(decoder_stream_error_delegate),
+      decoder_stream_receiver_(this),
+      encoder_stream_sender_(encoder_stream_sender_delegate) {
+  DCHECK(decoder_stream_error_delegate_);
+  DCHECK(encoder_stream_sender_delegate);
+}
+
+QpackEncoder::~QpackEncoder() {}
+
+std::unique_ptr<spdy::HpackEncoder::ProgressiveEncoder>
+QpackEncoder::EncodeHeaderList(QuicStreamId stream_id,
+                               const spdy::SpdyHeaderBlock* header_list) {
+  return QuicMakeUnique<QpackProgressiveEncoder>(
+      stream_id, &header_table_, &encoder_stream_sender_, header_list);
+}
+
+void QpackEncoder::DecodeDecoderStreamData(QuicStringPiece data) {
+  decoder_stream_receiver_.Decode(data);
+}
+
+void QpackEncoder::OnInsertCountIncrement(uint64_t increment) {
+  // TODO(bnc): Implement dynamic table management for encoding.
+}
+
+void QpackEncoder::OnHeaderAcknowledgement(QuicStreamId stream_id) {
+  // TODO(bnc): Implement dynamic table management for encoding.
+}
+
+void QpackEncoder::OnStreamCancellation(QuicStreamId stream_id) {
+  // TODO(bnc): Implement dynamic table management for encoding.
+}
+
+void QpackEncoder::OnErrorDetected(QuicStringPiece error_message) {
+  decoder_stream_error_delegate_->OnDecoderStreamError(error_message);
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_encoder.h b/quic/core/qpack/qpack_encoder.h
new file mode 100644
index 0000000..4e65532
--- /dev/null
+++ b/quic/core/qpack/qpack_encoder.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_receiver.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+
+namespace spdy {
+
+class SpdyHeaderBlock;
+
+}
+
+namespace quic {
+
+// QPACK encoder class.  Exactly one instance should exist per QUIC connection.
+// This class vends a new QpackProgressiveEncoder instance for each new header
+// list to be encoded.
+class QUIC_EXPORT_PRIVATE QpackEncoder
+    : public QpackDecoderStreamReceiver::Delegate {
+ public:
+  // Interface for receiving notification that an error has occurred on the
+  // decoder stream.  This MUST be treated as a connection error of type
+  // HTTP_QPACK_DECODER_STREAM_ERROR.
+  class QUIC_EXPORT_PRIVATE DecoderStreamErrorDelegate {
+   public:
+    virtual ~DecoderStreamErrorDelegate() {}
+
+    virtual void OnDecoderStreamError(QuicStringPiece error_message) = 0;
+  };
+
+  QpackEncoder(
+      DecoderStreamErrorDelegate* decoder_stream_error_delegate,
+      QpackEncoderStreamSender::Delegate* encoder_stream_sender_delegate);
+  ~QpackEncoder() override;
+
+  // This factory method is called to start encoding a header list.
+  // |*header_list| must remain valid and must not change
+  // during the lifetime of the returned ProgressiveEncoder instance.
+  std::unique_ptr<spdy::HpackEncoder::ProgressiveEncoder> EncodeHeaderList(
+      QuicStreamId stream_id,
+      const spdy::SpdyHeaderBlock* header_list);
+
+  // Decode data received on the decoder stream.
+  void DecodeDecoderStreamData(QuicStringPiece data);
+
+  // QpackDecoderStreamReceiver::Delegate implementation
+  void OnInsertCountIncrement(uint64_t increment) override;
+  void OnHeaderAcknowledgement(QuicStreamId stream_id) override;
+  void OnStreamCancellation(QuicStreamId stream_id) override;
+  void OnErrorDetected(QuicStringPiece error_message) override;
+
+ private:
+  DecoderStreamErrorDelegate* const decoder_stream_error_delegate_;
+  QpackDecoderStreamReceiver decoder_stream_receiver_;
+  QpackEncoderStreamSender encoder_stream_sender_;
+  QpackHeaderTable header_table_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_H_
diff --git a/quic/core/qpack/qpack_encoder_stream_receiver.cc b/quic/core/qpack/qpack_encoder_stream_receiver.cc
new file mode 100644
index 0000000..3f8ef08
--- /dev/null
+++ b/quic/core/qpack/qpack_encoder_stream_receiver.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_receiver.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/quic/core/qpack/qpack_constants.h"
+
+namespace quic {
+
+QpackEncoderStreamReceiver::QpackEncoderStreamReceiver(Delegate* delegate)
+    : instruction_decoder_(QpackEncoderStreamLanguage(), this),
+      delegate_(delegate),
+      error_detected_(false) {
+  DCHECK(delegate_);
+}
+
+void QpackEncoderStreamReceiver::Decode(QuicStringPiece data) {
+  if (data.empty() || error_detected_) {
+    return;
+  }
+
+  instruction_decoder_.Decode(data);
+}
+
+bool QpackEncoderStreamReceiver::OnInstructionDecoded(
+    const QpackInstruction* instruction) {
+  if (instruction == InsertWithNameReferenceInstruction()) {
+    delegate_->OnInsertWithNameReference(instruction_decoder_.s_bit(),
+                                         instruction_decoder_.varint(),
+                                         instruction_decoder_.value());
+    return true;
+  }
+
+  if (instruction == InsertWithoutNameReferenceInstruction()) {
+    delegate_->OnInsertWithoutNameReference(instruction_decoder_.name(),
+                                            instruction_decoder_.value());
+    return true;
+  }
+
+  if (instruction == DuplicateInstruction()) {
+    delegate_->OnDuplicate(instruction_decoder_.varint());
+    return true;
+  }
+
+  DCHECK_EQ(instruction, SetDynamicTableCapacityInstruction());
+  delegate_->OnSetDynamicTableCapacity(instruction_decoder_.varint());
+  return true;
+}
+
+void QpackEncoderStreamReceiver::OnError(QuicStringPiece error_message) {
+  DCHECK(!error_detected_);
+
+  error_detected_ = true;
+  delegate_->OnErrorDetected(error_message);
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_encoder_stream_receiver.h b/quic/core/qpack/qpack_encoder_stream_receiver.h
new file mode 100644
index 0000000..66a2985
--- /dev/null
+++ b/quic/core/qpack/qpack_encoder_stream_receiver.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_STREAM_RECEIVER_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_STREAM_RECEIVER_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+// This class decodes data received on the encoder stream.
+class QUIC_EXPORT_PRIVATE QpackEncoderStreamReceiver
+    : public QpackInstructionDecoder::Delegate {
+ public:
+  // An interface for handling instructions decoded from the encoder stream, see
+  // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#rfc.section.5.2
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+
+    // 5.2.1. Insert With Name Reference
+    virtual void OnInsertWithNameReference(bool is_static,
+                                           uint64_t name_index,
+                                           QuicStringPiece value) = 0;
+    // 5.2.2. Insert Without Name Reference
+    virtual void OnInsertWithoutNameReference(QuicStringPiece name,
+                                              QuicStringPiece value) = 0;
+    // 5.2.3. Duplicate
+    virtual void OnDuplicate(uint64_t index) = 0;
+    // 5.2.4. Set Dynamic Table Capacity
+    virtual void OnSetDynamicTableCapacity(uint64_t capacity) = 0;
+    // Decoding error
+    virtual void OnErrorDetected(QuicStringPiece error_message) = 0;
+  };
+
+  explicit QpackEncoderStreamReceiver(Delegate* delegate);
+  QpackEncoderStreamReceiver() = delete;
+  QpackEncoderStreamReceiver(const QpackEncoderStreamReceiver&) = delete;
+  QpackEncoderStreamReceiver& operator=(const QpackEncoderStreamReceiver&) =
+      delete;
+  ~QpackEncoderStreamReceiver() override = default;
+
+  // Decode data and call appropriate Delegate method after each decoded
+  // instruction.  Once an error occurs, Delegate::OnErrorDetected() is called,
+  // and all further data is ignored.
+  void Decode(QuicStringPiece data);
+
+  // QpackInstructionDecoder::Delegate implementation.
+  bool OnInstructionDecoded(const QpackInstruction* instruction) override;
+  void OnError(QuicStringPiece error_message) override;
+
+ private:
+  QpackInstructionDecoder instruction_decoder_;
+  Delegate* const delegate_;
+
+  // True if a decoding error has been detected.
+  bool error_detected_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_STREAM_RECEIVER_H_
diff --git a/quic/core/qpack/qpack_encoder_stream_receiver_test.cc b/quic/core/qpack/qpack_encoder_stream_receiver_test.cc
new file mode 100644
index 0000000..5685981
--- /dev/null
+++ b/quic/core/qpack/qpack_encoder_stream_receiver_test.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_receiver.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+using testing::Eq;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+class MockDelegate : public QpackEncoderStreamReceiver::Delegate {
+ public:
+  ~MockDelegate() override = default;
+
+  MOCK_METHOD3(OnInsertWithNameReference,
+               void(bool is_static,
+                    uint64_t name_index,
+                    QuicStringPiece value));
+  MOCK_METHOD2(OnInsertWithoutNameReference,
+               void(QuicStringPiece name, QuicStringPiece value));
+  MOCK_METHOD1(OnDuplicate, void(uint64_t index));
+  MOCK_METHOD1(OnSetDynamicTableCapacity, void(uint64_t capacity));
+  MOCK_METHOD1(OnErrorDetected, void(QuicStringPiece error_message));
+};
+
+class QpackEncoderStreamReceiverTest : public QuicTest {
+ protected:
+  QpackEncoderStreamReceiverTest() : stream_(&delegate_) {}
+  ~QpackEncoderStreamReceiverTest() override = default;
+
+  void Decode(QuicStringPiece data) { stream_.Decode(data); }
+  StrictMock<MockDelegate>* delegate() { return &delegate_; }
+
+ private:
+  QpackEncoderStreamReceiver stream_;
+  StrictMock<MockDelegate> delegate_;
+};
+
+TEST_F(QpackEncoderStreamReceiverTest, InsertWithNameReference) {
+  // Static, index fits in prefix, empty value.
+  EXPECT_CALL(*delegate(), OnInsertWithNameReference(true, 5, Eq("")));
+  // Static, index fits in prefix, Huffman encoded value.
+  EXPECT_CALL(*delegate(), OnInsertWithNameReference(true, 2, Eq("foo")));
+  // Not static, index does not fit in prefix, not Huffman encoded value.
+  EXPECT_CALL(*delegate(), OnInsertWithNameReference(false, 137, Eq("bar")));
+  // Value length does not fit in prefix.
+  // 'Z' would be Huffman encoded to 8 bits, so no Huffman encoding is used.
+  EXPECT_CALL(*delegate(),
+              OnInsertWithNameReference(false, 42, Eq(QuicString(127, 'Z'))));
+
+  Decode(QuicTextUtils::HexDecode(
+      "c500"
+      "c28294e7"
+      "bf4a03626172"
+      "aa7f005a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+      "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+      "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+      "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"));
+}
+
+TEST_F(QpackEncoderStreamReceiverTest, InsertWithNameReferenceIndexTooLarge) {
+  EXPECT_CALL(*delegate(), OnErrorDetected(Eq("Encoded integer too large.")));
+
+  Decode(QuicTextUtils::HexDecode("bfffffffffffffffffffffff"));
+}
+
+TEST_F(QpackEncoderStreamReceiverTest, InsertWithNameReferenceValueTooLong) {
+  EXPECT_CALL(*delegate(), OnErrorDetected(Eq("Encoded integer too large.")));
+
+  Decode(QuicTextUtils::HexDecode("c57fffffffffffffffffffff"));
+}
+
+TEST_F(QpackEncoderStreamReceiverTest, InsertWithoutNameReference) {
+  // Empty name and value.
+  EXPECT_CALL(*delegate(), OnInsertWithoutNameReference(Eq(""), Eq("")));
+  // Huffman encoded short strings.
+  EXPECT_CALL(*delegate(), OnInsertWithoutNameReference(Eq("bar"), Eq("bar")));
+  // Not Huffman encoded short strings.
+  EXPECT_CALL(*delegate(), OnInsertWithoutNameReference(Eq("foo"), Eq("foo")));
+  // Not Huffman encoded long strings; length does not fit on prefix.
+  // 'Z' would be Huffman encoded to 8 bits, so no Huffman encoding is used.
+  EXPECT_CALL(*delegate(),
+              OnInsertWithoutNameReference(Eq(QuicString(31, 'Z')),
+                                           Eq(QuicString(127, 'Z'))));
+
+  Decode(QuicTextUtils::HexDecode(
+      "4000"
+      "4362617203626172"
+      "6294e78294e7"
+      "5f005a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a7f005a"
+      "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+      "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+      "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+      "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"));
+}
+
+// Name Length value is too large for varint decoder to decode.
+TEST_F(QpackEncoderStreamReceiverTest,
+       InsertWithoutNameReferenceNameTooLongForVarintDecoder) {
+  EXPECT_CALL(*delegate(), OnErrorDetected(Eq("Encoded integer too large.")));
+
+  Decode(QuicTextUtils::HexDecode("5fffffffffffffffffffff"));
+}
+
+// Name Length value can be decoded by varint decoder but exceeds 1 MB limit.
+TEST_F(QpackEncoderStreamReceiverTest,
+       InsertWithoutNameReferenceNameExceedsLimit) {
+  EXPECT_CALL(*delegate(), OnErrorDetected(Eq("String literal too long.")));
+
+  Decode(QuicTextUtils::HexDecode("5fffff7f"));
+}
+
+// Value Length value is too large for varint decoder to decode.
+TEST_F(QpackEncoderStreamReceiverTest,
+       InsertWithoutNameReferenceValueTooLongForVarintDecoder) {
+  EXPECT_CALL(*delegate(), OnErrorDetected(Eq("Encoded integer too large.")));
+
+  Decode(QuicTextUtils::HexDecode("436261727fffffffffffffffffffff"));
+}
+
+// Value Length value can be decoded by varint decoder but exceeds 1 MB limit.
+TEST_F(QpackEncoderStreamReceiverTest,
+       InsertWithoutNameReferenceValueExceedsLimit) {
+  EXPECT_CALL(*delegate(), OnErrorDetected(Eq("String literal too long.")));
+
+  Decode(QuicTextUtils::HexDecode("436261727fffff7f"));
+}
+
+TEST_F(QpackEncoderStreamReceiverTest, Duplicate) {
+  // Small index fits in prefix.
+  EXPECT_CALL(*delegate(), OnDuplicate(17));
+  // Large index requires two extension bytes.
+  EXPECT_CALL(*delegate(), OnDuplicate(500));
+
+  Decode(QuicTextUtils::HexDecode("111fd503"));
+}
+
+TEST_F(QpackEncoderStreamReceiverTest, DuplicateIndexTooLarge) {
+  EXPECT_CALL(*delegate(), OnErrorDetected(Eq("Encoded integer too large.")));
+
+  Decode(QuicTextUtils::HexDecode("1fffffffffffffffffffff"));
+}
+
+TEST_F(QpackEncoderStreamReceiverTest, SetDynamicTableCapacity) {
+  // Small capacity fits in prefix.
+  EXPECT_CALL(*delegate(), OnSetDynamicTableCapacity(17));
+  // Large capacity requires two extension bytes.
+  EXPECT_CALL(*delegate(), OnSetDynamicTableCapacity(500));
+
+  Decode(QuicTextUtils::HexDecode("313fd503"));
+}
+
+TEST_F(QpackEncoderStreamReceiverTest, SetDynamicTableCapacityTooLarge) {
+  EXPECT_CALL(*delegate(), OnErrorDetected(Eq("Encoded integer too large.")));
+
+  Decode(QuicTextUtils::HexDecode("3fffffffffffffffffffff"));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_encoder_stream_sender.cc b/quic/core/qpack/qpack_encoder_stream_sender.cc
new file mode 100644
index 0000000..fb43046
--- /dev/null
+++ b/quic/core/qpack/qpack_encoder_stream_sender.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h"
+
+#include <cstddef>
+#include <limits>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_constants.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+QpackEncoderStreamSender::QpackEncoderStreamSender(Delegate* delegate)
+    : delegate_(delegate) {
+  DCHECK(delegate_);
+}
+
+void QpackEncoderStreamSender::SendInsertWithNameReference(
+    bool is_static,
+    uint64_t name_index,
+    QuicStringPiece value) {
+  instruction_encoder_.set_s_bit(is_static);
+  instruction_encoder_.set_varint(name_index);
+  instruction_encoder_.set_value(value);
+
+  instruction_encoder_.Encode(InsertWithNameReferenceInstruction());
+
+  QuicString output;
+
+  instruction_encoder_.Next(std::numeric_limits<size_t>::max(), &output);
+  DCHECK(!instruction_encoder_.HasNext());
+
+  delegate_->WriteEncoderStreamData(output);
+}
+
+void QpackEncoderStreamSender::SendInsertWithoutNameReference(
+    QuicStringPiece name,
+    QuicStringPiece value) {
+  instruction_encoder_.set_name(name);
+  instruction_encoder_.set_value(value);
+
+  instruction_encoder_.Encode(InsertWithoutNameReferenceInstruction());
+
+  QuicString output;
+
+  instruction_encoder_.Next(std::numeric_limits<size_t>::max(), &output);
+  DCHECK(!instruction_encoder_.HasNext());
+
+  delegate_->WriteEncoderStreamData(output);
+}
+
+void QpackEncoderStreamSender::SendDuplicate(uint64_t index) {
+  instruction_encoder_.set_varint(index);
+
+  instruction_encoder_.Encode(DuplicateInstruction());
+
+  QuicString output;
+
+  instruction_encoder_.Next(std::numeric_limits<size_t>::max(), &output);
+  DCHECK(!instruction_encoder_.HasNext());
+
+  delegate_->WriteEncoderStreamData(output);
+}
+
+void QpackEncoderStreamSender::SendSetDynamicTableCapacity(uint64_t capacity) {
+  instruction_encoder_.set_varint(capacity);
+
+  instruction_encoder_.Encode(SetDynamicTableCapacityInstruction());
+
+  QuicString output;
+
+  instruction_encoder_.Next(std::numeric_limits<size_t>::max(), &output);
+  DCHECK(!instruction_encoder_.HasNext());
+
+  delegate_->WriteEncoderStreamData(output);
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_encoder_stream_sender.h b/quic/core/qpack/qpack_encoder_stream_sender.h
new file mode 100644
index 0000000..ad34568
--- /dev/null
+++ b/quic/core/qpack/qpack_encoder_stream_sender.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_STREAM_SENDER_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_STREAM_SENDER_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+// This class serializes instructions for transmission on the encoder stream.
+class QUIC_EXPORT_PRIVATE QpackEncoderStreamSender {
+ public:
+  // An interface for handling encoded data.
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+
+    // Encoded |data| is ready to be written on the encoder stream.
+    // WriteEncoderStreamData() is called exactly once for each instruction.
+    // |data| contains the entire encoded instruction and it is guaranteed to be
+    // not empty.
+    virtual void WriteEncoderStreamData(QuicStringPiece data) = 0;
+  };
+
+  explicit QpackEncoderStreamSender(Delegate* delegate);
+  QpackEncoderStreamSender() = delete;
+  QpackEncoderStreamSender(const QpackEncoderStreamSender&) = delete;
+  QpackEncoderStreamSender& operator=(const QpackEncoderStreamSender&) = delete;
+
+  // Methods for sending instructions, see
+  // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#rfc.section.5.2
+
+  // 5.2.1. Insert With Name Reference
+  void SendInsertWithNameReference(bool is_static,
+                                   uint64_t name_index,
+                                   QuicStringPiece value);
+  // 5.2.2. Insert Without Name Reference
+  void SendInsertWithoutNameReference(QuicStringPiece name,
+                                      QuicStringPiece value);
+  // 5.2.3. Duplicate
+  void SendDuplicate(uint64_t index);
+  // 5.2.4. Set Dynamic Table Capacity
+  void SendSetDynamicTableCapacity(uint64_t capacity);
+
+ private:
+  Delegate* const delegate_;
+  QpackInstructionEncoder instruction_encoder_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_STREAM_SENDER_H_
diff --git a/quic/core/qpack/qpack_encoder_stream_sender_test.cc b/quic/core/qpack/qpack_encoder_stream_sender_test.cc
new file mode 100644
index 0000000..a850f18
--- /dev/null
+++ b/quic/core/qpack/qpack_encoder_stream_sender_test.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+using ::testing::Eq;
+using ::testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+class QpackEncoderStreamSenderTest : public QuicTest {
+ protected:
+  QpackEncoderStreamSenderTest() : stream_(&delegate_) {}
+  ~QpackEncoderStreamSenderTest() override = default;
+
+  StrictMock<MockEncoderStreamSenderDelegate> delegate_;
+  QpackEncoderStreamSender stream_;
+};
+
+TEST_F(QpackEncoderStreamSenderTest, InsertWithNameReference) {
+  // Static, index fits in prefix, empty value.
+  EXPECT_CALL(delegate_,
+              WriteEncoderStreamData(Eq(QuicTextUtils::HexDecode("c500"))));
+  stream_.SendInsertWithNameReference(true, 5, "");
+
+  // Static, index fits in prefix, Huffman encoded value.
+  EXPECT_CALL(delegate_,
+              WriteEncoderStreamData(Eq(QuicTextUtils::HexDecode("c28294e7"))));
+  stream_.SendInsertWithNameReference(true, 2, "foo");
+
+  // Not static, index does not fit in prefix, not Huffman encoded value.
+  EXPECT_CALL(delegate_, WriteEncoderStreamData(
+                             Eq(QuicTextUtils::HexDecode("bf4a03626172"))));
+  stream_.SendInsertWithNameReference(false, 137, "bar");
+
+  // Value length does not fit in prefix.
+  // 'Z' would be Huffman encoded to 8 bits, so no Huffman encoding is used.
+  EXPECT_CALL(
+      delegate_,
+      WriteEncoderStreamData(Eq(QuicTextUtils::HexDecode(
+          "aa7f005a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+          "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+          "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+          "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"))));
+  stream_.SendInsertWithNameReference(false, 42, QuicString(127, 'Z'));
+}
+
+TEST_F(QpackEncoderStreamSenderTest, InsertWithoutNameReference) {
+  // Empty name and value.
+  EXPECT_CALL(delegate_,
+              WriteEncoderStreamData(Eq(QuicTextUtils::HexDecode("4000"))));
+  stream_.SendInsertWithoutNameReference("", "");
+
+  // Huffman encoded short strings.
+  EXPECT_CALL(delegate_, WriteEncoderStreamData(
+                             Eq(QuicTextUtils::HexDecode("4362617203626172"))));
+  stream_.SendInsertWithoutNameReference("bar", "bar");
+
+  // Not Huffman encoded short strings.
+  EXPECT_CALL(delegate_, WriteEncoderStreamData(
+                             Eq(QuicTextUtils::HexDecode("6294e78294e7"))));
+  stream_.SendInsertWithoutNameReference("foo", "foo");
+
+  // Not Huffman encoded long strings; length does not fit on prefix.
+  // 'Z' would be Huffman encoded to 8 bits, so no Huffman encoding is used.
+  EXPECT_CALL(
+      delegate_,
+      WriteEncoderStreamData(Eq(QuicTextUtils::HexDecode(
+          "5f005a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a7f"
+          "005a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+          "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+          "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+          "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"))));
+  stream_.SendInsertWithoutNameReference(QuicString(31, 'Z'),
+                                         QuicString(127, 'Z'));
+}
+
+TEST_F(QpackEncoderStreamSenderTest, Duplicate) {
+  // Small index fits in prefix.
+  EXPECT_CALL(delegate_,
+              WriteEncoderStreamData(Eq(QuicTextUtils::HexDecode("11"))));
+  stream_.SendDuplicate(17);
+
+  // Large index requires two extension bytes.
+  EXPECT_CALL(delegate_,
+              WriteEncoderStreamData(Eq(QuicTextUtils::HexDecode("1fd503"))));
+  stream_.SendDuplicate(500);
+}
+
+TEST_F(QpackEncoderStreamSenderTest, SetDynamicTableCapacity) {
+  // Small capacity fits in prefix.
+  EXPECT_CALL(delegate_,
+              WriteEncoderStreamData(Eq(QuicTextUtils::HexDecode("31"))));
+  stream_.SendSetDynamicTableCapacity(17);
+
+  // Large capacity requires two extension bytes.
+  EXPECT_CALL(delegate_,
+              WriteEncoderStreamData(Eq(QuicTextUtils::HexDecode("3fd503"))));
+  stream_.SendSetDynamicTableCapacity(500);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_encoder_test.cc b/quic/core/qpack/qpack_encoder_test.cc
new file mode 100644
index 0000000..6dea968
--- /dev/null
+++ b/quic/core/qpack/qpack_encoder_test.cc
@@ -0,0 +1,167 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+using ::testing::Eq;
+using ::testing::StrictMock;
+using ::testing::Values;
+
+namespace quic {
+namespace test {
+namespace {
+
+class QpackEncoderTest : public QuicTestWithParam<FragmentMode> {
+ protected:
+  QpackEncoderTest() : fragment_mode_(GetParam()) {}
+  ~QpackEncoderTest() override = default;
+
+  QuicString Encode(const spdy::SpdyHeaderBlock* header_list) {
+    return QpackEncode(
+        &decoder_stream_error_delegate_, &encoder_stream_sender_delegate_,
+        FragmentModeToFragmentSizeGenerator(fragment_mode_), header_list);
+  }
+
+  StrictMock<MockDecoderStreamErrorDelegate> decoder_stream_error_delegate_;
+  NoopEncoderStreamSenderDelegate encoder_stream_sender_delegate_;
+
+ private:
+  const FragmentMode fragment_mode_;
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         QpackEncoderTest,
+                         Values(FragmentMode::kSingleChunk,
+                                FragmentMode::kOctetByOctet));
+
+TEST_P(QpackEncoderTest, Empty) {
+  spdy::SpdyHeaderBlock header_list;
+  QuicString output = Encode(&header_list);
+
+  EXPECT_EQ(QuicTextUtils::HexDecode("0000"), output);
+}
+
+TEST_P(QpackEncoderTest, EmptyName) {
+  spdy::SpdyHeaderBlock header_list;
+  header_list[""] = "foo";
+  QuicString output = Encode(&header_list);
+
+  EXPECT_EQ(QuicTextUtils::HexDecode("0000208294e7"), output);
+}
+
+TEST_P(QpackEncoderTest, EmptyValue) {
+  spdy::SpdyHeaderBlock header_list;
+  header_list["foo"] = "";
+  QuicString output = Encode(&header_list);
+
+  EXPECT_EQ(QuicTextUtils::HexDecode("00002a94e700"), output);
+}
+
+TEST_P(QpackEncoderTest, EmptyNameAndValue) {
+  spdy::SpdyHeaderBlock header_list;
+  header_list[""] = "";
+  QuicString output = Encode(&header_list);
+
+  EXPECT_EQ(QuicTextUtils::HexDecode("00002000"), output);
+}
+
+TEST_P(QpackEncoderTest, Simple) {
+  spdy::SpdyHeaderBlock header_list;
+  header_list["foo"] = "bar";
+  QuicString output = Encode(&header_list);
+
+  EXPECT_EQ(QuicTextUtils::HexDecode("00002a94e703626172"), output);
+}
+
+TEST_P(QpackEncoderTest, Multiple) {
+  spdy::SpdyHeaderBlock header_list;
+  header_list["foo"] = "bar";
+  // 'Z' would be Huffman encoded to 8 bits, so no Huffman encoding is used.
+  header_list["ZZZZZZZ"] = QuicString(127, 'Z');
+  QuicString output = Encode(&header_list);
+
+  EXPECT_EQ(
+      QuicTextUtils::HexDecode(
+          "0000"                // prefix
+          "2a94e703626172"      // foo: bar
+          "27005a5a5a5a5a5a5a"  // 7 octet long header name, the smallest number
+                                // that does not fit on a 3-bit prefix.
+          "7f005a5a5a5a5a5a5a"  // 127 octet long header value, the smallest
+          "5a5a5a5a5a5a5a5a5a"  // number that does not fit on a 7-bit prefix.
+          "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+          "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+          "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
+          "5a5a5a5a5a5a5a5a5a"),
+      output);
+}
+
+TEST_P(QpackEncoderTest, StaticTable) {
+  {
+    spdy::SpdyHeaderBlock header_list;
+    header_list[":method"] = "GET";
+    header_list["accept-encoding"] = "gzip, deflate, br";
+    header_list["location"] = "";
+
+    QuicString output = Encode(&header_list);
+    EXPECT_EQ(QuicTextUtils::HexDecode("0000d1dfcc"), output);
+  }
+  {
+    spdy::SpdyHeaderBlock header_list;
+    header_list[":method"] = "POST";
+    header_list["accept-encoding"] = "compress";
+    header_list["location"] = "foo";
+
+    QuicString output = Encode(&header_list);
+    EXPECT_EQ(QuicTextUtils::HexDecode("0000d45f108621e9aec2a11f5c8294e7"),
+              output);
+  }
+  {
+    spdy::SpdyHeaderBlock header_list;
+    header_list[":method"] = "TRACE";
+    header_list["accept-encoding"] = "";
+
+    QuicString output = Encode(&header_list);
+    EXPECT_EQ(QuicTextUtils::HexDecode("00005f000554524143455f1000"), output);
+  }
+}
+
+TEST_P(QpackEncoderTest, SimpleIndexed) {
+  spdy::SpdyHeaderBlock header_list;
+  header_list[":path"] = "/";
+
+  QpackEncoder encoder(&decoder_stream_error_delegate_,
+                       &encoder_stream_sender_delegate_);
+  auto progressive_encoder =
+      encoder.EncodeHeaderList(/* stream_id = */ 1, &header_list);
+  EXPECT_TRUE(progressive_encoder->HasNext());
+
+  // This indexed header field takes exactly three bytes:
+  // two for the prefix, one for the indexed static entry.
+  QuicString output;
+  progressive_encoder->Next(3, &output);
+
+  EXPECT_EQ(QuicTextUtils::HexDecode("0000c1"), output);
+  EXPECT_FALSE(progressive_encoder->HasNext());
+}
+
+TEST_P(QpackEncoderTest, DecoderStreamError) {
+  EXPECT_CALL(decoder_stream_error_delegate_,
+              OnDecoderStreamError(Eq("Encoded integer too large.")));
+
+  QpackEncoder encoder(&decoder_stream_error_delegate_,
+                       &encoder_stream_sender_delegate_);
+  encoder.DecodeDecoderStreamData(
+      QuicTextUtils::HexDecode("ffffffffffffffffffffff"));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_encoder_test_utils.cc b/quic/core/qpack/qpack_encoder_test_utils.cc
new file mode 100644
index 0000000..b9a3263
--- /dev/null
+++ b/quic/core/qpack/qpack_encoder_test_utils.cc
@@ -0,0 +1,37 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test_utils.h"
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+
+namespace quic {
+namespace test {
+
+void NoopDecoderStreamErrorDelegate::OnDecoderStreamError(
+    QuicStringPiece error_message) {}
+
+void NoopEncoderStreamSenderDelegate::WriteEncoderStreamData(
+    QuicStringPiece data) {}
+
+QuicString QpackEncode(
+    QpackEncoder::DecoderStreamErrorDelegate* decoder_stream_error_delegate,
+    QpackEncoderStreamSender::Delegate* encoder_stream_sender_delegate,
+    const FragmentSizeGenerator& fragment_size_generator,
+    const spdy::SpdyHeaderBlock* header_list) {
+  QpackEncoder encoder(decoder_stream_error_delegate,
+                       encoder_stream_sender_delegate);
+  auto progressive_encoder =
+      encoder.EncodeHeaderList(/* stream_id = */ 1, header_list);
+
+  QuicString output;
+  while (progressive_encoder->HasNext()) {
+    progressive_encoder->Next(fragment_size_generator(), &output);
+  }
+
+  return output;
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_encoder_test_utils.h b/quic/core/qpack/qpack_encoder_test_utils.h
new file mode 100644
index 0000000..03d05cb
--- /dev/null
+++ b/quic/core/qpack/qpack_encoder_test_utils.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_TEST_UTILS_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_TEST_UTILS_H_
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+
+namespace quic {
+namespace test {
+
+// QpackEncoder::DecoderStreamErrorDelegate implementation that does nothing.
+class NoopDecoderStreamErrorDelegate
+    : public QpackEncoder::DecoderStreamErrorDelegate {
+ public:
+  ~NoopDecoderStreamErrorDelegate() override = default;
+
+  void OnDecoderStreamError(QuicStringPiece error_message) override;
+};
+
+// Mock QpackEncoder::DecoderStreamErrorDelegate implementation.
+class MockDecoderStreamErrorDelegate
+    : public QpackEncoder::DecoderStreamErrorDelegate {
+ public:
+  ~MockDecoderStreamErrorDelegate() override = default;
+
+  MOCK_METHOD1(OnDecoderStreamError, void(QuicStringPiece error_message));
+};
+
+// QpackEncoderStreamSender::Delegate implementation that does nothing.
+class NoopEncoderStreamSenderDelegate
+    : public QpackEncoderStreamSender::Delegate {
+ public:
+  ~NoopEncoderStreamSenderDelegate() override = default;
+
+  void WriteEncoderStreamData(QuicStringPiece data) override;
+};
+
+// Mock QpackEncoderStreamSender::Delegate implementation.
+class MockEncoderStreamSenderDelegate
+    : public QpackEncoderStreamSender::Delegate {
+ public:
+  ~MockEncoderStreamSenderDelegate() override = default;
+
+  MOCK_METHOD1(WriteEncoderStreamData, void(QuicStringPiece data));
+};
+
+QuicString QpackEncode(
+    QpackEncoder::DecoderStreamErrorDelegate* decoder_stream_error_delegate,
+    QpackEncoderStreamSender::Delegate* encoder_stream_sender_delegate,
+    const FragmentSizeGenerator& fragment_size_generator,
+    const spdy::SpdyHeaderBlock* header_list);
+
+}  // namespace test
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_ENCODER_TEST_UTILS_H_
diff --git a/quic/core/qpack/qpack_header_table.cc b/quic/core/qpack/qpack_header_table.cc
new file mode 100644
index 0000000..4ba3ee3
--- /dev/null
+++ b/quic/core/qpack/qpack_header_table.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h"
+
+namespace quic {
+
+namespace {
+
+const uint64_t kEntrySizeOverhead = 32;
+
+uint64_t EntrySize(QuicStringPiece name, QuicStringPiece value) {
+  return name.size() + value.size() + kEntrySizeOverhead;
+}
+
+}  // anonymous namespace
+
+QpackHeaderTable::QpackHeaderTable()
+    : static_entries_(ObtainQpackStaticTable().GetStaticEntries()),
+      static_index_(ObtainQpackStaticTable().GetStaticIndex()),
+      static_name_index_(ObtainQpackStaticTable().GetStaticNameIndex()),
+      dynamic_table_size_(0),
+      dynamic_table_capacity_(0),
+      maximum_dynamic_table_capacity_(0),
+      max_entries_(0),
+      dropped_entry_count_(0) {}
+
+QpackHeaderTable::~QpackHeaderTable() = default;
+
+const QpackEntry* QpackHeaderTable::LookupEntry(bool is_static,
+                                                uint64_t index) const {
+  if (is_static) {
+    if (index >= static_entries_.size()) {
+      return nullptr;
+    }
+
+    return &static_entries_[index];
+  }
+
+  if (index < dropped_entry_count_) {
+    return nullptr;
+  }
+
+  index -= dropped_entry_count_;
+
+  if (index >= dynamic_entries_.size()) {
+    return nullptr;
+  }
+
+  return &dynamic_entries_[index];
+}
+
+QpackHeaderTable::MatchType QpackHeaderTable::FindHeaderField(
+    QuicStringPiece name,
+    QuicStringPiece value,
+    bool* is_static,
+    uint64_t* index) const {
+  QpackEntry query(name, value);
+
+  // Look for exact match in static table.
+  auto index_it = static_index_.find(&query);
+  if (index_it != static_index_.end()) {
+    DCHECK((*index_it)->IsStatic());
+    *index = (*index_it)->InsertionIndex();
+    *is_static = true;
+    return MatchType::kNameAndValue;
+  }
+
+  // Look for exact match in dynamic table.
+  index_it = dynamic_index_.find(&query);
+  if (index_it != dynamic_index_.end()) {
+    DCHECK(!(*index_it)->IsStatic());
+    *index = (*index_it)->InsertionIndex();
+    *is_static = false;
+    return MatchType::kNameAndValue;
+  }
+
+  // Look for name match in static table.
+  auto name_index_it = static_name_index_.find(name);
+  if (name_index_it != static_name_index_.end()) {
+    DCHECK(name_index_it->second->IsStatic());
+    *index = name_index_it->second->InsertionIndex();
+    *is_static = true;
+    return MatchType::kName;
+  }
+
+  // Look for name match in dynamic table.
+  name_index_it = dynamic_name_index_.find(name);
+  if (name_index_it != dynamic_name_index_.end()) {
+    DCHECK(!name_index_it->second->IsStatic());
+    *index = name_index_it->second->InsertionIndex();
+    *is_static = false;
+    return MatchType::kName;
+  }
+
+  return MatchType::kNoMatch;
+}
+
+const QpackEntry* QpackHeaderTable::InsertEntry(QuicStringPiece name,
+                                                QuicStringPiece value) {
+  const uint64_t entry_size = EntrySize(name, value);
+  if (entry_size > dynamic_table_capacity_) {
+    return nullptr;
+  }
+
+  const uint64_t index = dropped_entry_count_ + dynamic_entries_.size();
+  dynamic_entries_.push_back({name, value, /* is_static = */ false, index});
+  QpackEntry* const new_entry = &dynamic_entries_.back();
+
+  // Evict entries after inserting the new entry instead of before
+  // in order to avoid invalidating |name| and |value|.
+  dynamic_table_size_ += entry_size;
+  EvictDownToCurrentCapacity();
+
+  auto index_result = dynamic_index_.insert(new_entry);
+  if (!index_result.second) {
+    // An entry with the same name and value already exists.  It needs to be
+    // replaced, because |dynamic_index_| tracks the most recent entry for a
+    // given name and value.
+    DCHECK_GT(new_entry->InsertionIndex(),
+              (*index_result.first)->InsertionIndex());
+    dynamic_index_.erase(index_result.first);
+    auto result = dynamic_index_.insert(new_entry);
+    CHECK(result.second);
+  }
+
+  auto name_result = dynamic_name_index_.insert({new_entry->name(), new_entry});
+  if (!name_result.second) {
+    // An entry with the same name already exists.  It needs to be replaced,
+    // because |dynamic_name_index_| tracks the most recent entry for a given
+    // name.
+    DCHECK_GT(new_entry->InsertionIndex(),
+              name_result.first->second->InsertionIndex());
+    dynamic_name_index_.erase(name_result.first);
+    auto result = dynamic_name_index_.insert({new_entry->name(), new_entry});
+    CHECK(result.second);
+  }
+
+  return new_entry;
+}
+
+bool QpackHeaderTable::SetDynamicTableCapacity(uint64_t capacity) {
+  if (capacity > maximum_dynamic_table_capacity_) {
+    return false;
+  }
+
+  dynamic_table_capacity_ = capacity;
+  EvictDownToCurrentCapacity();
+
+  DCHECK_LE(dynamic_table_size_, dynamic_table_capacity_);
+
+  return true;
+}
+
+void QpackHeaderTable::SetMaximumDynamicTableCapacity(
+    uint64_t maximum_dynamic_table_capacity) {
+  // This method can only be called once: in the decoding context, shortly after
+  // construction; in the encoding context, upon receiving the SETTINGS frame.
+  DCHECK_EQ(0u, dynamic_table_capacity_);
+  DCHECK_EQ(0u, maximum_dynamic_table_capacity_);
+  DCHECK_EQ(0u, max_entries_);
+
+  dynamic_table_capacity_ = maximum_dynamic_table_capacity;
+  maximum_dynamic_table_capacity_ = maximum_dynamic_table_capacity;
+  max_entries_ = maximum_dynamic_table_capacity / 32;
+}
+
+void QpackHeaderTable::EvictDownToCurrentCapacity() {
+  while (dynamic_table_size_ > dynamic_table_capacity_) {
+    DCHECK(!dynamic_entries_.empty());
+
+    QpackEntry* const entry = &dynamic_entries_.front();
+    const uint64_t entry_size = EntrySize(entry->name(), entry->value());
+
+    DCHECK_GE(dynamic_table_size_, entry_size);
+    dynamic_table_size_ -= entry_size;
+
+    auto index_it = dynamic_index_.find(entry);
+    // Remove |dynamic_index_| entry only if it points to the same
+    // QpackEntry in |dynamic_entries_|.  Note that |dynamic_index_| has a
+    // comparison function that only considers name and value, not actual
+    // QpackEntry* address, so find() can return a different entry if name and
+    // value match.
+    if (index_it != dynamic_index_.end() && *index_it == entry) {
+      dynamic_index_.erase(index_it);
+    }
+
+    auto name_it = dynamic_name_index_.find(entry->name());
+    // Remove |dynamic_name_index_| entry only if it points to the same
+    // QpackEntry in |dynamic_entries_|.
+    if (name_it != dynamic_name_index_.end() && name_it->second == entry) {
+      dynamic_name_index_.erase(name_it);
+    }
+
+    dynamic_entries_.pop_front();
+    ++dropped_entry_count_;
+  }
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_header_table.h b/quic/core/qpack/qpack_header_table.h
new file mode 100644
index 0000000..eda7ab5
--- /dev/null
+++ b/quic/core/qpack/qpack_header_table.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+
+namespace quic {
+
+using QpackEntry = spdy::HpackEntry;
+
+// This class manages the QPACK static and dynamic tables.  For dynamic entries,
+// it only has a concept of absolute indices.  The caller needs to perform the
+// necessary transformations to and from relative indices and post-base indices.
+class QUIC_EXPORT_PRIVATE QpackHeaderTable {
+ public:
+  using EntryTable = spdy::HpackHeaderTable::EntryTable;
+  using EntryHasher = spdy::HpackHeaderTable::EntryHasher;
+  using EntriesEq = spdy::HpackHeaderTable::EntriesEq;
+  using UnorderedEntrySet = spdy::HpackHeaderTable::UnorderedEntrySet;
+  using NameToEntryMap = spdy::HpackHeaderTable::NameToEntryMap;
+
+  // Result of header table lookup.
+  enum class MatchType { kNameAndValue, kName, kNoMatch };
+
+  QpackHeaderTable();
+  QpackHeaderTable(const QpackHeaderTable&) = delete;
+  QpackHeaderTable& operator=(const QpackHeaderTable&) = delete;
+
+  ~QpackHeaderTable();
+
+  // Returns the entry at absolute index |index| from the static or dynamic
+  // table according to |is_static|.  |index| is zero based for both the static
+  // and the dynamic table.  The returned pointer is valid until the entry is
+  // evicted, even if other entries are inserted into the dynamic table.
+  // Returns nullptr if entry does not exist.
+  const QpackEntry* LookupEntry(bool is_static, uint64_t index) const;
+
+  // Returns the absolute index of an entry with matching name and value if such
+  // exists, otherwise one with matching name is such exists.  |index| is zero
+  // based for both the static and the dynamic table.
+  MatchType FindHeaderField(QuicStringPiece name,
+                            QuicStringPiece value,
+                            bool* is_static,
+                            uint64_t* index) const;
+
+  // Insert (name, value) into the dynamic table.  May evict entries.  Returns a
+  // pointer to the inserted owned entry on success.  Returns nullptr if entry
+  // is larger than the capacity of the dynamic table.
+  const QpackEntry* InsertEntry(QuicStringPiece name, QuicStringPiece value);
+
+  // Change dynamic table capacity to |capacity|.  Returns true on success.
+  // Returns false is |capacity| exceeds maximum dynamic table capacity.
+  bool SetDynamicTableCapacity(uint64_t capacity);
+
+  // Set |maximum_dynamic_table_capacity_|.  The initial value is zero.  The
+  // final value is determined by the decoder and is sent to the encoder as
+  // SETTINGS_HEADER_TABLE_SIZE.  Therefore in the decoding context the final
+  // value can be set upon connection establishment, whereas in the encoding
+  // context it can be set when the SETTINGS frame is received.
+  // This method must only be called at most once.
+  void SetMaximumDynamicTableCapacity(uint64_t maximum_dynamic_table_capacity);
+
+  // Used on request streams to encode and decode Required Insert Count.
+  uint64_t max_entries() const { return max_entries_; }
+
+  // The number of entries inserted to the dynamic table (including ones that
+  // were dropped since).  Used for relative indexing on the encoder stream.
+  uint64_t inserted_entry_count() const {
+    return dynamic_entries_.size() + dropped_entry_count_;
+  }
+
+  // The number of entries dropped from the dynamic table.
+  uint64_t dropped_entry_count() const { return dropped_entry_count_; }
+
+ private:
+  // Evict entries from the dynamic table until table size is less than or equal
+  // to current value of |dynamic_table_capacity_|.
+  void EvictDownToCurrentCapacity();
+
+  // Static Table
+
+  // |static_entries_|, |static_index_|, |static_name_index_| are owned by
+  // QpackStaticTable singleton.
+
+  // Tracks QpackEntries by index.
+  const EntryTable& static_entries_;
+
+  // Tracks the unique static entry for a given header name and value.
+  const UnorderedEntrySet& static_index_;
+
+  // Tracks the first static entry for a given header name.
+  const NameToEntryMap& static_name_index_;
+
+  // Dynamic Table
+
+  // Queue of dynamic table entries, for lookup by index.
+  // |dynamic_entries_| owns the entries in the dynamic table.
+  EntryTable dynamic_entries_;
+
+  // An unordered set of QpackEntry pointers with a comparison operator that
+  // only cares about name and value.  This allows fast lookup of the most
+  // recently inserted dynamic entry for a given header name and value pair.
+  // Entries point to entries owned by |dynamic_entries_|.
+  UnorderedEntrySet dynamic_index_;
+
+  // An unordered map of QpackEntry pointers keyed off header name.  This allows
+  // fast lookup of the most recently inserted dynamic entry for a given header
+  // name.  Entries point to entries owned by |dynamic_entries_|.
+  NameToEntryMap dynamic_name_index_;
+
+  // Size of the dynamic table.  This is the sum of the size of its entries.
+  uint64_t dynamic_table_size_;
+
+  // Dynamic Table Capacity is the maximum allowed value of
+  // |dynamic_table_size_|.  Entries are evicted if necessary before inserting a
+  // new entry to ensure that dynamic table size never exceeds capacity.
+  // Initial value is |maximum_dynamic_table_capacity_|.  Capacity can be
+  // changed by the encoder, as long as it does not exceed
+  // |maximum_dynamic_table_capacity_|.
+  uint64_t dynamic_table_capacity_;
+
+  // Maximum allowed value of |dynamic_table_capacity|.  The initial value is
+  // zero.  Can be changed by SetMaximumDynamicTableCapacity().
+  uint64_t maximum_dynamic_table_capacity_;
+
+  // MaxEntries, see Section 3.2.2.  Calculated based on
+  // |maximum_dynamic_table_capacity_|.
+  uint64_t max_entries_;
+
+  // The number of entries dropped from the dynamic table.
+  uint64_t dropped_entry_count_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_
diff --git a/quic/core/qpack/qpack_header_table_test.cc b/quic/core/qpack/qpack_header_table_test.cc
new file mode 100644
index 0000000..f3ac5b5
--- /dev/null
+++ b/quic/core/qpack/qpack_header_table_test.cc
@@ -0,0 +1,356 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+const uint64_t kMaximumDynamicTableCapacityForTesting = 1024 * 1024;
+
+class QpackHeaderTableTest : public QuicTest {
+ protected:
+  QpackHeaderTableTest() {
+    table_.SetMaximumDynamicTableCapacity(
+        kMaximumDynamicTableCapacityForTesting);
+  }
+  ~QpackHeaderTableTest() override = default;
+
+  void ExpectEntryAtIndex(bool is_static,
+                          uint64_t index,
+                          QuicStringPiece expected_name,
+                          QuicStringPiece expected_value) const {
+    const auto* entry = table_.LookupEntry(is_static, index);
+    ASSERT_TRUE(entry);
+    EXPECT_EQ(expected_name, entry->name());
+    EXPECT_EQ(expected_value, entry->value());
+  }
+
+  void ExpectNoEntryAtIndex(bool is_static, uint64_t index) const {
+    EXPECT_FALSE(table_.LookupEntry(is_static, index));
+  }
+
+  void ExpectMatch(QuicStringPiece name,
+                   QuicStringPiece value,
+                   QpackHeaderTable::MatchType expected_match_type,
+                   bool expected_is_static,
+                   uint64_t expected_index) const {
+    // Initialize outparams to a value different from the expected to ensure
+    // that FindHeaderField() sets them.
+    bool is_static = !expected_is_static;
+    uint64_t index = expected_index + 1;
+
+    QpackHeaderTable::MatchType matchtype =
+        table_.FindHeaderField(name, value, &is_static, &index);
+
+    EXPECT_EQ(expected_match_type, matchtype) << name << ": " << value;
+    EXPECT_EQ(expected_is_static, is_static) << name << ": " << value;
+    EXPECT_EQ(expected_index, index) << name << ": " << value;
+  }
+
+  void ExpectNoMatch(QuicStringPiece name, QuicStringPiece value) const {
+    bool is_static = false;
+    uint64_t index = 0;
+
+    QpackHeaderTable::MatchType matchtype =
+        table_.FindHeaderField(name, value, &is_static, &index);
+
+    EXPECT_EQ(QpackHeaderTable::MatchType::kNoMatch, matchtype)
+        << name << ": " << value;
+  }
+
+  void InsertEntry(QuicStringPiece name, QuicStringPiece value) {
+    EXPECT_TRUE(table_.InsertEntry(name, value));
+  }
+
+  void ExpectToFailInsertingEntry(QuicStringPiece name, QuicStringPiece value) {
+    EXPECT_FALSE(table_.InsertEntry(name, value));
+  }
+
+  bool SetDynamicTableCapacity(uint64_t capacity) {
+    return table_.SetDynamicTableCapacity(capacity);
+  }
+
+  uint64_t max_entries() const { return table_.max_entries(); }
+  uint64_t inserted_entry_count() const {
+    return table_.inserted_entry_count();
+  }
+  uint64_t dropped_entry_count() const { return table_.dropped_entry_count(); }
+
+ private:
+  QpackHeaderTable table_;
+};
+
+TEST_F(QpackHeaderTableTest, LookupStaticEntry) {
+  ExpectEntryAtIndex(/* is_static = */ true, 0, ":authority", "");
+
+  ExpectEntryAtIndex(/* is_static = */ true, 1, ":path", "/");
+
+  // 98 is the last entry.
+  ExpectEntryAtIndex(/* is_static = */ true, 98, "x-frame-options",
+                     "sameorigin");
+
+  ASSERT_EQ(99u, QpackStaticTableVector().size());
+  ExpectNoEntryAtIndex(/* is_static = */ true, 99);
+}
+
+TEST_F(QpackHeaderTableTest, InsertAndLookupDynamicEntry) {
+  // Dynamic table is initially entry.
+  ExpectNoEntryAtIndex(/* is_static = */ false, 0);
+  ExpectNoEntryAtIndex(/* is_static = */ false, 1);
+  ExpectNoEntryAtIndex(/* is_static = */ false, 2);
+  ExpectNoEntryAtIndex(/* is_static = */ false, 3);
+
+  // Insert one entry.
+  InsertEntry("foo", "bar");
+
+  ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar");
+
+  ExpectNoEntryAtIndex(/* is_static = */ false, 1);
+  ExpectNoEntryAtIndex(/* is_static = */ false, 2);
+  ExpectNoEntryAtIndex(/* is_static = */ false, 3);
+
+  // Insert a different entry.
+  InsertEntry("baz", "bing");
+
+  ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar");
+
+  ExpectEntryAtIndex(/* is_static = */ false, 1, "baz", "bing");
+
+  ExpectNoEntryAtIndex(/* is_static = */ false, 2);
+  ExpectNoEntryAtIndex(/* is_static = */ false, 3);
+
+  // Insert an entry identical to the most recently inserted one.
+  InsertEntry("baz", "bing");
+
+  ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar");
+
+  ExpectEntryAtIndex(/* is_static = */ false, 1, "baz", "bing");
+
+  ExpectEntryAtIndex(/* is_static = */ false, 2, "baz", "bing");
+
+  ExpectNoEntryAtIndex(/* is_static = */ false, 3);
+}
+
+TEST_F(QpackHeaderTableTest, FindStaticHeaderField) {
+  // A header name that has multiple entries with different values.
+  ExpectMatch(":method", "GET", QpackHeaderTable::MatchType::kNameAndValue,
+              true, 17u);
+
+  ExpectMatch(":method", "POST", QpackHeaderTable::MatchType::kNameAndValue,
+              true, 20u);
+
+  ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kName, true,
+              15u);
+
+  // A header name that has a single entry with non-empty value.
+  ExpectMatch("accept-encoding", "gzip, deflate, br",
+              QpackHeaderTable::MatchType::kNameAndValue, true, 31u);
+
+  ExpectMatch("accept-encoding", "compress", QpackHeaderTable::MatchType::kName,
+              true, 31u);
+
+  ExpectMatch("accept-encoding", "", QpackHeaderTable::MatchType::kName, true,
+              31u);
+
+  // A header name that has a single entry with empty value.
+  ExpectMatch("location", "", QpackHeaderTable::MatchType::kNameAndValue, true,
+              12u);
+
+  ExpectMatch("location", "foo", QpackHeaderTable::MatchType::kName, true, 12u);
+
+  // No matching header name.
+  ExpectNoMatch("foo", "");
+  ExpectNoMatch("foo", "bar");
+}
+
+TEST_F(QpackHeaderTableTest, FindDynamicHeaderField) {
+  // Dynamic table is initially entry.
+  ExpectNoMatch("foo", "bar");
+  ExpectNoMatch("foo", "baz");
+
+  // Insert one entry.
+  InsertEntry("foo", "bar");
+
+  // Match name and value.
+  ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, false,
+              0u);
+
+  // Match name only.
+  ExpectMatch("foo", "baz", QpackHeaderTable::MatchType::kName, false, 0u);
+
+  // Insert an identical entry.  FindHeaderField() should return the index of
+  // the most recently inserted matching entry.
+  InsertEntry("foo", "bar");
+
+  // Match name and value.
+  ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, false,
+              1u);
+
+  // Match name only.
+  ExpectMatch("foo", "baz", QpackHeaderTable::MatchType::kName, false, 1u);
+}
+
+TEST_F(QpackHeaderTableTest, FindHeaderFieldPrefersStaticTable) {
+  // Insert an entry to the dynamic table that exists in the static table.
+  InsertEntry(":method", "GET");
+
+  // Insertion works.
+  ExpectEntryAtIndex(/* is_static = */ false, 0, ":method", "GET");
+
+  // FindHeaderField() prefers static table if both have name-and-value match.
+  ExpectMatch(":method", "GET", QpackHeaderTable::MatchType::kNameAndValue,
+              true, 17u);
+
+  // FindHeaderField() prefers static table if both have name match but no value
+  // match, and prefers the first entry with matching name.
+  ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kName, true,
+              15u);
+
+  // Add new entry to the dynamic table.
+  InsertEntry(":method", "TRACE");
+
+  // FindHeaderField prefers name-and-value match in dynamic table over name
+  // only match in static table.
+  ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kNameAndValue,
+              false, 1u);
+}
+
+// MaxEntries is determined by maximum dynamic table capacity,
+// which is set at construction time.
+TEST_F(QpackHeaderTableTest, MaxEntries) {
+  QpackHeaderTable table1;
+  table1.SetMaximumDynamicTableCapacity(1024);
+  EXPECT_EQ(32u, table1.max_entries());
+
+  QpackHeaderTable table2;
+  table2.SetMaximumDynamicTableCapacity(500);
+  EXPECT_EQ(15u, table2.max_entries());
+}
+
+TEST_F(QpackHeaderTableTest, SetDynamicTableCapacity) {
+  // Dynamic table capacity does not affect MaxEntries.
+  EXPECT_TRUE(SetDynamicTableCapacity(1024));
+  EXPECT_EQ(32u * 1024, max_entries());
+
+  EXPECT_TRUE(SetDynamicTableCapacity(500));
+  EXPECT_EQ(32u * 1024, max_entries());
+
+  // Dynamic table capacity cannot exceed maximum dynamic table capacity.
+  EXPECT_FALSE(
+      SetDynamicTableCapacity(2 * kMaximumDynamicTableCapacityForTesting));
+}
+
+TEST_F(QpackHeaderTableTest, EvictByInsertion) {
+  EXPECT_TRUE(SetDynamicTableCapacity(40));
+
+  // Entry size is 3 + 3 + 32 = 38.
+  InsertEntry("foo", "bar");
+  EXPECT_EQ(1u, inserted_entry_count());
+  EXPECT_EQ(0u, dropped_entry_count());
+
+  ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
+              /* expected_is_static = */ false, 0u);
+
+  // Inserting second entry evicts the first one.
+  InsertEntry("baz", "qux");
+  EXPECT_EQ(2u, inserted_entry_count());
+  EXPECT_EQ(1u, dropped_entry_count());
+
+  ExpectNoMatch("foo", "bar");
+  ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
+              /* expected_is_static = */ false, 1u);
+
+  // Inserting an entry that does not fit results in error.
+  ExpectToFailInsertingEntry("foobar", "foobar");
+}
+
+TEST_F(QpackHeaderTableTest, EvictByUpdateTableSize) {
+  // Entry size is 3 + 3 + 32 = 38.
+  InsertEntry("foo", "bar");
+  InsertEntry("baz", "qux");
+  EXPECT_EQ(2u, inserted_entry_count());
+  EXPECT_EQ(0u, dropped_entry_count());
+
+  ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
+              /* expected_is_static = */ false, 0u);
+  ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
+              /* expected_is_static = */ false, 1u);
+
+  EXPECT_TRUE(SetDynamicTableCapacity(40));
+  EXPECT_EQ(2u, inserted_entry_count());
+  EXPECT_EQ(1u, dropped_entry_count());
+
+  ExpectNoMatch("foo", "bar");
+  ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
+              /* expected_is_static = */ false, 1u);
+
+  EXPECT_TRUE(SetDynamicTableCapacity(20));
+  EXPECT_EQ(2u, inserted_entry_count());
+  EXPECT_EQ(2u, dropped_entry_count());
+
+  ExpectNoMatch("foo", "bar");
+  ExpectNoMatch("baz", "qux");
+}
+
+TEST_F(QpackHeaderTableTest, EvictOldestOfIdentical) {
+  EXPECT_TRUE(SetDynamicTableCapacity(80));
+
+  // Entry size is 3 + 3 + 32 = 38.
+  // Insert same entry twice.
+  InsertEntry("foo", "bar");
+  InsertEntry("foo", "bar");
+  EXPECT_EQ(2u, inserted_entry_count());
+  EXPECT_EQ(0u, dropped_entry_count());
+
+  // Find most recently inserted entry.
+  ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
+              /* expected_is_static = */ false, 1u);
+
+  // Inserting third entry evicts the first one, not the second.
+  InsertEntry("baz", "qux");
+  EXPECT_EQ(3u, inserted_entry_count());
+  EXPECT_EQ(1u, dropped_entry_count());
+
+  ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
+              /* expected_is_static = */ false, 1u);
+  ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
+              /* expected_is_static = */ false, 2u);
+}
+
+TEST_F(QpackHeaderTableTest, EvictOldestOfSameName) {
+  EXPECT_TRUE(SetDynamicTableCapacity(80));
+
+  // Entry size is 3 + 3 + 32 = 38.
+  // Insert two entries with same name but different values.
+  InsertEntry("foo", "bar");
+  InsertEntry("foo", "baz");
+  EXPECT_EQ(2u, inserted_entry_count());
+  EXPECT_EQ(0u, dropped_entry_count());
+
+  // Find most recently inserted entry with matching name.
+  ExpectMatch("foo", "foo", QpackHeaderTable::MatchType::kName,
+              /* expected_is_static = */ false, 1u);
+
+  // Inserting third entry evicts the first one, not the second.
+  InsertEntry("baz", "qux");
+  EXPECT_EQ(3u, inserted_entry_count());
+  EXPECT_EQ(1u, dropped_entry_count());
+
+  ExpectMatch("foo", "foo", QpackHeaderTable::MatchType::kName,
+              /* expected_is_static = */ false, 1u);
+  ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
+              /* expected_is_static = */ false, 2u);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_instruction_decoder.cc b/quic/core/qpack/qpack_instruction_decoder.cc
new file mode 100644
index 0000000..dbb2076
--- /dev/null
+++ b/quic/core/qpack/qpack_instruction_decoder.cc
@@ -0,0 +1,310 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/logging.h"
+
+namespace quic {
+
+namespace {
+
+// Maximum length of header name and header value.  This limits the amount of
+// memory the peer can make the decoder allocate when sending string literals.
+const size_t kStringLiteralLengthLimit = 1024 * 1024;
+
+}  // namespace
+
+QpackInstructionDecoder::QpackInstructionDecoder(const QpackLanguage* language,
+                                                 Delegate* delegate)
+    : language_(language),
+      delegate_(delegate),
+      s_bit_(false),
+      varint_(0),
+      varint2_(0),
+      is_huffman_encoded_(false),
+      string_length_(0),
+      error_detected_(false),
+      state_(State::kStartInstruction) {}
+
+void QpackInstructionDecoder::Decode(QuicStringPiece data) {
+  DCHECK(!data.empty());
+  DCHECK(!error_detected_);
+
+  while (true) {
+    size_t bytes_consumed = 0;
+
+    switch (state_) {
+      case State::kStartInstruction:
+        DoStartInstruction(data);
+        break;
+      case State::kStartField:
+        DoStartField();
+        break;
+      case State::kReadBit:
+        DoReadBit(data);
+        break;
+      case State::kVarintStart:
+        bytes_consumed = DoVarintStart(data);
+        break;
+      case State::kVarintResume:
+        bytes_consumed = DoVarintResume(data);
+        break;
+      case State::kVarintDone:
+        DoVarintDone();
+        break;
+      case State::kReadString:
+        bytes_consumed = DoReadString(data);
+        break;
+      case State::kReadStringDone:
+        DoReadStringDone();
+        break;
+    }
+
+    if (error_detected_) {
+      return;
+    }
+
+    DCHECK_LE(bytes_consumed, data.size());
+
+    data = QuicStringPiece(data.data() + bytes_consumed,
+                           data.size() - bytes_consumed);
+
+    // Stop processing if no more data but next state would require it.
+    if (data.empty() && (state_ != State::kStartField) &&
+        (state_ != State::kVarintDone) && (state_ != State::kReadStringDone)) {
+      return;
+    }
+  }
+}
+
+bool QpackInstructionDecoder::AtInstructionBoundary() const {
+  return state_ == State::kStartInstruction;
+}
+
+void QpackInstructionDecoder::DoStartInstruction(QuicStringPiece data) {
+  DCHECK(!data.empty());
+
+  instruction_ = LookupOpcode(data[0]);
+  field_ = instruction_->fields.begin();
+
+  state_ = State::kStartField;
+}
+
+void QpackInstructionDecoder::DoStartField() {
+  if (field_ == instruction_->fields.end()) {
+    // Completed decoding this instruction.
+
+    if (!delegate_->OnInstructionDecoded(instruction_)) {
+      error_detected_ = true;
+      return;
+    }
+
+    state_ = State::kStartInstruction;
+    return;
+  }
+
+  switch (field_->type) {
+    case QpackInstructionFieldType::kSbit:
+    case QpackInstructionFieldType::kName:
+    case QpackInstructionFieldType::kValue:
+      state_ = State::kReadBit;
+      return;
+    case QpackInstructionFieldType::kVarint:
+    case QpackInstructionFieldType::kVarint2:
+      state_ = State::kVarintStart;
+      return;
+  }
+}
+
+void QpackInstructionDecoder::DoReadBit(QuicStringPiece data) {
+  DCHECK(!data.empty());
+
+  switch (field_->type) {
+    case QpackInstructionFieldType::kSbit: {
+      const uint8_t bitmask = field_->param;
+      s_bit_ = (data[0] & bitmask) == bitmask;
+
+      ++field_;
+      state_ = State::kStartField;
+
+      return;
+    }
+    case QpackInstructionFieldType::kName:
+    case QpackInstructionFieldType::kValue: {
+      const uint8_t prefix_length = field_->param;
+      DCHECK_GE(7, prefix_length);
+      const uint8_t bitmask = 1 << prefix_length;
+      is_huffman_encoded_ = (data[0] & bitmask) == bitmask;
+
+      state_ = State::kVarintStart;
+
+      return;
+    }
+    default:
+      DCHECK(false);
+  }
+}
+
+size_t QpackInstructionDecoder::DoVarintStart(QuicStringPiece data) {
+  DCHECK(!data.empty());
+  DCHECK(field_->type == QpackInstructionFieldType::kVarint ||
+         field_->type == QpackInstructionFieldType::kVarint2 ||
+         field_->type == QpackInstructionFieldType::kName ||
+         field_->type == QpackInstructionFieldType::kValue);
+
+  http2::DecodeBuffer buffer(data.data() + 1, data.size() - 1);
+  http2::DecodeStatus status =
+      varint_decoder_.Start(data[0], field_->param, &buffer);
+
+  size_t bytes_consumed = 1 + buffer.Offset();
+  switch (status) {
+    case http2::DecodeStatus::kDecodeDone:
+      state_ = State::kVarintDone;
+      return bytes_consumed;
+    case http2::DecodeStatus::kDecodeInProgress:
+      state_ = State::kVarintResume;
+      return bytes_consumed;
+    case http2::DecodeStatus::kDecodeError:
+      OnError("Encoded integer too large.");
+      return bytes_consumed;
+  }
+}
+
+size_t QpackInstructionDecoder::DoVarintResume(QuicStringPiece data) {
+  DCHECK(!data.empty());
+  DCHECK(field_->type == QpackInstructionFieldType::kVarint ||
+         field_->type == QpackInstructionFieldType::kVarint2 ||
+         field_->type == QpackInstructionFieldType::kName ||
+         field_->type == QpackInstructionFieldType::kValue);
+
+  http2::DecodeBuffer buffer(data);
+  http2::DecodeStatus status = varint_decoder_.Resume(&buffer);
+
+  size_t bytes_consumed = buffer.Offset();
+  switch (status) {
+    case http2::DecodeStatus::kDecodeDone:
+      state_ = State::kVarintDone;
+      return bytes_consumed;
+    case http2::DecodeStatus::kDecodeInProgress:
+      DCHECK_EQ(bytes_consumed, data.size());
+      DCHECK(buffer.Empty());
+      return bytes_consumed;
+    case http2::DecodeStatus::kDecodeError:
+      OnError("Encoded integer too large.");
+      return bytes_consumed;
+  }
+}
+
+void QpackInstructionDecoder::DoVarintDone() {
+  DCHECK(field_->type == QpackInstructionFieldType::kVarint ||
+         field_->type == QpackInstructionFieldType::kVarint2 ||
+         field_->type == QpackInstructionFieldType::kName ||
+         field_->type == QpackInstructionFieldType::kValue);
+
+  if (field_->type == QpackInstructionFieldType::kVarint) {
+    varint_ = varint_decoder_.value();
+
+    ++field_;
+    state_ = State::kStartField;
+    return;
+  }
+
+  if (field_->type == QpackInstructionFieldType::kVarint2) {
+    varint2_ = varint_decoder_.value();
+
+    ++field_;
+    state_ = State::kStartField;
+    return;
+  }
+
+  string_length_ = varint_decoder_.value();
+  if (string_length_ > kStringLiteralLengthLimit) {
+    OnError("String literal too long.");
+    return;
+  }
+
+  QuicString* const string =
+      (field_->type == QpackInstructionFieldType::kName) ? &name_ : &value_;
+  string->clear();
+
+  if (string_length_ == 0) {
+    ++field_;
+    state_ = State::kStartField;
+    return;
+  }
+
+  string->reserve(string_length_);
+
+  state_ = State::kReadString;
+}
+
+size_t QpackInstructionDecoder::DoReadString(QuicStringPiece data) {
+  DCHECK(!data.empty());
+  DCHECK(field_->type == QpackInstructionFieldType::kName ||
+         field_->type == QpackInstructionFieldType::kValue);
+
+  QuicString* const string =
+      (field_->type == QpackInstructionFieldType::kName) ? &name_ : &value_;
+  DCHECK_LT(string->size(), string_length_);
+
+  size_t bytes_consumed =
+      std::min(string_length_ - string->size(), data.size());
+  string->append(data.data(), bytes_consumed);
+
+  DCHECK_LE(string->size(), string_length_);
+  if (string->size() == string_length_) {
+    state_ = State::kReadStringDone;
+  }
+  return bytes_consumed;
+}
+
+void QpackInstructionDecoder::DoReadStringDone() {
+  DCHECK(field_->type == QpackInstructionFieldType::kName ||
+         field_->type == QpackInstructionFieldType::kValue);
+
+  QuicString* const string =
+      (field_->type == QpackInstructionFieldType::kName) ? &name_ : &value_;
+  DCHECK_EQ(string->size(), string_length_);
+
+  if (is_huffman_encoded_) {
+    huffman_decoder_.Reset();
+    // HpackHuffmanDecoder::Decode() cannot perform in-place decoding.
+    QuicString decoded_value;
+    huffman_decoder_.Decode(*string, &decoded_value);
+    if (!huffman_decoder_.InputProperlyTerminated()) {
+      OnError("Error in Huffman-encoded string.");
+      return;
+    }
+    *string = std::move(decoded_value);
+  }
+
+  ++field_;
+  state_ = State::kStartField;
+}
+
+const QpackInstruction* QpackInstructionDecoder::LookupOpcode(
+    uint8_t byte) const {
+  for (const auto* instruction : *language_) {
+    if ((byte & instruction->opcode.mask) == instruction->opcode.value) {
+      return instruction;
+    }
+  }
+  // |language_| should be defined such that instruction opcodes cover every
+  // possible input.
+  DCHECK(false);
+  return nullptr;
+}
+
+void QpackInstructionDecoder::OnError(QuicStringPiece error_message) {
+  DCHECK(!error_detected_);
+
+  error_detected_ = true;
+  delegate_->OnError(error_message);
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_instruction_decoder.h b/quic/core/qpack/qpack_instruction_decoder.h
new file mode 100644
index 0000000..9d674c3
--- /dev/null
+++ b/quic/core/qpack/qpack_instruction_decoder.h
@@ -0,0 +1,146 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_INSTRUCTION_DECODER_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_INSTRUCTION_DECODER_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_constants.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+// Generic instruction decoder class.  Takes a QpackLanguage that describes a
+// language, that is, a set of instruction opcodes together with a list of
+// fields that follow each instruction.
+class QUIC_EXPORT_PRIVATE QpackInstructionDecoder {
+ public:
+  // Delegate is notified each time an instruction is decoded or when an error
+  // occurs.
+  class QUIC_EXPORT_PRIVATE Delegate {
+   public:
+    virtual ~Delegate() = default;
+
+    // Called when an instruction (including all its fields) is decoded.
+    // |instruction| points to an entry in |language|.
+    // Returns true if decoded fields are valid.
+    // Returns false otherwise, in which case QpackInstructionDecoder stops
+    // decoding: Delegate methods will not be called, and Decode() must not be
+    // called.
+    virtual bool OnInstructionDecoded(const QpackInstruction* instruction) = 0;
+
+    // Called by QpackInstructionDecoder if an error has occurred.
+    // No more data is processed afterwards.
+    virtual void OnError(QuicStringPiece error_message) = 0;
+  };
+
+  // Both |*language| and |*delegate| must outlive this object.
+  QpackInstructionDecoder(const QpackLanguage* language, Delegate* delegate);
+  QpackInstructionDecoder() = delete;
+  QpackInstructionDecoder(const QpackInstructionDecoder&) = delete;
+  QpackInstructionDecoder& operator=(const QpackInstructionDecoder&) = delete;
+
+  // Provide a data fragment to decode.  Must not be called after an error has
+  // occurred.  Must not be called with empty |data|.
+  void Decode(QuicStringPiece data);
+
+  // Returns true if no decoding has taken place yet or if the last instruction
+  // has been entirely parsed.
+  bool AtInstructionBoundary() const;
+
+  // Accessors for decoded values.  Should only be called for fields that are
+  // part of the most recently decoded instruction, and only after |this| calls
+  // Delegate::OnInstructionDecoded() but before Decode() is called again.
+  bool s_bit() const { return s_bit_; }
+  uint64_t varint() const { return varint_; }
+  uint64_t varint2() const { return varint2_; }
+  const QuicString& name() const { return name_; }
+  const QuicString& value() const { return value_; }
+
+ private:
+  enum class State {
+    // Identify instruction.
+    kStartInstruction,
+    // Start decoding next field.
+    kStartField,
+    // Read a single bit.
+    kReadBit,
+    // Start reading integer.
+    kVarintStart,
+    // Resume reading integer.
+    kVarintResume,
+    // Done reading integer.
+    kVarintDone,
+    // Read string.
+    kReadString,
+    // Done reading string.
+    kReadStringDone
+  };
+
+  // One method for each state.  Some take input data and return the number of
+  // octets processed.  Some take input data but do have void return type
+  // because they not consume any bytes.  Some do not take any arguments because
+  // they only change internal state.
+  void DoStartInstruction(QuicStringPiece data);
+  void DoStartField();
+  void DoReadBit(QuicStringPiece data);
+  size_t DoVarintStart(QuicStringPiece data);
+  size_t DoVarintResume(QuicStringPiece data);
+  void DoVarintDone();
+  size_t DoReadString(QuicStringPiece data);
+  void DoReadStringDone();
+
+  // Identify instruction based on opcode encoded in |byte|.
+  // Returns a pointer to an element of |*language_|.
+  const QpackInstruction* LookupOpcode(uint8_t byte) const;
+
+  // Stops decoding and calls Delegate::OnError().
+  void OnError(QuicStringPiece error_message);
+
+  // Describes the language used for decoding.
+  const QpackLanguage* const language_;
+
+  // The Delegate to notify of decoded instructions and errors.
+  Delegate* const delegate_;
+
+  // Storage for decoded field values.
+  bool s_bit_;
+  uint64_t varint_;
+  uint64_t varint2_;
+  QuicString name_;
+  QuicString value_;
+  // Whether the currently decoded header name or value is Huffman encoded.
+  bool is_huffman_encoded_;
+  // Length of string being read into |name_| or |value_|.
+  size_t string_length_;
+
+  // Decoder instance for decoding integers.
+  http2::HpackVarintDecoder varint_decoder_;
+
+  // Decoder instance for decoding Huffman encoded strings.
+  http2::HpackHuffmanDecoder huffman_decoder_;
+
+  // True if a decoding error has been detected either by
+  // QpackInstructionDecoder or by Delegate.
+  bool error_detected_;
+
+  // Decoding state.
+  State state_;
+
+  // Instruction currently being decoded.
+  const QpackInstruction* instruction_;
+
+  // Field currently being decoded.
+  QpackInstructionFields::const_iterator field_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_INSTRUCTION_DECODER_H_
diff --git a/quic/core/qpack/qpack_instruction_decoder_test.cc b/quic/core/qpack/qpack_instruction_decoder_test.cc
new file mode 100644
index 0000000..08557ec
--- /dev/null
+++ b/quic/core/qpack/qpack_instruction_decoder_test.cc
@@ -0,0 +1,171 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_constants.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::Expectation;
+using ::testing::Return;
+using ::testing::StrictMock;
+using ::testing::Values;
+
+namespace quic {
+namespace test {
+namespace {
+
+// This instruction has three fields: an S bit and two varints.
+const QpackInstruction* TestInstruction1() {
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{QpackInstructionOpcode{0x00, 0x80},
+                           {{QpackInstructionFieldType::kSbit, 0x40},
+                            {QpackInstructionFieldType::kVarint, 6},
+                            {QpackInstructionFieldType::kVarint2, 8}}};
+  return instruction;
+}
+
+// This instruction has two fields: a header name with a 6-bit prefix, and a
+// header value with a 7-bit prefix, both preceded by a Huffman bit.
+const QpackInstruction* TestInstruction2() {
+  static const QpackInstruction* const instruction =
+      new QpackInstruction{QpackInstructionOpcode{0x80, 0x80},
+                           {{QpackInstructionFieldType::kName, 6},
+                            {QpackInstructionFieldType::kValue, 7}}};
+  return instruction;
+}
+
+const QpackLanguage* TestLanguage() {
+  static const QpackLanguage* const language =
+      new QpackLanguage{TestInstruction1(), TestInstruction2()};
+  return language;
+}
+
+class MockDelegate : public QpackInstructionDecoder::Delegate {
+ public:
+  MockDelegate() {
+    ON_CALL(*this, OnInstructionDecoded(_)).WillByDefault(Return(true));
+  }
+
+  MockDelegate(const MockDelegate&) = delete;
+  MockDelegate& operator=(const MockDelegate&) = delete;
+  ~MockDelegate() override = default;
+
+  MOCK_METHOD1(OnInstructionDecoded, bool(const QpackInstruction* instruction));
+  MOCK_METHOD1(OnError, void(QuicStringPiece error_message));
+};
+
+class QpackInstructionDecoderTest : public QuicTestWithParam<FragmentMode> {
+ public:
+  QpackInstructionDecoderTest()
+      : decoder_(TestLanguage(), &delegate_), fragment_mode_(GetParam()) {}
+  ~QpackInstructionDecoderTest() override = default;
+
+ protected:
+  // Decode one full instruction with fragment sizes dictated by
+  // |fragment_mode_|.
+  // Verifies that AtInstructionBoundary() returns true before and after the
+  // instruction, and returns false while decoding is in progress.
+  void DecodeInstruction(QuicStringPiece data) {
+    EXPECT_TRUE(decoder_.AtInstructionBoundary());
+
+    FragmentSizeGenerator fragment_size_generator =
+        FragmentModeToFragmentSizeGenerator(fragment_mode_);
+
+    while (!data.empty()) {
+      size_t fragment_size = std::min(fragment_size_generator(), data.size());
+      decoder_.Decode(data.substr(0, fragment_size));
+      data = data.substr(fragment_size);
+      if (!data.empty()) {
+        EXPECT_FALSE(decoder_.AtInstructionBoundary());
+      }
+    }
+
+    EXPECT_TRUE(decoder_.AtInstructionBoundary());
+  }
+
+  StrictMock<MockDelegate> delegate_;
+  QpackInstructionDecoder decoder_;
+
+ private:
+  const FragmentMode fragment_mode_;
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         QpackInstructionDecoderTest,
+                         Values(FragmentMode::kSingleChunk,
+                                FragmentMode::kOctetByOctet));
+
+TEST_P(QpackInstructionDecoderTest, SBitAndVarint2) {
+  EXPECT_CALL(delegate_, OnInstructionDecoded(TestInstruction1()));
+  DecodeInstruction(QuicTextUtils::HexDecode("7f01ff65"));
+
+  EXPECT_TRUE(decoder_.s_bit());
+  EXPECT_EQ(64u, decoder_.varint());
+  EXPECT_EQ(356u, decoder_.varint2());
+
+  EXPECT_CALL(delegate_, OnInstructionDecoded(TestInstruction1()));
+  DecodeInstruction(QuicTextUtils::HexDecode("05c8"));
+
+  EXPECT_FALSE(decoder_.s_bit());
+  EXPECT_EQ(5u, decoder_.varint());
+  EXPECT_EQ(200u, decoder_.varint2());
+}
+
+TEST_P(QpackInstructionDecoderTest, NameAndValue) {
+  EXPECT_CALL(delegate_, OnInstructionDecoded(TestInstruction2()));
+  DecodeInstruction(QuicTextUtils::HexDecode("83666f6f03626172"));
+
+  EXPECT_EQ("foo", decoder_.name());
+  EXPECT_EQ("bar", decoder_.value());
+
+  EXPECT_CALL(delegate_, OnInstructionDecoded(TestInstruction2()));
+  DecodeInstruction(QuicTextUtils::HexDecode("8000"));
+
+  EXPECT_EQ("", decoder_.name());
+  EXPECT_EQ("", decoder_.value());
+
+  EXPECT_CALL(delegate_, OnInstructionDecoded(TestInstruction2()));
+  DecodeInstruction(QuicTextUtils::HexDecode("c294e7838c767f"));
+
+  EXPECT_EQ("foo", decoder_.name());
+  EXPECT_EQ("bar", decoder_.value());
+}
+
+TEST_P(QpackInstructionDecoderTest, InvalidHuffmanEncoding) {
+  EXPECT_CALL(delegate_, OnError(Eq("Error in Huffman-encoded string.")));
+  decoder_.Decode(QuicTextUtils::HexDecode("c1ff"));
+}
+
+TEST_P(QpackInstructionDecoderTest, InvalidVarintEncoding) {
+  EXPECT_CALL(delegate_, OnError(Eq("Encoded integer too large.")));
+  decoder_.Decode(QuicTextUtils::HexDecode("ffffffffffffffffffffff"));
+}
+
+TEST_P(QpackInstructionDecoderTest, DelegateSignalsError) {
+  // First instruction is valid.
+  Expectation first_call =
+      EXPECT_CALL(delegate_, OnInstructionDecoded(TestInstruction1()))
+          .WillOnce(Return(true));
+  // Second instruction is invalid.  Decoding must halt.
+  EXPECT_CALL(delegate_, OnInstructionDecoded(TestInstruction1()))
+      .After(first_call)
+      .WillOnce(Return(false));
+  decoder_.Decode(QuicTextUtils::HexDecode("01000200030004000500"));
+
+  EXPECT_EQ(2u, decoder_.varint());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_instruction_encoder.cc b/quic/core/qpack/qpack_instruction_encoder.cc
new file mode 100644
index 0000000..72aa7fd
--- /dev/null
+++ b/quic/core/qpack/qpack_instruction_encoder.cc
@@ -0,0 +1,217 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_utils.h"
+
+namespace quic {
+
+QpackInstructionEncoder::QpackInstructionEncoder()
+    : s_bit_(false),
+      varint_(0),
+      varint2_(0),
+      byte_(0),
+      state_(State::kOpcode),
+      instruction_(nullptr) {}
+
+void QpackInstructionEncoder::Encode(const QpackInstruction* instruction) {
+  DCHECK(!HasNext());
+
+  state_ = State::kOpcode;
+  instruction_ = instruction;
+  field_ = instruction_->fields.begin();
+
+  // Field list must not be empty.
+  DCHECK(field_ != instruction_->fields.end());
+}
+
+bool QpackInstructionEncoder::HasNext() const {
+  return instruction_ && (field_ != instruction_->fields.end());
+}
+
+void QpackInstructionEncoder::Next(size_t max_encoded_bytes,
+                                   QuicString* output) {
+  DCHECK(HasNext());
+  DCHECK_NE(0u, max_encoded_bytes);
+
+  while (max_encoded_bytes > 0 && HasNext()) {
+    size_t encoded_bytes = 0;
+
+    switch (state_) {
+      case State::kOpcode:
+        DoOpcode();
+        break;
+      case State::kStartField:
+        DoStartField();
+        break;
+      case State::kSbit:
+        DoStaticBit();
+        break;
+      case State::kVarintStart:
+        encoded_bytes = DoVarintStart(max_encoded_bytes, output);
+        break;
+      case State::kVarintResume:
+        encoded_bytes = DoVarintResume(max_encoded_bytes, output);
+        break;
+      case State::kStartString:
+        DoStartString();
+        break;
+      case State::kWriteString:
+        encoded_bytes = DoWriteString(max_encoded_bytes, output);
+        break;
+    }
+
+    DCHECK_LE(encoded_bytes, max_encoded_bytes);
+    max_encoded_bytes -= encoded_bytes;
+  }
+}
+
+void QpackInstructionEncoder::DoOpcode() {
+  DCHECK_EQ(0u, byte_);
+
+  byte_ = instruction_->opcode.value;
+
+  state_ = State::kStartField;
+}
+
+void QpackInstructionEncoder::DoStartField() {
+  switch (field_->type) {
+    case QpackInstructionFieldType::kSbit:
+      state_ = State::kSbit;
+      return;
+    case QpackInstructionFieldType::kVarint:
+    case QpackInstructionFieldType::kVarint2:
+      state_ = State::kVarintStart;
+      return;
+    case QpackInstructionFieldType::kName:
+    case QpackInstructionFieldType::kValue:
+      state_ = State::kStartString;
+      return;
+  }
+}
+
+void QpackInstructionEncoder::DoStaticBit() {
+  DCHECK(field_->type == QpackInstructionFieldType::kSbit);
+
+  if (s_bit_) {
+    DCHECK_EQ(0, byte_ & field_->param);
+
+    byte_ |= field_->param;
+  }
+
+  ++field_;
+  state_ = State::kStartField;
+}
+
+size_t QpackInstructionEncoder::DoVarintStart(size_t max_encoded_bytes,
+                                              QuicString* output) {
+  DCHECK(field_->type == QpackInstructionFieldType::kVarint ||
+         field_->type == QpackInstructionFieldType::kVarint2 ||
+         field_->type == QpackInstructionFieldType::kName ||
+         field_->type == QpackInstructionFieldType::kValue);
+  DCHECK(!varint_encoder_.IsEncodingInProgress());
+
+  uint64_t integer_to_encode;
+  switch (field_->type) {
+    case QpackInstructionFieldType::kVarint:
+      integer_to_encode = varint_;
+      break;
+    case QpackInstructionFieldType::kVarint2:
+      integer_to_encode = varint2_;
+      break;
+    default:
+      integer_to_encode = string_to_write_.size();
+      break;
+  }
+
+  output->push_back(
+      varint_encoder_.StartEncoding(byte_, field_->param, integer_to_encode));
+  byte_ = 0;
+
+  if (varint_encoder_.IsEncodingInProgress()) {
+    state_ = State::kVarintResume;
+    return 1;
+  }
+
+  if (field_->type == QpackInstructionFieldType::kVarint ||
+      field_->type == QpackInstructionFieldType::kVarint2) {
+    ++field_;
+    state_ = State::kStartField;
+    return 1;
+  }
+
+  state_ = State::kWriteString;
+  return 1;
+}
+
+size_t QpackInstructionEncoder::DoVarintResume(size_t max_encoded_bytes,
+                                               QuicString* output) {
+  DCHECK(field_->type == QpackInstructionFieldType::kVarint ||
+         field_->type == QpackInstructionFieldType::kVarint2 ||
+         field_->type == QpackInstructionFieldType::kName ||
+         field_->type == QpackInstructionFieldType::kValue);
+  DCHECK(varint_encoder_.IsEncodingInProgress());
+
+  const size_t encoded_bytes =
+      varint_encoder_.ResumeEncoding(max_encoded_bytes, output);
+  if (varint_encoder_.IsEncodingInProgress()) {
+    DCHECK_EQ(encoded_bytes, max_encoded_bytes);
+    return encoded_bytes;
+  }
+
+  DCHECK_LE(encoded_bytes, max_encoded_bytes);
+
+  if (field_->type == QpackInstructionFieldType::kVarint ||
+      field_->type == QpackInstructionFieldType::kVarint2) {
+    ++field_;
+    state_ = State::kStartField;
+    return encoded_bytes;
+  }
+
+  state_ = State::kWriteString;
+  return encoded_bytes;
+}
+
+void QpackInstructionEncoder::DoStartString() {
+  DCHECK(field_->type == QpackInstructionFieldType::kName ||
+         field_->type == QpackInstructionFieldType::kValue);
+
+  string_to_write_ =
+      (field_->type == QpackInstructionFieldType::kName) ? name_ : value_;
+  http2::HuffmanEncode(string_to_write_, &huffman_encoded_string_);
+
+  if (huffman_encoded_string_.size() < string_to_write_.size()) {
+    DCHECK_EQ(0, byte_ & (1 << field_->param));
+
+    byte_ |= (1 << field_->param);
+    string_to_write_ = huffman_encoded_string_;
+  }
+
+  state_ = State::kVarintStart;
+}
+
+size_t QpackInstructionEncoder::DoWriteString(size_t max_encoded_bytes,
+                                              QuicString* output) {
+  DCHECK(field_->type == QpackInstructionFieldType::kName ||
+         field_->type == QpackInstructionFieldType::kValue);
+
+  if (max_encoded_bytes < string_to_write_.size()) {
+    const size_t encoded_bytes = max_encoded_bytes;
+    QuicStrAppend(output, string_to_write_.substr(0, encoded_bytes));
+    string_to_write_ = string_to_write_.substr(encoded_bytes);
+    return encoded_bytes;
+  }
+
+  const size_t encoded_bytes = string_to_write_.size();
+  QuicStrAppend(output, string_to_write_);
+
+  ++field_;
+  state_ = State::kStartField;
+  return encoded_bytes;
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_instruction_encoder.h b/quic/core/qpack/qpack_instruction_encoder.h
new file mode 100644
index 0000000..15d908a
--- /dev/null
+++ b/quic/core/qpack/qpack_instruction_encoder.h
@@ -0,0 +1,118 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_INSTRUCTION_ENCODER_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_INSTRUCTION_ENCODER_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_constants.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+// Generic instruction encoder class.  Takes a QpackLanguage that describes a
+// language, that is, a set of instruction opcodes together with a list of
+// fields that follow each instruction.
+class QUIC_EXPORT_PRIVATE QpackInstructionEncoder {
+ public:
+  QpackInstructionEncoder();
+  QpackInstructionEncoder(const QpackInstructionEncoder&) = delete;
+  QpackInstructionEncoder& operator=(const QpackInstructionEncoder&) = delete;
+
+  // Setters for values to be encoded.
+  // |name| and |value| must remain valid until the instruction is encoded.
+  void set_s_bit(bool s_bit) { s_bit_ = s_bit; }
+  void set_varint(uint64_t varint) { varint_ = varint; }
+  void set_varint2(uint64_t varint2) { varint2_ = varint2; }
+  void set_name(QuicStringPiece name) { name_ = name; }
+  void set_value(QuicStringPiece value) { value_ = value; }
+
+  // Start encoding an instruction.  Must only be called after the previous
+  // instruction has been completely encoded.
+  void Encode(const QpackInstruction* instruction);
+
+  // Returns true iff more data remains to be encoded for the current
+  // instruction.  Returns false if there is no current instruction, that is, if
+  // Encode() has never been called.
+  bool HasNext() const;
+
+  // Encodes the next up to |max_encoded_bytes| octets of the current
+  // instruction, appending to |output|.  Must only be called when HasNext()
+  // returns true.  |max_encoded_bytes| must be positive.
+  void Next(size_t max_encoded_bytes, QuicString* output);
+
+ private:
+  enum class State {
+    // Write instruction opcode to |byte_|.
+    kOpcode,
+    // Select state based on type of current field.
+    kStartField,
+    // Write static bit to |byte_|.
+    kSbit,
+    // Start encoding an integer (|varint_| or |varint2_| or string length) with
+    // a prefix, using |byte_| for the high bits.
+    kVarintStart,
+    // Resume encoding an integer.
+    kVarintResume,
+    // Determine if Huffman encoding should be used for |name_| or |value_|, set
+    // up |name_| or |value_| and |huffman_encoded_string_| accordingly, and
+    // write the Huffman bit to |byte_|.
+    kStartString,
+    // Write string.
+    kWriteString
+  };
+
+  // One method for each state.  Some encode up to |max_encoded_bytes| octets,
+  // appending to |output|.  Some only change internal state.
+  void DoOpcode();
+  void DoStartField();
+  void DoStaticBit();
+  size_t DoVarintStart(size_t max_encoded_bytes, QuicString* output);
+  size_t DoVarintResume(size_t max_encoded_bytes, QuicString* output);
+  void DoStartString();
+  size_t DoWriteString(size_t max_encoded_bytes, QuicString* output);
+
+  // Storage for field values to be encoded.
+  bool s_bit_;
+  uint64_t varint_;
+  uint64_t varint2_;
+  // The caller must keep the string that |name_| and |value_| point to
+  // valid until they are encoded.
+  QuicStringPiece name_;
+  QuicStringPiece value_;
+
+  // Storage for the Huffman encoded string literal to be written if Huffman
+  // encoding is used.
+  QuicString huffman_encoded_string_;
+
+  // If Huffman encoding is used, points to a substring of
+  // |huffman_encoded_string_|.
+  // Otherwise points to a substring of |name_| or |value_|.
+  QuicStringPiece string_to_write_;
+
+  // Storage for a single byte that contains multiple fields, that is, multiple
+  // states are writing it.
+  uint8_t byte_;
+
+  // Encoding state.
+  State state_;
+
+  // Instruction currently being decoded.
+  const QpackInstruction* instruction_;
+
+  // Field currently being decoded.
+  QpackInstructionFields::const_iterator field_;
+
+  // Decoder instance for decoding integers.
+  http2::HpackVarintEncoder varint_encoder_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_INSTRUCTION_ENCODER_H_
diff --git a/quic/core/qpack/qpack_instruction_encoder_test.cc b/quic/core/qpack/qpack_instruction_encoder_test.cc
new file mode 100644
index 0000000..006476f
--- /dev/null
+++ b/quic/core/qpack/qpack_instruction_encoder_test.cc
@@ -0,0 +1,152 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder.h"
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+using ::testing::Values;
+
+namespace quic {
+namespace test {
+namespace {
+
+class QpackInstructionEncoderTest : public QuicTestWithParam<FragmentMode> {
+ protected:
+  QpackInstructionEncoderTest() : fragment_mode_(GetParam()) {}
+  ~QpackInstructionEncoderTest() override = default;
+
+  // Encode |instruction| with fragment sizes dictated by |fragment_mode_|.
+  QuicString EncodeInstruction(const QpackInstruction* instruction) {
+    EXPECT_FALSE(encoder_.HasNext());
+
+    FragmentSizeGenerator fragment_size_generator =
+        FragmentModeToFragmentSizeGenerator(fragment_mode_);
+    QuicString output;
+    encoder_.Encode(instruction);
+    while (encoder_.HasNext()) {
+      encoder_.Next(fragment_size_generator(), &output);
+    }
+
+    return output;
+  }
+
+  QpackInstructionEncoder encoder_;
+
+ private:
+  const FragmentMode fragment_mode_;
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         QpackInstructionEncoderTest,
+                         Values(FragmentMode::kSingleChunk,
+                                FragmentMode::kOctetByOctet));
+
+TEST_P(QpackInstructionEncoderTest, Varint) {
+  const QpackInstruction instruction{QpackInstructionOpcode{0x00, 0x80},
+                                     {{QpackInstructionFieldType::kVarint, 7}}};
+
+  encoder_.set_varint(5);
+  EXPECT_EQ(QuicTextUtils::HexDecode("05"), EncodeInstruction(&instruction));
+
+  encoder_.set_varint(127);
+  EXPECT_EQ(QuicTextUtils::HexDecode("7f00"), EncodeInstruction(&instruction));
+}
+
+TEST_P(QpackInstructionEncoderTest, SBitAndTwoVarint2) {
+  const QpackInstruction instruction{
+      QpackInstructionOpcode{0x80, 0xc0},
+      {{QpackInstructionFieldType::kSbit, 0x20},
+       {QpackInstructionFieldType::kVarint, 5},
+       {QpackInstructionFieldType::kVarint2, 8}}};
+
+  encoder_.set_s_bit(true);
+  encoder_.set_varint(5);
+  encoder_.set_varint2(200);
+  EXPECT_EQ(QuicTextUtils::HexDecode("a5c8"), EncodeInstruction(&instruction));
+
+  encoder_.set_s_bit(false);
+  encoder_.set_varint(31);
+  encoder_.set_varint2(356);
+  EXPECT_EQ(QuicTextUtils::HexDecode("9f00ff65"),
+            EncodeInstruction(&instruction));
+}
+
+TEST_P(QpackInstructionEncoderTest, SBitAndVarintAndValue) {
+  const QpackInstruction instruction{QpackInstructionOpcode{0xc0, 0xc0},
+                                     {{QpackInstructionFieldType::kSbit, 0x20},
+                                      {QpackInstructionFieldType::kVarint, 5},
+                                      {QpackInstructionFieldType::kValue, 7}}};
+
+  encoder_.set_s_bit(true);
+  encoder_.set_varint(100);
+  encoder_.set_value("foo");
+  EXPECT_EQ(QuicTextUtils::HexDecode("ff458294e7"),
+            EncodeInstruction(&instruction));
+
+  encoder_.set_s_bit(false);
+  encoder_.set_varint(3);
+  encoder_.set_value("bar");
+  EXPECT_EQ(QuicTextUtils::HexDecode("c303626172"),
+            EncodeInstruction(&instruction));
+}
+
+TEST_P(QpackInstructionEncoderTest, Name) {
+  const QpackInstruction instruction{QpackInstructionOpcode{0xe0, 0xe0},
+                                     {{QpackInstructionFieldType::kName, 4}}};
+
+  encoder_.set_name("");
+  EXPECT_EQ(QuicTextUtils::HexDecode("e0"), EncodeInstruction(&instruction));
+
+  encoder_.set_name("foo");
+  EXPECT_EQ(QuicTextUtils::HexDecode("f294e7"),
+            EncodeInstruction(&instruction));
+
+  encoder_.set_name("bar");
+  EXPECT_EQ(QuicTextUtils::HexDecode("e3626172"),
+            EncodeInstruction(&instruction));
+}
+
+TEST_P(QpackInstructionEncoderTest, Value) {
+  const QpackInstruction instruction{QpackInstructionOpcode{0xf0, 0xf0},
+                                     {{QpackInstructionFieldType::kValue, 3}}};
+
+  encoder_.set_value("");
+  EXPECT_EQ(QuicTextUtils::HexDecode("f0"), EncodeInstruction(&instruction));
+
+  encoder_.set_value("foo");
+  EXPECT_EQ(QuicTextUtils::HexDecode("fa94e7"),
+            EncodeInstruction(&instruction));
+
+  encoder_.set_value("bar");
+  EXPECT_EQ(QuicTextUtils::HexDecode("f3626172"),
+            EncodeInstruction(&instruction));
+}
+
+TEST_P(QpackInstructionEncoderTest, SBitAndNameAndValue) {
+  const QpackInstruction instruction{QpackInstructionOpcode{0xf0, 0xf0},
+                                     {{QpackInstructionFieldType::kSbit, 0x08},
+                                      {QpackInstructionFieldType::kName, 2},
+                                      {QpackInstructionFieldType::kValue, 7}}};
+
+  encoder_.set_s_bit(false);
+  encoder_.set_name("");
+  encoder_.set_value("");
+  EXPECT_EQ(QuicTextUtils::HexDecode("f000"), EncodeInstruction(&instruction));
+
+  encoder_.set_s_bit(true);
+  encoder_.set_name("foo");
+  encoder_.set_value("bar");
+  EXPECT_EQ(QuicTextUtils::HexDecode("fe94e703626172"),
+            EncodeInstruction(&instruction));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_progressive_decoder.cc b/quic/core/qpack/qpack_progressive_decoder.cc
new file mode 100644
index 0000000..430e707
--- /dev/null
+++ b/quic/core/qpack/qpack_progressive_decoder.cc
@@ -0,0 +1,369 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_constants.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+
+namespace quic {
+
+QpackProgressiveDecoder::QpackProgressiveDecoder(
+    QuicStreamId stream_id,
+    QpackHeaderTable* header_table,
+    QpackDecoderStreamSender* decoder_stream_sender,
+    HeadersHandlerInterface* handler)
+    : stream_id_(stream_id),
+      prefix_decoder_(
+          QuicMakeUnique<QpackInstructionDecoder>(QpackPrefixLanguage(), this)),
+      instruction_decoder_(QpackRequestStreamLanguage(), this),
+      header_table_(header_table),
+      decoder_stream_sender_(decoder_stream_sender),
+      handler_(handler),
+      required_insert_count_(0),
+      base_(0),
+      required_insert_count_so_far_(0),
+      prefix_decoded_(false),
+      decoding_(true),
+      error_detected_(false) {}
+
+// static
+bool QpackProgressiveDecoder::DecodeRequiredInsertCount(
+    uint64_t encoded_required_insert_count,
+    uint64_t max_entries,
+    uint64_t total_number_of_inserts,
+    uint64_t* required_insert_count) {
+  if (encoded_required_insert_count == 0) {
+    *required_insert_count = 0;
+    return true;
+  }
+
+  // |max_entries| is calculated by dividing an unsigned 64-bit integer by 32,
+  // precluding all calculations in this method from overflowing.
+  DCHECK_LE(max_entries, std::numeric_limits<uint64_t>::max() / 32);
+
+  if (encoded_required_insert_count > 2 * max_entries) {
+    return false;
+  }
+
+  *required_insert_count = encoded_required_insert_count - 1;
+  DCHECK_LT(*required_insert_count, std::numeric_limits<uint64_t>::max() / 16);
+
+  uint64_t current_wrapped = total_number_of_inserts % (2 * max_entries);
+  DCHECK_LT(current_wrapped, std::numeric_limits<uint64_t>::max() / 16);
+
+  if (current_wrapped >= *required_insert_count + max_entries) {
+    // Required Insert Count wrapped around 1 extra time.
+    *required_insert_count += 2 * max_entries;
+  } else if (current_wrapped + max_entries < *required_insert_count) {
+    // Decoder wrapped around 1 extra time.
+    current_wrapped += 2 * max_entries;
+  }
+
+  if (*required_insert_count >
+      std::numeric_limits<uint64_t>::max() - total_number_of_inserts) {
+    return false;
+  }
+
+  *required_insert_count += total_number_of_inserts;
+
+  // Prevent underflow, also disallow invalid value 0 for Required Insert Count.
+  if (current_wrapped >= *required_insert_count) {
+    return false;
+  }
+
+  *required_insert_count -= current_wrapped;
+
+  return true;
+}
+
+void QpackProgressiveDecoder::Decode(QuicStringPiece data) {
+  DCHECK(decoding_);
+
+  if (data.empty() || error_detected_) {
+    return;
+  }
+
+  // Decode prefix byte by byte until the first (and only) instruction is
+  // decoded.
+  while (!prefix_decoded_) {
+    prefix_decoder_->Decode(data.substr(0, 1));
+    data = data.substr(1);
+    if (data.empty()) {
+      return;
+    }
+  }
+
+  instruction_decoder_.Decode(data);
+}
+
+void QpackProgressiveDecoder::EndHeaderBlock() {
+  DCHECK(decoding_);
+  decoding_ = false;
+
+  if (error_detected_) {
+    return;
+  }
+
+  if (!instruction_decoder_.AtInstructionBoundary()) {
+    OnError("Incomplete header block.");
+    return;
+  }
+
+  if (!prefix_decoded_) {
+    OnError("Incomplete header data prefix.");
+    return;
+  }
+
+  if (required_insert_count_ != required_insert_count_so_far_) {
+    OnError("Required Insert Count too large.");
+    return;
+  }
+
+  decoder_stream_sender_->SendHeaderAcknowledgement(stream_id_);
+  handler_->OnDecodingCompleted();
+}
+
+bool QpackProgressiveDecoder::OnInstructionDecoded(
+    const QpackInstruction* instruction) {
+  if (instruction == QpackIndexedHeaderFieldInstruction()) {
+    return DoIndexedHeaderFieldInstruction();
+  }
+  if (instruction == QpackIndexedHeaderFieldPostBaseInstruction()) {
+    return DoIndexedHeaderFieldPostBaseInstruction();
+  }
+  if (instruction == QpackLiteralHeaderFieldNameReferenceInstruction()) {
+    return DoLiteralHeaderFieldNameReferenceInstruction();
+  }
+  if (instruction == QpackLiteralHeaderFieldPostBaseInstruction()) {
+    return DoLiteralHeaderFieldPostBaseInstruction();
+  }
+  if (instruction == QpackLiteralHeaderFieldInstruction()) {
+    return DoLiteralHeaderFieldInstruction();
+  }
+  DCHECK_EQ(instruction, QpackPrefixInstruction());
+  return DoPrefixInstruction();
+}
+
+void QpackProgressiveDecoder::OnError(QuicStringPiece error_message) {
+  DCHECK(!error_detected_);
+
+  error_detected_ = true;
+  handler_->OnDecodingErrorDetected(error_message);
+}
+
+bool QpackProgressiveDecoder::DoIndexedHeaderFieldInstruction() {
+  if (!instruction_decoder_.s_bit()) {
+    uint64_t absolute_index;
+    if (!RequestStreamRelativeIndexToAbsoluteIndex(
+            instruction_decoder_.varint(), &absolute_index)) {
+      OnError("Invalid relative index.");
+      return false;
+    }
+
+    if (absolute_index >= required_insert_count_) {
+      OnError("Absolute Index must be smaller than Required Insert Count.");
+      return false;
+    }
+
+    DCHECK_LT(absolute_index, std::numeric_limits<uint64_t>::max());
+    required_insert_count_so_far_ =
+        std::max(required_insert_count_so_far_, absolute_index + 1);
+
+    auto entry =
+        header_table_->LookupEntry(/* is_static = */ false, absolute_index);
+    if (!entry) {
+      OnError("Dynamic table entry not found.");
+      return false;
+    }
+
+    handler_->OnHeaderDecoded(entry->name(), entry->value());
+    return true;
+  }
+
+  auto entry = header_table_->LookupEntry(/* is_static = */ true,
+                                          instruction_decoder_.varint());
+  if (!entry) {
+    OnError("Static table entry not found.");
+    return false;
+  }
+
+  handler_->OnHeaderDecoded(entry->name(), entry->value());
+  return true;
+}
+
+bool QpackProgressiveDecoder::DoIndexedHeaderFieldPostBaseInstruction() {
+  uint64_t absolute_index;
+  if (!PostBaseIndexToAbsoluteIndex(instruction_decoder_.varint(),
+                                    &absolute_index)) {
+    OnError("Invalid post-base index.");
+    return false;
+  }
+
+  if (absolute_index >= required_insert_count_) {
+    OnError("Absolute Index must be smaller than Required Insert Count.");
+    return false;
+  }
+
+  DCHECK_LT(absolute_index, std::numeric_limits<uint64_t>::max());
+  required_insert_count_so_far_ =
+      std::max(required_insert_count_so_far_, absolute_index + 1);
+
+  auto entry =
+      header_table_->LookupEntry(/* is_static = */ false, absolute_index);
+  if (!entry) {
+    OnError("Dynamic table entry not found.");
+    return false;
+  }
+
+  handler_->OnHeaderDecoded(entry->name(), entry->value());
+  return true;
+}
+
+bool QpackProgressiveDecoder::DoLiteralHeaderFieldNameReferenceInstruction() {
+  if (!instruction_decoder_.s_bit()) {
+    uint64_t absolute_index;
+    if (!RequestStreamRelativeIndexToAbsoluteIndex(
+            instruction_decoder_.varint(), &absolute_index)) {
+      OnError("Invalid relative index.");
+      return false;
+    }
+
+    if (absolute_index >= required_insert_count_) {
+      OnError("Absolute Index must be smaller than Required Insert Count.");
+      return false;
+    }
+
+    DCHECK_LT(absolute_index, std::numeric_limits<uint64_t>::max());
+    required_insert_count_so_far_ =
+        std::max(required_insert_count_so_far_, absolute_index + 1);
+
+    auto entry =
+        header_table_->LookupEntry(/* is_static = */ false, absolute_index);
+    if (!entry) {
+      OnError("Dynamic table entry not found.");
+      return false;
+    }
+
+    handler_->OnHeaderDecoded(entry->name(), instruction_decoder_.value());
+    return true;
+  }
+
+  auto entry = header_table_->LookupEntry(/* is_static = */ true,
+                                          instruction_decoder_.varint());
+  if (!entry) {
+    OnError("Static table entry not found.");
+    return false;
+  }
+
+  handler_->OnHeaderDecoded(entry->name(), instruction_decoder_.value());
+  return true;
+}
+
+bool QpackProgressiveDecoder::DoLiteralHeaderFieldPostBaseInstruction() {
+  uint64_t absolute_index;
+  if (!PostBaseIndexToAbsoluteIndex(instruction_decoder_.varint(),
+                                    &absolute_index)) {
+    OnError("Invalid post-base index.");
+    return false;
+  }
+
+  if (absolute_index >= required_insert_count_) {
+    OnError("Absolute Index must be smaller than Required Insert Count.");
+    return false;
+  }
+
+  DCHECK_LT(absolute_index, std::numeric_limits<uint64_t>::max());
+  required_insert_count_so_far_ =
+      std::max(required_insert_count_so_far_, absolute_index + 1);
+
+  auto entry =
+      header_table_->LookupEntry(/* is_static = */ false, absolute_index);
+  if (!entry) {
+    OnError("Dynamic table entry not found.");
+    return false;
+  }
+
+  handler_->OnHeaderDecoded(entry->name(), instruction_decoder_.value());
+  return true;
+}
+
+bool QpackProgressiveDecoder::DoLiteralHeaderFieldInstruction() {
+  handler_->OnHeaderDecoded(instruction_decoder_.name(),
+                            instruction_decoder_.value());
+
+  return true;
+}
+
+bool QpackProgressiveDecoder::DoPrefixInstruction() {
+  DCHECK(!prefix_decoded_);
+
+  if (!DecodeRequiredInsertCount(
+          prefix_decoder_->varint(), header_table_->max_entries(),
+          header_table_->inserted_entry_count(), &required_insert_count_)) {
+    OnError("Error decoding Required Insert Count.");
+    return false;
+  }
+
+  const bool sign = prefix_decoder_->s_bit();
+  const uint64_t delta_base = prefix_decoder_->varint2();
+  if (!DeltaBaseToBase(sign, delta_base, &base_)) {
+    OnError("Error calculating Base.");
+    return false;
+  }
+
+  prefix_decoded_ = true;
+
+  return true;
+}
+
+bool QpackProgressiveDecoder::DeltaBaseToBase(bool sign,
+                                              uint64_t delta_base,
+                                              uint64_t* base) {
+  if (sign) {
+    if (delta_base == std::numeric_limits<uint64_t>::max() ||
+        required_insert_count_ < delta_base + 1) {
+      return false;
+    }
+    *base = required_insert_count_ - delta_base - 1;
+    return true;
+  }
+
+  if (delta_base >
+      std::numeric_limits<uint64_t>::max() - required_insert_count_) {
+    return false;
+  }
+  *base = required_insert_count_ + delta_base;
+  return true;
+}
+
+bool QpackProgressiveDecoder::RequestStreamRelativeIndexToAbsoluteIndex(
+    uint64_t relative_index,
+    uint64_t* absolute_index) const {
+  if (relative_index == std::numeric_limits<uint64_t>::max() ||
+      relative_index + 1 > base_) {
+    return false;
+  }
+
+  *absolute_index = base_ - 1 - relative_index;
+  return true;
+}
+
+bool QpackProgressiveDecoder::PostBaseIndexToAbsoluteIndex(
+    uint64_t post_base_index,
+    uint64_t* absolute_index) const {
+  if (post_base_index >= std::numeric_limits<uint64_t>::max() - base_) {
+    return false;
+  }
+
+  *absolute_index = base_ + post_base_index;
+  return true;
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_progressive_decoder.h b/quic/core/qpack/qpack_progressive_decoder.h
new file mode 100644
index 0000000..7988861
--- /dev/null
+++ b/quic/core/qpack/qpack_progressive_decoder.h
@@ -0,0 +1,140 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_PROGRESSIVE_DECODER_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_PROGRESSIVE_DECODER_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_receiver.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+class QpackHeaderTable;
+
+// Class to decode a single header block.
+class QUIC_EXPORT_PRIVATE QpackProgressiveDecoder
+    : public QpackInstructionDecoder::Delegate {
+ public:
+  // Interface for receiving decoded header block from the decoder.
+  class QUIC_EXPORT_PRIVATE HeadersHandlerInterface {
+   public:
+    virtual ~HeadersHandlerInterface() {}
+
+    // Called when a new header name-value pair is decoded.  Multiple values for
+    // a given name will be emitted as multiple calls to OnHeader.
+    virtual void OnHeaderDecoded(QuicStringPiece name,
+                                 QuicStringPiece value) = 0;
+
+    // Called when the header block is completely decoded.
+    // Indicates the total number of bytes in this block.
+    // The decoder will not access the handler after this call.
+    // Note that this method might not be called synchronously when the header
+    // block is received on the wire, in case decoding is blocked on receiving
+    // entries on the encoder stream.  TODO(bnc): Implement blocked decoding.
+    virtual void OnDecodingCompleted() = 0;
+
+    // Called when a decoding error has occurred.  No other methods will be
+    // called afterwards.
+    virtual void OnDecodingErrorDetected(QuicStringPiece error_message) = 0;
+  };
+
+  QpackProgressiveDecoder() = delete;
+  QpackProgressiveDecoder(QuicStreamId stream_id,
+                          QpackHeaderTable* header_table,
+                          QpackDecoderStreamSender* decoder_stream_sender,
+                          HeadersHandlerInterface* handler);
+  QpackProgressiveDecoder(const QpackProgressiveDecoder&) = delete;
+  QpackProgressiveDecoder& operator=(const QpackProgressiveDecoder&) = delete;
+  ~QpackProgressiveDecoder() override = default;
+
+  // Calculate Required Insert Count from Encoded Required Insert Count,
+  // MaxEntries, and total number of dynamic table insertions according to
+  // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#ric.
+  // Returns true on success, false on invalid input or overflow/underflow.
+  static bool DecodeRequiredInsertCount(uint64_t encoded_required_insert_count,
+                                        uint64_t max_entries,
+                                        uint64_t total_number_of_inserts,
+                                        uint64_t* required_insert_count);
+
+  // Provide a data fragment to decode.
+  void Decode(QuicStringPiece data);
+
+  // Signal that the entire header block has been received and passed in
+  // through Decode().  No methods must be called afterwards.
+  void EndHeaderBlock();
+
+  // QpackInstructionDecoder::Delegate implementation.
+  bool OnInstructionDecoded(const QpackInstruction* instruction) override;
+  void OnError(QuicStringPiece error_message) override;
+
+ private:
+  bool DoIndexedHeaderFieldInstruction();
+  bool DoIndexedHeaderFieldPostBaseInstruction();
+  bool DoLiteralHeaderFieldNameReferenceInstruction();
+  bool DoLiteralHeaderFieldPostBaseInstruction();
+  bool DoLiteralHeaderFieldInstruction();
+  bool DoPrefixInstruction();
+
+  // Calculates Base from |required_insert_count_|, which must be set before
+  // calling this method, and sign bit and Delta Base in the Header Data Prefix,
+  // which are passed in as arguments.  Returns true on success, false on
+  // failure due to overflow/underflow.
+  bool DeltaBaseToBase(bool sign, uint64_t delta_base, uint64_t* base);
+
+  // The request stream can use relative index (but different from the kind of
+  // relative index used on the encoder stream), and post-base index.
+  // These methods convert relative index and post-base index to absolute index
+  // (one based).  They return true on success, or false if conversion fails due
+  // to overflow/underflow.  On success, |*absolute_index| is guaranteed to be
+  // strictly less than std::numeric_limits<uint64_t>::max().
+  bool RequestStreamRelativeIndexToAbsoluteIndex(
+      uint64_t relative_index,
+      uint64_t* absolute_index) const;
+  bool PostBaseIndexToAbsoluteIndex(uint64_t post_base_index,
+                                    uint64_t* absolute_index) const;
+
+  const QuicStreamId stream_id_;
+
+  // |prefix_decoder_| only decodes a handful of bytes then it can be
+  // destroyed to conserve memory.  |instruction_decoder_|, on the other hand,
+  // is used until the entire header block is decoded.
+  std::unique_ptr<QpackInstructionDecoder> prefix_decoder_;
+  QpackInstructionDecoder instruction_decoder_;
+
+  const QpackHeaderTable* const header_table_;
+  QpackDecoderStreamSender* const decoder_stream_sender_;
+  HeadersHandlerInterface* const handler_;
+
+  // Required Insert Count and Base are decoded from the Header Data Prefix.
+  uint64_t required_insert_count_;
+  uint64_t base_;
+
+  // Required Insert Count is one larger than the largest absolute index of all
+  // referenced dynamic table entries, or zero if no dynamic table entries are
+  // referenced.  |required_insert_count_so_far_| starts out as zero and keeps
+  // track of the Required Insert Count based on entries decoded so far.
+  // After decoding is completed, it is compared to |required_insert_count_|.
+  uint64_t required_insert_count_so_far_;
+
+  // False until prefix is fully read and decoded.
+  bool prefix_decoded_;
+
+  // True until EndHeaderBlock() is called.
+  bool decoding_;
+
+  // True if a decoding error has been detected.
+  bool error_detected_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_PROGRESSIVE_DECODER_H_
diff --git a/quic/core/qpack/qpack_progressive_decoder_test.cc b/quic/core/qpack/qpack_progressive_decoder_test.cc
new file mode 100644
index 0000000..b306702
--- /dev/null
+++ b/quic/core/qpack/qpack_progressive_decoder_test.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+// For testing valid decodings, the Encoded Required Insert Count is calculated
+// from Required Insert Count, so that there is an expected value to compare
+// the decoded value against, and so that intricate inequalities can be
+// documented.
+struct {
+  uint64_t required_insert_count;
+  uint64_t max_entries;
+  uint64_t total_number_of_inserts;
+} kTestData[] = {
+    // Maximum dynamic table capacity is zero.
+    {0, 0, 0},
+    // No dynamic entries in header.
+    {0, 100, 0},
+    {0, 100, 500},
+    // Required Insert Count has not wrapped around yet, no entries evicted.
+    {15, 100, 25},
+    {20, 100, 10},
+    // Required Insert Count has not wrapped around yet, some entries evicted.
+    {90, 100, 110},
+    // Required Insert Count has wrapped around.
+    {234, 100, 180},
+    // Required Insert Count has wrapped around many times.
+    {5678, 100, 5701},
+    // Lowest and highest possible Required Insert Count values
+    // for given MaxEntries and total number of insertions.
+    {401, 100, 500},
+    {600, 100, 500}};
+
+uint64_t EncodeRequiredInsertCount(uint64_t required_insert_count,
+                                   uint64_t max_entries) {
+  if (required_insert_count == 0) {
+    return 0;
+  }
+
+  return required_insert_count % (2 * max_entries) + 1;
+}
+
+TEST(QpackProgressiveDecoderTest, DecodeRequiredInsertCount) {
+  for (size_t i = 0; i < QUIC_ARRAYSIZE(kTestData); ++i) {
+    const uint64_t required_insert_count = kTestData[i].required_insert_count;
+    const uint64_t max_entries = kTestData[i].max_entries;
+    const uint64_t total_number_of_inserts =
+        kTestData[i].total_number_of_inserts;
+
+    if (required_insert_count != 0) {
+      // Dynamic entries cannot be referenced if dynamic table capacity is zero.
+      ASSERT_LT(0u, max_entries) << i;
+      // Entry |total_number_of_inserts - 1 - max_entries| and earlier entries
+      // are evicted.  Entry |required_insert_count - 1| is referenced.  No
+      // evicted entry can be referenced.
+      ASSERT_LT(total_number_of_inserts, required_insert_count + max_entries)
+          << i;
+      // Entry |required_insert_count - 1 - max_entries| and earlier entries are
+      // evicted, entry |total_number_of_inserts - 1| is the last acknowledged
+      // entry.  Every evicted entry must be acknowledged.
+      ASSERT_LE(required_insert_count, total_number_of_inserts + max_entries)
+          << i;
+    }
+
+    uint64_t encoded_required_insert_count =
+        EncodeRequiredInsertCount(required_insert_count, max_entries);
+
+    // Initialize to a value different from the expected output to confirm that
+    // DecodeRequiredInsertCount() modifies the value of
+    // |decoded_required_insert_count|.
+    uint64_t decoded_required_insert_count = required_insert_count + 1;
+    EXPECT_TRUE(QpackProgressiveDecoder::DecodeRequiredInsertCount(
+        encoded_required_insert_count, max_entries, total_number_of_inserts,
+        &decoded_required_insert_count))
+        << i;
+
+    EXPECT_EQ(decoded_required_insert_count, required_insert_count) << i;
+  }
+}
+
+// Failures are tested with hardcoded values for encoded required insert count,
+// to provide test coverage for values that would never be produced by a well
+// behaved encoding function.
+struct {
+  uint64_t encoded_required_insert_count;
+  uint64_t max_entries;
+  uint64_t total_number_of_inserts;
+} kInvalidTestData[] = {
+    // Maximum dynamic table capacity is zero, yet header block
+    // claims to have a reference to a dynamic table entry.
+    {1, 0, 0},
+    {9, 0, 0},
+    // Examples from
+    // https://github.com/quicwg/base-drafts/issues/2112#issue-389626872.
+    {1, 10, 2},
+    {18, 10, 2},
+    // Encoded Required Insert Count value too small or too large
+    // for given MaxEntries and total number of insertions.
+    {400, 100, 500},
+    {601, 100, 500}};
+
+TEST(QpackProgressiveDecoderTest, DecodeRequiredInsertCountError) {
+  for (size_t i = 0; i < QUIC_ARRAYSIZE(kInvalidTestData); ++i) {
+    uint64_t decoded_required_insert_count = 0;
+    EXPECT_FALSE(QpackProgressiveDecoder::DecodeRequiredInsertCount(
+        kInvalidTestData[i].encoded_required_insert_count,
+        kInvalidTestData[i].max_entries,
+        kInvalidTestData[i].total_number_of_inserts,
+        &decoded_required_insert_count))
+        << i;
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_progressive_encoder.cc b/quic/core/qpack/qpack_progressive_encoder.cc
new file mode 100644
index 0000000..41433ff
--- /dev/null
+++ b/quic/core/qpack/qpack_progressive_encoder.cc
@@ -0,0 +1,138 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_progressive_encoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_constants.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+QpackProgressiveEncoder::QpackProgressiveEncoder(
+    QuicStreamId stream_id,
+    QpackHeaderTable* header_table,
+    QpackEncoderStreamSender* encoder_stream_sender,
+    const spdy::SpdyHeaderBlock* header_list)
+    : stream_id_(stream_id),
+      header_table_(header_table),
+      encoder_stream_sender_(encoder_stream_sender),
+      header_list_(header_list),
+      header_list_iterator_(header_list_->begin()),
+      prefix_encoded_(false) {
+  // TODO(bnc): Use |stream_id_| for dynamic table entry management, and
+  // remove this dummy DCHECK.
+  DCHECK_LE(0u, stream_id_);
+
+  DCHECK(header_table_);
+  DCHECK(encoder_stream_sender_);
+  DCHECK(header_list_);
+}
+
+bool QpackProgressiveEncoder::HasNext() const {
+  return header_list_iterator_ != header_list_->end() || !prefix_encoded_;
+}
+
+void QpackProgressiveEncoder::Next(size_t max_encoded_bytes,
+                                   QuicString* output) {
+  DCHECK_NE(0u, max_encoded_bytes);
+  DCHECK(HasNext());
+
+  // Since QpackInstructionEncoder::Next() does not indicate the number of bytes
+  // written, save the maximum new size of |*output|.
+  const size_t max_length = output->size() + max_encoded_bytes;
+
+  DCHECK_LT(output->size(), max_length);
+
+  if (!prefix_encoded_ && !instruction_encoder_.HasNext()) {
+    // TODO(bnc): Implement dynamic entries and set Required Insert Count and
+    // Delta Base accordingly.
+    instruction_encoder_.set_varint(0);
+    instruction_encoder_.set_varint2(0);
+    instruction_encoder_.set_s_bit(false);
+
+    instruction_encoder_.Encode(QpackPrefixInstruction());
+
+    DCHECK(instruction_encoder_.HasNext());
+  }
+
+  do {
+    // Call QpackInstructionEncoder::Encode for |*header_list_iterator_| if it
+    // has not been called yet.
+    if (!instruction_encoder_.HasNext()) {
+      DCHECK(prefix_encoded_);
+
+      // Even after |name| and |value| go out of scope, copies of these
+      // QuicStringPieces retained by QpackInstructionEncoder are still valid as
+      // long as |header_list_| is valid.
+      QuicStringPiece name = header_list_iterator_->first;
+      QuicStringPiece value = header_list_iterator_->second;
+
+      // |is_static| and |index| are saved by QpackInstructionEncoder by value,
+      // there are no lifetime concerns.
+      bool is_static;
+      uint64_t index;
+
+      auto match_type =
+          header_table_->FindHeaderField(name, value, &is_static, &index);
+
+      switch (match_type) {
+        case QpackHeaderTable::MatchType::kNameAndValue:
+          DCHECK(is_static) << "Dynamic table entries not supported yet.";
+
+          instruction_encoder_.set_s_bit(is_static);
+          instruction_encoder_.set_varint(index);
+
+          instruction_encoder_.Encode(QpackIndexedHeaderFieldInstruction());
+
+          break;
+        case QpackHeaderTable::MatchType::kName:
+          DCHECK(is_static) << "Dynamic table entries not supported yet.";
+
+          instruction_encoder_.set_s_bit(is_static);
+          instruction_encoder_.set_varint(index);
+          instruction_encoder_.set_value(value);
+
+          instruction_encoder_.Encode(
+              QpackLiteralHeaderFieldNameReferenceInstruction());
+
+          break;
+        case QpackHeaderTable::MatchType::kNoMatch:
+          instruction_encoder_.set_name(name);
+          instruction_encoder_.set_value(value);
+
+          instruction_encoder_.Encode(QpackLiteralHeaderFieldInstruction());
+
+          break;
+      }
+    }
+
+    DCHECK(instruction_encoder_.HasNext());
+
+    instruction_encoder_.Next(max_length - output->size(), output);
+
+    if (instruction_encoder_.HasNext()) {
+      // There was not enough room to completely encode current header field.
+      DCHECK_EQ(output->size(), max_length);
+
+      return;
+    }
+
+    // It is possible that the output buffer was just large enough for encoding
+    // the current header field, hence equality is allowed here.
+    DCHECK_LE(output->size(), max_length);
+
+    if (prefix_encoded_) {
+      // Move on to the next header field.
+      ++header_list_iterator_;
+    } else {
+      // Mark prefix as encoded.
+      prefix_encoded_ = true;
+    }
+  } while (HasNext() && output->size() < max_length);
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_progressive_encoder.h b/quic/core/qpack/qpack_progressive_encoder.h
new file mode 100644
index 0000000..86cce55
--- /dev/null
+++ b/quic/core/qpack/qpack_progressive_encoder.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_PROGRESSIVE_ENCODER_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_PROGRESSIVE_ENCODER_H_
+
+#include <cstddef>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+
+namespace quic {
+
+class QpackHeaderTable;
+
+// An implementation of ProgressiveEncoder interface that encodes a single
+// header block.
+class QUIC_EXPORT_PRIVATE QpackProgressiveEncoder
+    : public spdy::HpackEncoder::ProgressiveEncoder {
+ public:
+  QpackProgressiveEncoder() = delete;
+  QpackProgressiveEncoder(QuicStreamId stream_id,
+                          QpackHeaderTable* header_table,
+                          QpackEncoderStreamSender* encoder_stream_sender,
+                          const spdy::SpdyHeaderBlock* header_list);
+  QpackProgressiveEncoder(const QpackProgressiveEncoder&) = delete;
+  QpackProgressiveEncoder& operator=(const QpackProgressiveEncoder&) = delete;
+  ~QpackProgressiveEncoder() override = default;
+
+  // Returns true iff more remains to encode.
+  bool HasNext() const override;
+
+  // Encodes up to |max_encoded_bytes| octets, appending to |output|.
+  void Next(size_t max_encoded_bytes, QuicString* output) override;
+
+ private:
+  const QuicStreamId stream_id_;
+  QpackInstructionEncoder instruction_encoder_;
+  const QpackHeaderTable* const header_table_;
+  QpackEncoderStreamSender* const encoder_stream_sender_;
+  const spdy::SpdyHeaderBlock* const header_list_;
+
+  // Header field currently being encoded.
+  spdy::SpdyHeaderBlock::const_iterator header_list_iterator_;
+
+  // False until prefix is fully encoded.
+  bool prefix_encoded_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_PROGRESSIVE_ENCODER_H_
diff --git a/quic/core/qpack/qpack_round_trip_test.cc b/quic/core/qpack/qpack_round_trip_test.cc
new file mode 100644
index 0000000..700ff85
--- /dev/null
+++ b/quic/core/qpack/qpack_round_trip_test.cc
@@ -0,0 +1,137 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <tuple>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+
+using ::testing::Combine;
+using ::testing::Values;
+
+namespace quic {
+namespace test {
+namespace {
+
+class QpackRoundTripTest
+    : public QuicTestWithParam<std::tuple<FragmentMode, FragmentMode>> {
+ public:
+  QpackRoundTripTest()
+      : encoding_fragment_mode_(std::get<0>(GetParam())),
+        decoding_fragment_mode_(std::get<1>(GetParam())) {}
+  ~QpackRoundTripTest() override = default;
+
+  spdy::SpdyHeaderBlock EncodeThenDecode(
+      const spdy::SpdyHeaderBlock& header_list) {
+    NoopDecoderStreamErrorDelegate decoder_stream_error_delegate;
+    NoopEncoderStreamSenderDelegate encoder_stream_sender_delegate;
+    QuicString encoded_header_block = QpackEncode(
+        &decoder_stream_error_delegate, &encoder_stream_sender_delegate,
+        FragmentModeToFragmentSizeGenerator(encoding_fragment_mode_),
+        &header_list);
+
+    TestHeadersHandler handler;
+    NoopEncoderStreamErrorDelegate encoder_stream_error_delegate;
+    NoopDecoderStreamSenderDelegate decoder_stream_sender_delegate;
+    QpackDecode(&encoder_stream_error_delegate, &decoder_stream_sender_delegate,
+                &handler,
+                FragmentModeToFragmentSizeGenerator(decoding_fragment_mode_),
+                encoded_header_block);
+
+    EXPECT_TRUE(handler.decoding_completed());
+    EXPECT_FALSE(handler.decoding_error_detected());
+
+    return handler.ReleaseHeaderList();
+  }
+
+ private:
+  const FragmentMode encoding_fragment_mode_;
+  const FragmentMode decoding_fragment_mode_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    QpackRoundTripTest,
+    Combine(Values(FragmentMode::kSingleChunk, FragmentMode::kOctetByOctet),
+            Values(FragmentMode::kSingleChunk, FragmentMode::kOctetByOctet)));
+
+TEST_P(QpackRoundTripTest, Empty) {
+  spdy::SpdyHeaderBlock header_list;
+  spdy::SpdyHeaderBlock output = EncodeThenDecode(header_list);
+  EXPECT_EQ(header_list, output);
+}
+
+TEST_P(QpackRoundTripTest, EmptyName) {
+  spdy::SpdyHeaderBlock header_list;
+  header_list["foo"] = "bar";
+  header_list[""] = "bar";
+
+  spdy::SpdyHeaderBlock output = EncodeThenDecode(header_list);
+  EXPECT_EQ(header_list, output);
+}
+
+TEST_P(QpackRoundTripTest, EmptyValue) {
+  spdy::SpdyHeaderBlock header_list;
+  header_list["foo"] = "";
+  header_list[""] = "";
+
+  spdy::SpdyHeaderBlock output = EncodeThenDecode(header_list);
+  EXPECT_EQ(header_list, output);
+}
+
+TEST_P(QpackRoundTripTest, MultipleWithLongEntries) {
+  spdy::SpdyHeaderBlock header_list;
+  header_list["foo"] = "bar";
+  header_list[":path"] = "/";
+  header_list["foobaar"] = QuicString(127, 'Z');
+  header_list[QuicString(1000, 'b')] = QuicString(1000, 'c');
+
+  spdy::SpdyHeaderBlock output = EncodeThenDecode(header_list);
+  EXPECT_EQ(header_list, output);
+}
+
+TEST_P(QpackRoundTripTest, StaticTable) {
+  {
+    spdy::SpdyHeaderBlock header_list;
+    header_list[":method"] = "GET";
+    header_list["accept-encoding"] = "gzip, deflate";
+    header_list["cache-control"] = "";
+    header_list["foo"] = "bar";
+    header_list[":path"] = "/";
+
+    spdy::SpdyHeaderBlock output = EncodeThenDecode(header_list);
+    EXPECT_EQ(header_list, output);
+  }
+  {
+    spdy::SpdyHeaderBlock header_list;
+    header_list[":method"] = "POST";
+    header_list["accept-encoding"] = "brotli";
+    header_list["cache-control"] = "foo";
+    header_list["foo"] = "bar";
+    header_list[":path"] = "/";
+
+    spdy::SpdyHeaderBlock output = EncodeThenDecode(header_list);
+    EXPECT_EQ(header_list, output);
+  }
+  {
+    spdy::SpdyHeaderBlock header_list;
+    header_list[":method"] = "CONNECT";
+    header_list["accept-encoding"] = "";
+    header_list["foo"] = "bar";
+    header_list[":path"] = "/";
+
+    spdy::SpdyHeaderBlock output = EncodeThenDecode(header_list);
+    EXPECT_EQ(header_list, output);
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_static_table.cc b/quic/core/qpack/qpack_static_table.cc
new file mode 100644
index 0000000..f1986f7
--- /dev/null
+++ b/quic/core/qpack/qpack_static_table.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+
+namespace quic {
+
+// The "constructor" for a QpackStaticEntry that computes the lengths at
+// compile time.
+#define STATIC_ENTRY(name, value) \
+  { name, QUIC_ARRAYSIZE(name) - 1, value, QUIC_ARRAYSIZE(value) - 1 }
+
+const std::vector<QpackStaticEntry>& QpackStaticTableVector() {
+  static const auto* kQpackStaticTable = new std::vector<QpackStaticEntry>{
+      STATIC_ENTRY(":authority", ""),                                     // 0
+      STATIC_ENTRY(":path", "/"),                                         // 1
+      STATIC_ENTRY("age", "0"),                                           // 2
+      STATIC_ENTRY("content-disposition", ""),                            // 3
+      STATIC_ENTRY("content-length", "0"),                                // 4
+      STATIC_ENTRY("cookie", ""),                                         // 5
+      STATIC_ENTRY("date", ""),                                           // 6
+      STATIC_ENTRY("etag", ""),                                           // 7
+      STATIC_ENTRY("if-modified-since", ""),                              // 8
+      STATIC_ENTRY("if-none-match", ""),                                  // 9
+      STATIC_ENTRY("last-modified", ""),                                  // 10
+      STATIC_ENTRY("link", ""),                                           // 11
+      STATIC_ENTRY("location", ""),                                       // 12
+      STATIC_ENTRY("referer", ""),                                        // 13
+      STATIC_ENTRY("set-cookie", ""),                                     // 14
+      STATIC_ENTRY(":method", "CONNECT"),                                 // 15
+      STATIC_ENTRY(":method", "DELETE"),                                  // 16
+      STATIC_ENTRY(":method", "GET"),                                     // 17
+      STATIC_ENTRY(":method", "HEAD"),                                    // 18
+      STATIC_ENTRY(":method", "OPTIONS"),                                 // 19
+      STATIC_ENTRY(":method", "POST"),                                    // 20
+      STATIC_ENTRY(":method", "PUT"),                                     // 21
+      STATIC_ENTRY(":scheme", "http"),                                    // 22
+      STATIC_ENTRY(":scheme", "https"),                                   // 23
+      STATIC_ENTRY(":status", "103"),                                     // 24
+      STATIC_ENTRY(":status", "200"),                                     // 25
+      STATIC_ENTRY(":status", "304"),                                     // 26
+      STATIC_ENTRY(":status", "404"),                                     // 27
+      STATIC_ENTRY(":status", "503"),                                     // 28
+      STATIC_ENTRY("accept", "*/*"),                                      // 29
+      STATIC_ENTRY("accept", "application/dns-message"),                  // 30
+      STATIC_ENTRY("accept-encoding", "gzip, deflate, br"),               // 31
+      STATIC_ENTRY("accept-ranges", "bytes"),                             // 32
+      STATIC_ENTRY("access-control-allow-headers", "cache-control"),      // 33
+      STATIC_ENTRY("access-control-allow-headers", "content-type"),       // 35
+      STATIC_ENTRY("access-control-allow-origin", "*"),                   // 35
+      STATIC_ENTRY("cache-control", "max-age=0"),                         // 36
+      STATIC_ENTRY("cache-control", "max-age=2592000"),                   // 37
+      STATIC_ENTRY("cache-control", "max-age=604800"),                    // 38
+      STATIC_ENTRY("cache-control", "no-cache"),                          // 39
+      STATIC_ENTRY("cache-control", "no-store"),                          // 40
+      STATIC_ENTRY("cache-control", "public, max-age=31536000"),          // 41
+      STATIC_ENTRY("content-encoding", "br"),                             // 42
+      STATIC_ENTRY("content-encoding", "gzip"),                           // 43
+      STATIC_ENTRY("content-type", "application/dns-message"),            // 44
+      STATIC_ENTRY("content-type", "application/javascript"),             // 45
+      STATIC_ENTRY("content-type", "application/json"),                   // 46
+      STATIC_ENTRY("content-type", "application/x-www-form-urlencoded"),  // 47
+      STATIC_ENTRY("content-type", "image/gif"),                          // 48
+      STATIC_ENTRY("content-type", "image/jpeg"),                         // 49
+      STATIC_ENTRY("content-type", "image/png"),                          // 50
+      STATIC_ENTRY("content-type", "text/css"),                           // 51
+      STATIC_ENTRY("content-type", "text/html; charset=utf-8"),           // 52
+      STATIC_ENTRY("content-type", "text/plain"),                         // 53
+      STATIC_ENTRY("content-type", "text/plain;charset=utf-8"),           // 54
+      STATIC_ENTRY("range", "bytes=0-"),                                  // 55
+      STATIC_ENTRY("strict-transport-security", "max-age=31536000"),      // 56
+      STATIC_ENTRY("strict-transport-security",
+                   "max-age=31536000; includesubdomains"),  // 57
+      STATIC_ENTRY("strict-transport-security",
+                   "max-age=31536000; includesubdomains; preload"),        // 58
+      STATIC_ENTRY("vary", "accept-encoding"),                             // 59
+      STATIC_ENTRY("vary", "origin"),                                      // 60
+      STATIC_ENTRY("x-content-type-options", "nosniff"),                   // 61
+      STATIC_ENTRY("x-xss-protection", "1; mode=block"),                   // 62
+      STATIC_ENTRY(":status", "100"),                                      // 63
+      STATIC_ENTRY(":status", "204"),                                      // 64
+      STATIC_ENTRY(":status", "206"),                                      // 65
+      STATIC_ENTRY(":status", "302"),                                      // 66
+      STATIC_ENTRY(":status", "400"),                                      // 67
+      STATIC_ENTRY(":status", "403"),                                      // 68
+      STATIC_ENTRY(":status", "421"),                                      // 69
+      STATIC_ENTRY(":status", "425"),                                      // 70
+      STATIC_ENTRY(":status", "500"),                                      // 71
+      STATIC_ENTRY("accept-language", ""),                                 // 72
+      STATIC_ENTRY("access-control-allow-credentials", "FALSE"),           // 73
+      STATIC_ENTRY("access-control-allow-credentials", "TRUE"),            // 74
+      STATIC_ENTRY("access-control-allow-headers", "*"),                   // 75
+      STATIC_ENTRY("access-control-allow-methods", "get"),                 // 76
+      STATIC_ENTRY("access-control-allow-methods", "get, post, options"),  // 77
+      STATIC_ENTRY("access-control-allow-methods", "options"),             // 78
+      STATIC_ENTRY("access-control-expose-headers", "content-length"),     // 79
+      STATIC_ENTRY("access-control-request-headers", "content-type"),      // 80
+      STATIC_ENTRY("access-control-request-method", "get"),                // 81
+      STATIC_ENTRY("access-control-request-method", "post"),               // 82
+      STATIC_ENTRY("alt-svc", "clear"),                                    // 83
+      STATIC_ENTRY("authorization", ""),                                   // 84
+      STATIC_ENTRY(
+          "content-security-policy",
+          "script-src 'none'; object-src 'none'; base-uri 'none'"),  // 85
+      STATIC_ENTRY("early-data", "1"),                               // 86
+      STATIC_ENTRY("expect-ct", ""),                                 // 87
+      STATIC_ENTRY("forwarded", ""),                                 // 88
+      STATIC_ENTRY("if-range", ""),                                  // 89
+      STATIC_ENTRY("origin", ""),                                    // 90
+      STATIC_ENTRY("purpose", "prefetch"),                           // 91
+      STATIC_ENTRY("server", ""),                                    // 92
+      STATIC_ENTRY("timing-allow-origin", "*"),                      // 93
+      STATIC_ENTRY("upgrade-insecure-requests", "1"),                // 94
+      STATIC_ENTRY("user-agent", ""),                                // 95
+      STATIC_ENTRY("x-forwarded-for", ""),                           // 96
+      STATIC_ENTRY("x-frame-options", "deny"),                       // 97
+      STATIC_ENTRY("x-frame-options", "sameorigin"),                 // 98
+  };
+  return *kQpackStaticTable;
+}
+
+#undef STATIC_ENTRY
+
+const QpackStaticTable& ObtainQpackStaticTable() {
+  static const QpackStaticTable* const shared_static_table = []() {
+    auto* table = new QpackStaticTable();
+    table->Initialize(QpackStaticTableVector().data(),
+                      QpackStaticTableVector().size());
+    CHECK(table->IsInitialized());
+    return table;
+  }();
+  return *shared_static_table;
+}
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_static_table.h b/quic/core/qpack/qpack_static_table.h
new file mode 100644
index 0000000..d8c2555
--- /dev/null
+++ b/quic/core/qpack/qpack_static_table.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_STATIC_TABLE_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_STATIC_TABLE_H_
+
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h"
+
+namespace quic {
+
+using QpackStaticEntry = spdy::HpackStaticEntry;
+using QpackStaticTable = spdy::HpackStaticTable;
+
+// QPACK static table defined at
+// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#static-table.
+QUIC_EXPORT_PRIVATE const std::vector<QpackStaticEntry>&
+QpackStaticTableVector();
+
+// Returns a QpackStaticTable instance initialized with kQpackStaticTable.
+// The instance is read-only, has static lifetime, and is safe to share amoung
+// threads. This function is thread-safe.
+QUIC_EXPORT_PRIVATE const QpackStaticTable& ObtainQpackStaticTable();
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_STATIC_TABLE_H_
diff --git a/quic/core/qpack/qpack_static_table_test.cc b/quic/core/qpack/qpack_static_table_test.cc
new file mode 100644
index 0000000..50289f2
--- /dev/null
+++ b/quic/core/qpack/qpack_static_table_test.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h"
+
+#include <set>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+namespace test {
+
+namespace {
+
+// Check that an initialized instance has the right number of entries.
+TEST(QpackStaticTableTest, Initialize) {
+  QpackStaticTable table;
+  EXPECT_FALSE(table.IsInitialized());
+
+  table.Initialize(QpackStaticTableVector().data(),
+                   QpackStaticTableVector().size());
+  EXPECT_TRUE(table.IsInitialized());
+
+  auto static_entries = table.GetStaticEntries();
+  EXPECT_EQ(QpackStaticTableVector().size(), static_entries.size());
+
+  auto static_index = table.GetStaticIndex();
+  EXPECT_EQ(QpackStaticTableVector().size(), static_index.size());
+
+  auto static_name_index = table.GetStaticNameIndex();
+  std::set<QuicStringPiece> names;
+  for (auto entry : static_index) {
+    names.insert(entry->name());
+  }
+  EXPECT_EQ(names.size(), static_name_index.size());
+}
+
+// Test that ObtainQpackStaticTable returns the same instance every time.
+TEST(QpackStaticTableTest, IsSingleton) {
+  const QpackStaticTable* static_table_one = &ObtainQpackStaticTable();
+  const QpackStaticTable* static_table_two = &ObtainQpackStaticTable();
+  EXPECT_EQ(static_table_one, static_table_two);
+}
+
+}  // namespace
+
+}  // namespace test
+
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_test_utils.cc b/quic/core/qpack/qpack_test_utils.cc
new file mode 100644
index 0000000..2d4a72e
--- /dev/null
+++ b/quic/core/qpack/qpack_test_utils.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h"
+
+#include <limits>
+
+namespace quic {
+namespace test {
+
+FragmentSizeGenerator FragmentModeToFragmentSizeGenerator(
+    FragmentMode fragment_mode) {
+  switch (fragment_mode) {
+    case FragmentMode::kSingleChunk:
+      return []() { return std::numeric_limits<size_t>::max(); };
+    case FragmentMode::kOctetByOctet:
+      return []() { return 1; };
+  }
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/qpack/qpack_test_utils.h b/quic/core/qpack/qpack_test_utils.h
new file mode 100644
index 0000000..65bb5a2
--- /dev/null
+++ b/quic/core/qpack/qpack_test_utils.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QPACK_QPACK_TEST_UTILS_H_
+#define QUICHE_QUIC_CORE_QPACK_QPACK_TEST_UTILS_H_
+
+#include <cstddef>
+#include <functional>
+
+namespace quic {
+namespace test {
+
+// Called repeatedly to determine the size of each fragment when encoding or
+// decoding.  Must return a positive value.
+using FragmentSizeGenerator = std::function<size_t()>;
+
+enum class FragmentMode {
+  kSingleChunk,
+  kOctetByOctet,
+};
+
+FragmentSizeGenerator FragmentModeToFragmentSizeGenerator(
+    FragmentMode fragment_mode);
+
+}  // namespace test
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QPACK_QPACK_TEST_UTILS_H_