blob: 267c6790f57ea74deadac3f38775aa09677b8602 [file] [log] [blame]
QUICHE teama6ef0a62019-03-07 20:34:33 -05001// Copyright (c) 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/third_party/quiche/src/quic/core/qpack/offline/qpack_offline_decoder.h"
6
7#include <cstdint>
vasilvv872e7a32019-03-12 16:42:44 -07008#include <string>
QUICHE teama6ef0a62019-03-07 20:34:33 -05009#include <utility>
10
11#include "net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h"
12#include "net/third_party/quiche/src/quic/core/quic_types.h"
13#include "net/third_party/quiche/src/quic/platform/api/quic_endian.h"
14#include "net/third_party/quiche/src/quic/platform/api/quic_file_utils.h"
15#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
bnc13750a82019-07-17 17:02:53 -070016#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
QUICHE teama6ef0a62019-03-07 20:34:33 -050017#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
18
19namespace quic {
20
21QpackOfflineDecoder::QpackOfflineDecoder()
22 : encoder_stream_error_detected_(false),
bnc13750a82019-07-17 17:02:53 -070023 max_blocked_streams_(0) {}
QUICHE teama6ef0a62019-03-07 20:34:33 -050024
25bool QpackOfflineDecoder::DecodeAndVerifyOfflineData(
26 QuicStringPiece input_filename,
27 QuicStringPiece expected_headers_filename) {
28 if (!ParseInputFilename(input_filename)) {
29 QUIC_LOG(ERROR) << "Error parsing input filename " << input_filename;
30 return false;
31 }
32
33 if (!DecodeHeaderBlocksFromFile(input_filename)) {
34 QUIC_LOG(ERROR) << "Error decoding header blocks in " << input_filename;
35 return false;
36 }
37
38 if (!VerifyDecodedHeaderLists(expected_headers_filename)) {
39 QUIC_LOG(ERROR) << "Header lists decoded from " << input_filename
40 << " to not match expected headers parsed from "
41 << expected_headers_filename;
42 return false;
43 }
44
45 return true;
46}
47
48void QpackOfflineDecoder::OnEncoderStreamError(QuicStringPiece error_message) {
49 QUIC_LOG(ERROR) << "Encoder stream error: " << error_message;
50 encoder_stream_error_detected_ = true;
51}
52
53bool QpackOfflineDecoder::ParseInputFilename(QuicStringPiece input_filename) {
54 auto pieces = QuicTextUtils::Split(input_filename, '.');
55
56 if (pieces.size() < 3) {
57 QUIC_LOG(ERROR) << "Not enough fields in input filename " << input_filename;
58 return false;
59 }
60
61 auto piece_it = pieces.rbegin();
62
63 // Acknowledgement mode: 1 for immediate, 0 for none.
64 bool immediate_acknowledgement = false;
65 if (*piece_it == "0") {
66 immediate_acknowledgement = false;
67 } else if (*piece_it == "1") {
68 immediate_acknowledgement = true;
69 } else {
70 QUIC_LOG(ERROR)
71 << "Header acknowledgement field must be 0 or 1 in input filename "
72 << input_filename;
73 return false;
74 }
75
76 ++piece_it;
77
78 // Maximum allowed number of blocked streams.
bnc13750a82019-07-17 17:02:53 -070079 if (!QuicTextUtils::StringToUint64(*piece_it, &max_blocked_streams_)) {
QUICHE teama6ef0a62019-03-07 20:34:33 -050080 QUIC_LOG(ERROR) << "Error parsing part of input filename \"" << *piece_it
81 << "\" as an integer.";
82 return false;
83 }
84
QUICHE teama6ef0a62019-03-07 20:34:33 -050085 ++piece_it;
86
bnc13750a82019-07-17 17:02:53 -070087 // Maximum Dynamic Table Capacity in bytes
88 uint64_t maximum_dynamic_table_capacity = 0;
89 if (!QuicTextUtils::StringToUint64(*piece_it,
90 &maximum_dynamic_table_capacity)) {
QUICHE teama6ef0a62019-03-07 20:34:33 -050091 QUIC_LOG(ERROR) << "Error parsing part of input filename \"" << *piece_it
92 << "\" as an integer.";
93 return false;
94 }
bnc4c664c52019-08-04 18:14:12 -070095 qpack_decoder_ = QuicMakeUnique<QpackDecoder>(
96 maximum_dynamic_table_capacity, max_blocked_streams_, this,
97 &decoder_stream_sender_delegate_);
QUICHE teama6ef0a62019-03-07 20:34:33 -050098
99 return true;
100}
101
102bool QpackOfflineDecoder::DecodeHeaderBlocksFromFile(
103 QuicStringPiece input_filename) {
104 // Store data in |input_data_storage|; use a QuicStringPiece to efficiently
105 // keep track of remaining portion yet to be decoded.
vasilvvc48c8712019-03-11 13:38:16 -0700106 std::string input_data_storage;
QUICHE teama6ef0a62019-03-07 20:34:33 -0500107 ReadFileContents(input_filename, &input_data_storage);
108 QuicStringPiece input_data(input_data_storage);
109
110 while (!input_data.empty()) {
bnc13750a82019-07-17 17:02:53 -0700111 // Parse stream_id and length.
QUICHE teama6ef0a62019-03-07 20:34:33 -0500112 if (input_data.size() < sizeof(uint64_t) + sizeof(uint32_t)) {
113 QUIC_LOG(ERROR) << "Unexpected end of input file.";
114 return false;
115 }
116
117 uint64_t stream_id = QuicEndian::NetToHost64(
118 *reinterpret_cast<const uint64_t*>(input_data.data()));
119 input_data = input_data.substr(sizeof(uint64_t));
120
121 uint32_t length = QuicEndian::NetToHost32(
122 *reinterpret_cast<const uint32_t*>(input_data.data()));
123 input_data = input_data.substr(sizeof(uint32_t));
124
125 if (input_data.size() < length) {
126 QUIC_LOG(ERROR) << "Unexpected end of input file.";
127 return false;
128 }
129
bnc13750a82019-07-17 17:02:53 -0700130 // Parse data.
QUICHE teama6ef0a62019-03-07 20:34:33 -0500131 QuicStringPiece data = input_data.substr(0, length);
132 input_data = input_data.substr(length);
133
bnc13750a82019-07-17 17:02:53 -0700134 // Process data.
QUICHE teama6ef0a62019-03-07 20:34:33 -0500135 if (stream_id == 0) {
bnc4c664c52019-08-04 18:14:12 -0700136 qpack_decoder_->DecodeEncoderStreamData(data);
QUICHE teama6ef0a62019-03-07 20:34:33 -0500137
138 if (encoder_stream_error_detected_) {
139 QUIC_LOG(ERROR) << "Error detected on encoder stream.";
140 return false;
141 }
bnc13750a82019-07-17 17:02:53 -0700142 } else {
143 auto headers_handler = QuicMakeUnique<test::TestHeadersHandler>();
bnc4c664c52019-08-04 18:14:12 -0700144 auto progressive_decoder = qpack_decoder_->CreateProgressiveDecoder(
bnc13750a82019-07-17 17:02:53 -0700145 stream_id, headers_handler.get());
QUICHE teama6ef0a62019-03-07 20:34:33 -0500146
bnc13750a82019-07-17 17:02:53 -0700147 progressive_decoder->Decode(data);
148 progressive_decoder->EndHeaderBlock();
149
150 if (headers_handler->decoding_error_detected()) {
151 QUIC_LOG(ERROR) << "Sync decoding error on stream " << stream_id << ": "
152 << headers_handler->error_message();
153 return false;
154 }
155
156 decoders_.push_back({std::move(headers_handler),
157 std::move(progressive_decoder), stream_id});
QUICHE teama6ef0a62019-03-07 20:34:33 -0500158 }
159
bnc13750a82019-07-17 17:02:53 -0700160 // Move decoded header lists from TestHeadersHandlers and append them to
161 // |decoded_header_lists_| while preserving the order in |decoders_|.
162 while (!decoders_.empty() &&
163 decoders_.front().headers_handler->decoding_completed()) {
164 Decoder* decoder = &decoders_.front();
QUICHE teama6ef0a62019-03-07 20:34:33 -0500165
bnc13750a82019-07-17 17:02:53 -0700166 if (decoder->headers_handler->decoding_error_detected()) {
167 QUIC_LOG(ERROR) << "Async decoding error on stream "
168 << decoder->stream_id << ": "
169 << decoder->headers_handler->error_message();
170 return false;
171 }
QUICHE teama6ef0a62019-03-07 20:34:33 -0500172
bnc13750a82019-07-17 17:02:53 -0700173 if (!decoder->headers_handler->decoding_completed()) {
174 QUIC_LOG(ERROR) << "Decoding incomplete after reading entire"
175 " file, on stream "
176 << decoder->stream_id;
177 return false;
178 }
179
180 decoded_header_lists_.push_back(
181 decoder->headers_handler->ReleaseHeaderList());
182 decoders_.pop_front();
183 }
184
185 // Enforce limit on blocked streams.
bnc4c664c52019-08-04 18:14:12 -0700186 // TODO(b/112770235): Move this logic to QpackDecoder.
bnc13750a82019-07-17 17:02:53 -0700187 uint64_t blocked_streams_count = 0;
188 for (const auto& decoder : decoders_) {
189 if (!decoder.headers_handler->decoding_completed()) {
190 ++blocked_streams_count;
191 }
192 }
193
194 if (blocked_streams_count > max_blocked_streams_) {
195 QUIC_LOG(ERROR) << "Too many blocked streams: limit is "
196 << max_blocked_streams_ << ", actual count is "
197 << blocked_streams_count;
QUICHE teama6ef0a62019-03-07 20:34:33 -0500198 return false;
199 }
bnc13750a82019-07-17 17:02:53 -0700200 }
QUICHE teama6ef0a62019-03-07 20:34:33 -0500201
bnc13750a82019-07-17 17:02:53 -0700202 if (!decoders_.empty()) {
203 DCHECK(!decoders_.front().headers_handler->decoding_completed());
204
205 QUIC_LOG(ERROR) << "Blocked decoding uncomplete after reading entire"
206 " file, on stream "
207 << decoders_.front().stream_id;
208 return false;
QUICHE teama6ef0a62019-03-07 20:34:33 -0500209 }
210
211 return true;
212}
213
214bool QpackOfflineDecoder::VerifyDecodedHeaderLists(
215 QuicStringPiece expected_headers_filename) {
216 // Store data in |expected_headers_data_storage|; use a QuicStringPiece to
217 // efficiently keep track of remaining portion yet to be decoded.
vasilvvc48c8712019-03-11 13:38:16 -0700218 std::string expected_headers_data_storage;
QUICHE teama6ef0a62019-03-07 20:34:33 -0500219 ReadFileContents(expected_headers_filename, &expected_headers_data_storage);
220 QuicStringPiece expected_headers_data(expected_headers_data_storage);
221
222 while (!decoded_header_lists_.empty()) {
223 spdy::SpdyHeaderBlock decoded_header_list =
224 std::move(decoded_header_lists_.front());
225 decoded_header_lists_.pop_front();
226
227 spdy::SpdyHeaderBlock expected_header_list;
228 if (!ReadNextExpectedHeaderList(&expected_headers_data,
229 &expected_header_list)) {
230 QUIC_LOG(ERROR)
231 << "Error parsing expected header list to match next decoded "
232 "header list.";
233 return false;
234 }
235
236 if (!CompareHeaderBlocks(std::move(decoded_header_list),
237 std::move(expected_header_list))) {
238 QUIC_LOG(ERROR) << "Decoded header does not match expected header.";
239 return false;
240 }
241 }
242
243 if (!expected_headers_data.empty()) {
244 QUIC_LOG(ERROR)
245 << "Not enough encoded header lists to match expected ones.";
246 return false;
247 }
248
249 return true;
250}
251
252bool QpackOfflineDecoder::ReadNextExpectedHeaderList(
253 QuicStringPiece* expected_headers_data,
254 spdy::SpdyHeaderBlock* expected_header_list) {
255 while (true) {
256 QuicStringPiece::size_type endline = expected_headers_data->find('\n');
257
258 // Even last header list must be followed by an empty line.
259 if (endline == QuicStringPiece::npos) {
260 QUIC_LOG(ERROR) << "Unexpected end of expected header list file.";
261 return false;
262 }
263
264 if (endline == 0) {
265 // Empty line indicates end of header list.
266 *expected_headers_data = expected_headers_data->substr(1);
267 return true;
268 }
269
270 QuicStringPiece header_field = expected_headers_data->substr(0, endline);
271 auto pieces = QuicTextUtils::Split(header_field, '\t');
272
273 if (pieces.size() != 2) {
274 QUIC_LOG(ERROR) << "Header key and value must be separated by TAB.";
275 return false;
276 }
277
278 expected_header_list->AppendValueOrAddHeader(pieces[0], pieces[1]);
279
280 *expected_headers_data = expected_headers_data->substr(endline + 1);
281 }
282}
283
284bool QpackOfflineDecoder::CompareHeaderBlocks(
285 spdy::SpdyHeaderBlock decoded_header_list,
286 spdy::SpdyHeaderBlock expected_header_list) {
287 if (decoded_header_list == expected_header_list) {
288 return true;
289 }
290
291 // The h2o decoder reshuffles the "content-length" header and pseudo-headers,
292 // see
293 // https://github.com/qpackers/qifs/blob/master/encoded/qpack-03/h2o/README.md.
294 // Remove such headers one by one if they match.
295 const char* kContentLength = "content-length";
296 const char* kPseudoHeaderPrefix = ":";
297 for (spdy::SpdyHeaderBlock::iterator decoded_it = decoded_header_list.begin();
298 decoded_it != decoded_header_list.end();) {
299 const QuicStringPiece key = decoded_it->first;
300 if (key != kContentLength &&
301 !QuicTextUtils::StartsWith(key, kPseudoHeaderPrefix)) {
302 ++decoded_it;
303 continue;
304 }
305 spdy::SpdyHeaderBlock::iterator expected_it =
306 expected_header_list.find(key);
307 if (expected_it == expected_header_list.end() ||
308 decoded_it->second != expected_it->second) {
309 ++decoded_it;
310 continue;
311 }
312 // SpdyHeaderBlock does not support erasing by iterator, only by key.
313 ++decoded_it;
314 expected_header_list.erase(key);
315 // This will invalidate |key|.
316 decoded_header_list.erase(key);
317 }
318
319 return decoded_header_list == expected_header_list;
320}
321
322} // namespace quic