Project import generated by Copybara.
PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
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;
+}