Move QPACK test utilities to test_tools/qpack/.

Move test utility libraries from core/qpack to test_tools/qpack.
Move offline decoder lib from core/qpack/offline to test_tools/qpack.
Move offline decoder bin from core/qpack/offline to core/qpack.
Move two peers from test_tools to test_tools/qpack.

gfe-relnote: n/a, change to QUIC v99-only code.  Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 276793722
Change-Id: Iae5a90f2ab3fc9e0e36510b23dfd31666a6a9ccb
diff --git a/quic/test_tools/qpack/qpack_decoder_test_utils.cc b/quic/test_tools/qpack/qpack_decoder_test_utils.cc
new file mode 100644
index 0000000..2b835ae
--- /dev/null
+++ b/quic/test_tools/qpack/qpack_decoder_test_utils.cc
@@ -0,0 +1,88 @@
+// 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/test_tools/qpack/qpack_decoder_test_utils.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+
+namespace quic {
+namespace test {
+
+void NoopEncoderStreamErrorDelegate::OnEncoderStreamError(
+    QuicStringPiece /*error_message*/) {}
+
+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;
+  error_message_.assign(error_message.data(), error_message.size());
+}
+
+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_;
+}
+
+const std::string& TestHeadersHandler::error_message() const {
+  DCHECK(decoding_error_detected_);
+  return error_message_;
+}
+
+void QpackDecode(
+    uint64_t maximum_dynamic_table_capacity,
+    uint64_t maximum_blocked_streams,
+    QpackDecoder::EncoderStreamErrorDelegate* encoder_stream_error_delegate,
+    QpackStreamSenderDelegate* decoder_stream_sender_delegate,
+    QpackProgressiveDecoder::HeadersHandlerInterface* handler,
+    const FragmentSizeGenerator& fragment_size_generator,
+    QuicStringPiece data) {
+  QpackDecoder decoder(maximum_dynamic_table_capacity, maximum_blocked_streams,
+                       encoder_stream_error_delegate);
+  decoder.set_qpack_stream_sender_delegate(decoder_stream_sender_delegate);
+  auto progressive_decoder =
+      decoder.CreateProgressiveDecoder(/* 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/test_tools/qpack/qpack_decoder_test_utils.h b/quic/test_tools/qpack/qpack_decoder_test_utils.h
new file mode 100644
index 0000000..213aded
--- /dev/null
+++ b/quic/test_tools/qpack/qpack_decoder_test_utils.h
@@ -0,0 +1,103 @@
+// 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_TEST_TOOLS_QPACK_QPACK_DECODER_TEST_UTILS_H_
+#define QUICHE_QUIC_TEST_TOOLS_QPACK_QPACK_DECODER_TEST_UTILS_H_
+
+#include <string>
+
+#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/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_test_utils.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));
+};
+
+// 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;
+  const std::string& error_message() const;
+
+ private:
+  spdy::SpdyHeaderBlock header_list_;
+  bool decoding_completed_;
+  bool decoding_error_detected_;
+  std::string error_message_;
+};
+
+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(
+    uint64_t maximum_dynamic_table_capacity,
+    uint64_t maximum_blocked_streams,
+    QpackDecoder::EncoderStreamErrorDelegate* encoder_stream_error_delegate,
+    QpackStreamSenderDelegate* decoder_stream_sender_delegate,
+    QpackProgressiveDecoder::HeadersHandlerInterface* handler,
+    const FragmentSizeGenerator& fragment_size_generator,
+    QuicStringPiece data);
+
+}  // namespace test
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TEST_TOOLS_QPACK_QPACK_DECODER_TEST_UTILS_H_
diff --git a/quic/test_tools/qpack/qpack_encoder_peer.cc b/quic/test_tools/qpack/qpack_encoder_peer.cc
new file mode 100644
index 0000000..709686a
--- /dev/null
+++ b/quic/test_tools/qpack/qpack_encoder_peer.cc
@@ -0,0 +1,30 @@
+// 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/test_tools/qpack/qpack_encoder_peer.h"
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder.h"
+
+namespace quic {
+namespace test {
+
+// static
+QpackHeaderTable* QpackEncoderPeer::header_table(QpackEncoder* encoder) {
+  return &encoder->header_table_;
+}
+
+// static
+uint64_t QpackEncoderPeer::maximum_blocked_streams(
+    const QpackEncoder* encoder) {
+  return encoder->maximum_blocked_streams_;
+}
+
+// static
+uint64_t QpackEncoderPeer::smallest_blocking_index(
+    const QpackEncoder* encoder) {
+  return encoder->blocking_manager_.smallest_blocking_index();
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/test_tools/qpack/qpack_encoder_peer.h b/quic/test_tools/qpack/qpack_encoder_peer.h
new file mode 100644
index 0000000..a824276
--- /dev/null
+++ b/quic/test_tools/qpack/qpack_encoder_peer.h
@@ -0,0 +1,30 @@
+// 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_TEST_TOOLS_QPACK_QPACK_ENCODER_PEER_H_
+#define QUICHE_QUIC_TEST_TOOLS_QPACK_QPACK_ENCODER_PEER_H_
+
+#include <cstdint>
+
+namespace quic {
+
+class QpackEncoder;
+class QpackHeaderTable;
+
+namespace test {
+
+class QpackEncoderPeer {
+ public:
+  QpackEncoderPeer() = delete;
+
+  static QpackHeaderTable* header_table(QpackEncoder* encoder);
+  static uint64_t maximum_blocked_streams(const QpackEncoder* encoder);
+  static uint64_t smallest_blocking_index(const QpackEncoder* encoder);
+};
+
+}  // namespace test
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TEST_TOOLS_QPACK_QPACK_ENCODER_PEER_H_
diff --git a/quic/test_tools/qpack/qpack_encoder_test_utils.cc b/quic/test_tools/qpack/qpack_encoder_test_utils.cc
new file mode 100644
index 0000000..dbdd369
--- /dev/null
+++ b/quic/test_tools/qpack/qpack_encoder_test_utils.cc
@@ -0,0 +1,16 @@
+// 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/test_tools/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*/) {}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/test_tools/qpack/qpack_encoder_test_utils.h b/quic/test_tools/qpack/qpack_encoder_test_utils.h
new file mode 100644
index 0000000..5fa2292
--- /dev/null
+++ b/quic/test_tools/qpack/qpack_encoder_test_utils.h
@@ -0,0 +1,40 @@
+// 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_TEST_TOOLS_QPACK_QPACK_ENCODER_TEST_UTILS_H_
+#define QUICHE_QUIC_TEST_TOOLS_QPACK_QPACK_ENCODER_TEST_UTILS_H_
+
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder.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/quic/test_tools/qpack/qpack_test_utils.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));
+};
+
+}  // namespace test
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TEST_TOOLS_QPACK_QPACK_ENCODER_TEST_UTILS_H_
diff --git a/quic/test_tools/qpack/qpack_header_table_peer.cc b/quic/test_tools/qpack/qpack_header_table_peer.cc
new file mode 100644
index 0000000..c554a97
--- /dev/null
+++ b/quic/test_tools/qpack/qpack_header_table_peer.cc
@@ -0,0 +1,25 @@
+// 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/test_tools/qpack/qpack_header_table_peer.h"
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h"
+
+namespace quic {
+namespace test {
+
+// static
+uint64_t QpackHeaderTablePeer::dynamic_table_capacity(
+    const QpackHeaderTable* header_table) {
+  return header_table->dynamic_table_capacity_;
+}
+
+// static
+uint64_t QpackHeaderTablePeer::maximum_dynamic_table_capacity(
+    const QpackHeaderTable* header_table) {
+  return header_table->maximum_dynamic_table_capacity_;
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/test_tools/qpack/qpack_header_table_peer.h b/quic/test_tools/qpack/qpack_header_table_peer.h
new file mode 100644
index 0000000..19e8d0d
--- /dev/null
+++ b/quic/test_tools/qpack/qpack_header_table_peer.h
@@ -0,0 +1,29 @@
+// 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_TEST_TOOLS_QPACK_QPACK_HEADER_TABLE_PEER_H_
+#define QUICHE_QUIC_TEST_TOOLS_QPACK_QPACK_HEADER_TABLE_PEER_H_
+
+#include <cstdint>
+
+namespace quic {
+
+class QpackHeaderTable;
+
+namespace test {
+
+class QpackHeaderTablePeer {
+ public:
+  QpackHeaderTablePeer() = delete;
+
+  static uint64_t dynamic_table_capacity(const QpackHeaderTable* header_table);
+  static uint64_t maximum_dynamic_table_capacity(
+      const QpackHeaderTable* header_table);
+};
+
+}  // namespace test
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TEST_TOOLS_QPACK_QPACK_HEADER_TABLE_PEER_H_
diff --git a/quic/test_tools/qpack/qpack_offline_decoder.cc b/quic/test_tools/qpack/qpack_offline_decoder.cc
new file mode 100644
index 0000000..1472dd4
--- /dev/null
+++ b/quic/test_tools/qpack/qpack_offline_decoder.cc
@@ -0,0 +1,334 @@
+// 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.
+
+// Decoder to test QPACK Offline Interop corpus
+//
+// See https://github.com/quicwg/base-drafts/wiki/QPACK-Offline-Interop for
+// description of test data format.
+//
+// Example usage
+//
+//  cd $TEST_DATA
+//  git clone https://github.com/qpackers/qifs.git
+//  TEST_ENCODED_DATA=$TEST_DATA/qifs/encoded/qpack-06
+//  TEST_QIF_DATA=$TEST_DATA/qifs/qifs
+//  $BIN/qpack_offline_decoder \
+//      $TEST_ENCODED_DATA/f5/fb-req.qifencoded.4096.100.0 \
+//      $TEST_QIF_DATA/fb-req.qif
+//      $TEST_ENCODED_DATA/h2o/fb-req-hq.out.512.0.1 \
+//      $TEST_QIF_DATA/fb-req-hq.qif
+//      $TEST_ENCODED_DATA/ls-qpack/fb-resp-hq.out.0.0.0 \
+//      $TEST_QIF_DATA/fb-resp-hq.qif
+//      $TEST_ENCODED_DATA/proxygen/netbsd.qif.proxygen.out.4096.0.0 \
+//      $TEST_QIF_DATA/netbsd.qif
+//
+
+#include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_offline_decoder.h"
+
+#include <cstdint>
+#include <string>
+#include <utility>
+
+#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_text_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_test_utils.h"
+
+namespace quic {
+
+QpackOfflineDecoder::QpackOfflineDecoder()
+    : encoder_stream_error_detected_(false) {}
+
+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;
+  }
+
+  ++piece_it;
+
+  // Maximum Dynamic Table Capacity in bytes
+  uint64_t maximum_dynamic_table_capacity = 0;
+  if (!QuicTextUtils::StringToUint64(*piece_it,
+                                     &maximum_dynamic_table_capacity)) {
+    QUIC_LOG(ERROR) << "Error parsing part of input filename \"" << *piece_it
+                    << "\" as an integer.";
+    return false;
+  }
+  qpack_decoder_ = std::make_unique<QpackDecoder>(
+      maximum_dynamic_table_capacity, max_blocked_streams, this);
+  qpack_decoder_->set_qpack_stream_sender_delegate(
+      &decoder_stream_sender_delegate_);
+
+  // The initial dynamic table capacity is zero according to
+  // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#eviction.
+  // However, for historical reasons, offline interop encoders use
+  // |maximum_dynamic_table_capacity| as initial capacity.
+  qpack_decoder_->OnSetDynamicTableCapacity(maximum_dynamic_table_capacity);
+
+  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.
+  std::string input_data_storage;
+  ReadFileContents(input_filename, &input_data_storage);
+  QuicStringPiece input_data(input_data_storage);
+
+  while (!input_data.empty()) {
+    // Parse stream_id and length.
+    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;
+    }
+
+    // Parse data.
+    QuicStringPiece data = input_data.substr(0, length);
+    input_data = input_data.substr(length);
+
+    // Process data.
+    if (stream_id == 0) {
+      qpack_decoder_->encoder_stream_receiver()->Decode(data);
+
+      if (encoder_stream_error_detected_) {
+        QUIC_LOG(ERROR) << "Error detected on encoder stream.";
+        return false;
+      }
+    } else {
+      auto headers_handler = std::make_unique<test::TestHeadersHandler>();
+      auto progressive_decoder = qpack_decoder_->CreateProgressiveDecoder(
+          stream_id, headers_handler.get());
+
+      progressive_decoder->Decode(data);
+      progressive_decoder->EndHeaderBlock();
+
+      if (headers_handler->decoding_error_detected()) {
+        QUIC_LOG(ERROR) << "Sync decoding error on stream " << stream_id << ": "
+                        << headers_handler->error_message();
+        return false;
+      }
+
+      decoders_.push_back({std::move(headers_handler),
+                           std::move(progressive_decoder), stream_id});
+    }
+
+    // Move decoded header lists from TestHeadersHandlers and append them to
+    // |decoded_header_lists_| while preserving the order in |decoders_|.
+    while (!decoders_.empty() &&
+           decoders_.front().headers_handler->decoding_completed()) {
+      Decoder* decoder = &decoders_.front();
+
+      if (decoder->headers_handler->decoding_error_detected()) {
+        QUIC_LOG(ERROR) << "Async decoding error on stream "
+                        << decoder->stream_id << ": "
+                        << decoder->headers_handler->error_message();
+        return false;
+      }
+
+      if (!decoder->headers_handler->decoding_completed()) {
+        QUIC_LOG(ERROR) << "Decoding incomplete after reading entire"
+                           " file, on stream "
+                        << decoder->stream_id;
+        return false;
+      }
+
+      decoded_header_lists_.push_back(
+          decoder->headers_handler->ReleaseHeaderList());
+      decoders_.pop_front();
+    }
+  }
+
+  if (!decoders_.empty()) {
+    DCHECK(!decoders_.front().headers_handler->decoding_completed());
+
+    QUIC_LOG(ERROR) << "Blocked decoding uncomplete after reading entire"
+                       " file, on stream "
+                    << decoders_.front().stream_id;
+    return false;
+  }
+
+  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.
+  std::string 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/test_tools/qpack/qpack_offline_decoder.h b/quic/test_tools/qpack/qpack_offline_decoder.h
new file mode 100644
index 0000000..e292385
--- /dev/null
+++ b/quic/test_tools/qpack/qpack_offline_decoder.h
@@ -0,0 +1,87 @@
+// 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_TEST_TOOLS_QPACK_QPACK_OFFLINE_DECODER_H_
+#define QUICHE_QUIC_TEST_TOOLS_QPACK_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_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_decoder_test_utils.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 /*QUIC_EXPORT_PRIVATE*/ 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:
+  // Data structure to hold TestHeadersHandler and QpackProgressiveDecoder until
+  // decoding of a header header block (and all preceding header blocks) is
+  // complete.
+  struct /*QUIC_EXPORT_PRIVATE*/ Decoder {
+    std::unique_ptr<test::TestHeadersHandler> headers_handler;
+    std::unique_ptr<QpackProgressiveDecoder> progressive_decoder;
+    uint64_t stream_id;
+  };
+
+  // Parse decoder parameters from |input_filename| and set up |qpack_decoder_|
+  // accordingly.
+  bool ParseInputFilename(QuicStringPiece input_filename);
+
+  // Read encoded header blocks and encoder stream data from |input_filename|,
+  // pass them to |qpack_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_;
+  NoopQpackStreamSenderDelegate decoder_stream_sender_delegate_;
+  std::unique_ptr<QpackDecoder> qpack_decoder_;
+
+  // Objects necessary for decoding, one list element for each header block.
+  std::list<Decoder> decoders_;
+
+  // Decoded header lists.
+  std::list<spdy::SpdyHeaderBlock> decoded_header_lists_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TEST_TOOLS_QPACK_QPACK_OFFLINE_DECODER_H_
diff --git a/quic/test_tools/qpack/qpack_test_utils.cc b/quic/test_tools/qpack/qpack_test_utils.cc
new file mode 100644
index 0000000..faaddcb
--- /dev/null
+++ b/quic/test_tools/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/test_tools/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/test_tools/qpack/qpack_test_utils.h b/quic/test_tools/qpack/qpack_test_utils.h
new file mode 100644
index 0000000..d112e0d
--- /dev/null
+++ b/quic/test_tools/qpack/qpack_test_utils.h
@@ -0,0 +1,41 @@
+// 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_TEST_TOOLS_QPACK_QPACK_TEST_UTILS_H_
+#define QUICHE_QUIC_TEST_TOOLS_QPACK_QPACK_TEST_UTILS_H_
+
+#include <cstddef>
+#include <functional>
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_stream_sender_delegate.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+
+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);
+
+// Mock QpackUnidirectionalStreamSenderDelegate implementation.
+class /*QUIC_EXPORT_PRIVATE*/ MockQpackStreamSenderDelegate
+    : public QpackStreamSenderDelegate {
+ public:
+  ~MockQpackStreamSenderDelegate() override = default;
+
+  MOCK_METHOD1(WriteStreamData, void(QuicStringPiece data));
+};
+
+}  // namespace test
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TEST_TOOLS_QPACK_QPACK_TEST_UTILS_H_