diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-24 11:40:17 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-24 12:42:11 +0000 |
commit | 5d87695f37678f96492b258bbab36486c59866b4 (patch) | |
tree | be9783bbaf04fb930c4d74ca9c00b5e7954c8bc6 /chromium/net/third_party/quiche/src/quic/core/qpack | |
parent | 6c11fb357ec39bf087b8b632e2b1e375aef1b38b (diff) | |
download | qtwebengine-chromium-5d87695f37678f96492b258bbab36486c59866b4.tar.gz |
BASELINE: Update Chromium to 75.0.3770.56
Change-Id: I86d2007fd27a45d5797eee06f4c9369b8b50ac4f
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/net/third_party/quiche/src/quic/core/qpack')
50 files changed, 6552 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/offline/qpack_offline_decoder.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/offline/qpack_offline_decoder.cc new file mode 100644 index 00000000000..42f572b3eac --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 <string> +#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_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. + std::string 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. + 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/chromium/net/third_party/quiche/src/quic/core/qpack/offline/qpack_offline_decoder.h b/chromium/net/third_party/quiche/src/quic/core/qpack/offline/qpack_offline_decoder.h new file mode 100644 index 00000000000..922fd64835b --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/offline/qpack_offline_decoder_bin.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/offline/qpack_offline_decoder_bin.cc new file mode 100644 index 00000000000..a4f5373911c --- /dev/null +++ b/chromium/net/third_party/quiche/src/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<std::string> 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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_constants.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_constants.cc new file mode 100644 index 00000000000..dd4487d7060 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 "net/third_party/quiche/src/quic/platform/api/quic_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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_constants.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_constants.h new file mode 100644 index 00000000000..2812e63302d --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 <string> +#include <tuple> +#include <vector> + +#include "net/third_party/quiche/src/quic/platform/api/quic_export.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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator.cc new file mode 100644 index 00000000000..bcfe0e4e188 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator.cc @@ -0,0 +1,72 @@ +// 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, + size_t max_header_list_size) + : decoder_(qpack_decoder->DecodeHeaderBlock(id, this)), + uncompressed_header_bytes_(0), + compressed_header_bytes_(0), + error_detected_(false) { + quic_header_list_.set_max_header_list_size(max_header_list_size); + 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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator.h new file mode 100644 index 00000000000..5db88d71b33 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator.h @@ -0,0 +1,66 @@ +// 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 <string> + +#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_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, + size_t max_header_list_size); + 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_; + std::string error_message_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_QPACK_QPACK_DECODED_HEADERS_ACCUMULATOR_H_ diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc new file mode 100644 index 00000000000..c3c0b8101b0 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc @@ -0,0 +1,119 @@ +// 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::ElementsAre; +using ::testing::Eq; +using ::testing::Pair; +using ::testing::StrictMock; + +namespace quic { +namespace test { +namespace { + +// Arbitrary stream ID used for testing. +QuicStreamId kTestStreamId = 1; + +// Limit on header list size. +const size_t kMaxHeaderListSize = 100; + +// 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_, kMaxHeaderListSize) {} + + 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))); + + std::string encoded_data(QuicTextUtils::HexDecode("000023666f6f03626172")); + EXPECT_TRUE(accumulator_.Decode(encoded_data)); + EXPECT_TRUE(accumulator_.EndHeaderBlock()); + + const QuicHeaderList& header_list = accumulator_.quic_header_list(); + EXPECT_THAT(header_list, ElementsAre(Pair("foo", "bar"))); + + EXPECT_EQ(strlen("foo") + strlen("bar"), + header_list.uncompressed_header_bytes()); + EXPECT_EQ(encoded_data.size(), header_list.compressed_header_bytes()); +} + +TEST_F(QpackDecodedHeadersAccumulatorTest, ExceedingLimit) { + EXPECT_CALL(decoder_stream_sender_delegate_, + WriteDecoderStreamData(Eq(kHeaderAcknowledgement))); + + // Total length of header list exceeds kMaxHeaderListSize. + EXPECT_TRUE(accumulator_.Decode(QuicTextUtils::HexDecode( + "0000" // header block prefix + "26666f6f626172" // header key: "foobar" + "7d61616161616161616161616161616161616161" // header value: 'a' 125 times + "616161616161616161616161616161616161616161616161616161616161616161616161" + "616161616161616161616161616161616161616161616161616161616161616161616161" + "61616161616161616161616161616161616161616161616161616161616161616161"))); + EXPECT_TRUE(accumulator_.EndHeaderBlock()); + + // QuicHeaderList signals header list over limit by clearing it. + EXPECT_TRUE(accumulator_.quic_header_list().empty()); +} + +} // namespace test +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder.cc new file mode 100644 index 00000000000..9efccb6c029 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 "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 { + +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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h new file mode 100644 index 00000000000..c3b28a3e10b --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_receiver.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_receiver.cc new file mode 100644 index 00000000000..559ce433376 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_receiver.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_receiver.h new file mode 100644 index 00000000000..61c2773a62e --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_receiver_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_receiver_test.cc new file mode 100644 index 00000000000..fc7225f8139 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender.cc new file mode 100644 index 00000000000..9474d6cdf93 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 <string> + +#include "net/third_party/quiche/src/quic/core/qpack/qpack_constants.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_logging.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()); + + std::string 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()); + + std::string 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()); + + std::string output; + + instruction_encoder_.Next(std::numeric_limits<size_t>::max(), &output); + DCHECK(!instruction_encoder_.HasNext()); + + delegate_->WriteDecoderStreamData(output); +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender.h new file mode 100644 index 00000000000..a791173f801 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender_test.cc new file mode 100644 index 00000000000..cc957591083 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test.cc new file mode 100644 index 00000000000..8d83f0a8bc0 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test.cc @@ -0,0 +1,703 @@ +// 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 "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_logging.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"))); + std::string 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. + std::string header_value(600, 'Z'); + DecodeEncoderStreamData(header_value); + + // Duplicate most recent entry 200 times. + DecodeEncoderStreamData(std::string(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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.cc new file mode 100644 index 00000000000..e8bbd178e62 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.h new file mode 100644 index 00000000000..ca5b60818fa --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder.cc new file mode 100644 index 00000000000..108ffd57ee4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder.cc @@ -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. + +#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder.h" + +#include <string> + +#include "net/third_party/quiche/src/quic/core/qpack/qpack_progressive_encoder.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" +#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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder.h new file mode 100644 index 00000000000..4e655329b5f --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_receiver.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_receiver.cc new file mode 100644 index 00000000000..3f8ef08a7ee --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_receiver.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_receiver.h new file mode 100644 index 00000000000..5519b4c67a2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 <string> + +#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_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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_receiver_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_receiver_test.cc new file mode 100644 index 00000000000..dcb2039ccfd --- /dev/null +++ b/chromium/net/third_party/quiche/src/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(std::string(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(std::string(31, 'Z')), + Eq(std::string(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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.cc new file mode 100644 index 00000000000..3fb3b33f35e --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 <string> + +#include "net/third_party/quiche/src/quic/core/qpack/qpack_constants.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_logging.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()); + + std::string 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()); + + std::string 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()); + + std::string 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()); + + std::string output; + + instruction_encoder_.Next(std::numeric_limits<size_t>::max(), &output); + DCHECK(!instruction_encoder_.HasNext()); + + delegate_->WriteEncoderStreamData(output); +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h new file mode 100644 index 00000000000..ad3456889bd --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender_test.cc new file mode 100644 index 00000000000..a2e73763dea --- /dev/null +++ b/chromium/net/third_party/quiche/src/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, std::string(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(std::string(31, 'Z'), + std::string(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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test.cc new file mode 100644 index 00000000000..fb07d49e1ad --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test.cc @@ -0,0 +1,168 @@ +// 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 <string> + +#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_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; + + std::string 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; + std::string output = Encode(&header_list); + + EXPECT_EQ(QuicTextUtils::HexDecode("0000"), output); +} + +TEST_P(QpackEncoderTest, EmptyName) { + spdy::SpdyHeaderBlock header_list; + header_list[""] = "foo"; + std::string output = Encode(&header_list); + + EXPECT_EQ(QuicTextUtils::HexDecode("0000208294e7"), output); +} + +TEST_P(QpackEncoderTest, EmptyValue) { + spdy::SpdyHeaderBlock header_list; + header_list["foo"] = ""; + std::string output = Encode(&header_list); + + EXPECT_EQ(QuicTextUtils::HexDecode("00002a94e700"), output); +} + +TEST_P(QpackEncoderTest, EmptyNameAndValue) { + spdy::SpdyHeaderBlock header_list; + header_list[""] = ""; + std::string output = Encode(&header_list); + + EXPECT_EQ(QuicTextUtils::HexDecode("00002000"), output); +} + +TEST_P(QpackEncoderTest, Simple) { + spdy::SpdyHeaderBlock header_list; + header_list["foo"] = "bar"; + std::string 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"] = std::string(127, 'Z'); + std::string 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"] = ""; + + std::string 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"; + + std::string output = Encode(&header_list); + EXPECT_EQ(QuicTextUtils::HexDecode("0000d45f108621e9aec2a11f5c8294e7"), + output); + } + { + spdy::SpdyHeaderBlock header_list; + header_list[":method"] = "TRACE"; + header_list["accept-encoding"] = ""; + + std::string 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. + std::string 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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test_utils.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test_utils.cc new file mode 100644 index 00000000000..dd1ccb31256 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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) {} + +std::string 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); + + std::string output; + while (progressive_encoder->HasNext()) { + progressive_encoder->Next(fragment_size_generator(), &output); + } + + return output; +} + +} // namespace test +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test_utils.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test_utils.h new file mode 100644 index 00000000000..3c9b404a1d4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test_utils.h @@ -0,0 +1,64 @@ +// 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 <string> + +#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_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)); +}; + +std::string 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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_header_table.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_header_table.cc new file mode 100644 index 00000000000..01943301c66 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 "net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_logging.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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h new file mode 100644 index 00000000000..eda7ab525a2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_header_table_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_header_table_test.cc new file mode 100644 index 00000000000..f3ac5b5d3ef --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.cc new file mode 100644 index 00000000000..2076fa7c990 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 "net/third_party/quiche/src/quic/platform/api/quic_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; + } + + std::string* 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); + + std::string* 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); + + std::string* 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. + std::string 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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.h new file mode 100644 index 00000000000..f478c249b07 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 <string> + +#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_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 std::string& name() const { return name_; } + const std::string& 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_; + std::string name_; + std::string 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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder_test.cc new file mode 100644 index 00000000000..f0d1d1d0564 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 "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_logging.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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder.cc new file mode 100644 index 00000000000..3ae38f65697 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_logging.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, + std::string* 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, + std::string* 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, + std::string* 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, + std::string* 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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder.h new file mode 100644 index 00000000000..917378ed8f3 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 <string> + +#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_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, std::string* 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, std::string* output); + size_t DoVarintResume(size_t max_encoded_bytes, std::string* output); + void DoStartString(); + size_t DoWriteString(size_t max_encoded_bytes, std::string* 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. + std::string 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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder_test.cc new file mode 100644 index 00000000000..8f6aeaab97a --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 "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_logging.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_|. + std::string EncodeInstruction(const QpackInstruction* instruction) { + EXPECT_FALSE(encoder_.HasNext()); + + FragmentSizeGenerator fragment_size_generator = + FragmentModeToFragmentSizeGenerator(fragment_mode_); + std::string 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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder.cc new file mode 100644 index 00000000000..05a5fc03d8a --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 "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_logging.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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder.h new file mode 100644 index 00000000000..52adf45b236 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 <string> + +#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_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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder_test.cc new file mode 100644 index 00000000000..b3067023b97 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_progressive_encoder.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_progressive_encoder.cc new file mode 100644 index 00000000000..05a025b28be --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_progressive_encoder.cc @@ -0,0 +1,139 @@ +// 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 <string> + +#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_logging.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, + std::string* 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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_progressive_encoder.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_progressive_encoder.h new file mode 100644 index 00000000000..8e204e23b38 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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, std::string* 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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_round_trip_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_round_trip_test.cc new file mode 100644 index 00000000000..9b2df550b88 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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 <string> +#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_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; + std::string 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"] = std::string(127, 'Z'); + header_list[std::string(1000, 'b')] = std::string(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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_static_table.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_static_table.cc new file mode 100644 index 00000000000..f1986f798ad --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h new file mode 100644 index 00000000000..d8c255568a4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_static_table_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_static_table_test.cc new file mode 100644 index 00000000000..50289f2e298 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.cc new file mode 100644 index 00000000000..2d4a72e71b0 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h new file mode 100644 index 00000000000..65bb5a20ded --- /dev/null +++ b/chromium/net/third_party/quiche/src/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_ |