|  | // 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 |