blob: 3835047e8b2a93e88e7c68ea47baeb4cb14c3357 [file] [log] [blame]
QUICHE team82dee2f2019-01-18 12:35:12 -05001// Copyright 2014 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/spdy/core/hpack/hpack_encoder.h"
6
7#include <algorithm>
8#include <limits>
bnc463f2352019-10-10 04:49:34 -07009#include <utility>
QUICHE team82dee2f2019-01-18 12:35:12 -050010
QUICHE team82dee2f2019-01-18 12:35:12 -050011#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
12#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
13#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
14#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
15#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
QUICHE teamded03512019-03-07 14:45:11 -080016#include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h"
QUICHE team82dee2f2019-01-18 12:35:12 -050017
18namespace spdy {
19
20class HpackEncoder::RepresentationIterator {
21 public:
22 // |pseudo_headers| and |regular_headers| must outlive the iterator.
23 RepresentationIterator(const Representations& pseudo_headers,
24 const Representations& regular_headers)
25 : pseudo_begin_(pseudo_headers.begin()),
26 pseudo_end_(pseudo_headers.end()),
27 regular_begin_(regular_headers.begin()),
28 regular_end_(regular_headers.end()) {}
29
30 // |headers| must outlive the iterator.
31 explicit RepresentationIterator(const Representations& headers)
32 : pseudo_begin_(headers.begin()),
33 pseudo_end_(headers.end()),
34 regular_begin_(headers.end()),
35 regular_end_(headers.end()) {}
36
37 bool HasNext() {
38 return pseudo_begin_ != pseudo_end_ || regular_begin_ != regular_end_;
39 }
40
41 const Representation Next() {
42 if (pseudo_begin_ != pseudo_end_) {
43 return *pseudo_begin_++;
44 } else {
45 return *regular_begin_++;
46 }
47 }
48
49 private:
50 Representations::const_iterator pseudo_begin_;
51 Representations::const_iterator pseudo_end_;
52 Representations::const_iterator regular_begin_;
53 Representations::const_iterator regular_end_;
54};
55
56namespace {
57
58// The default header listener.
bnc7f82d042020-01-03 12:18:53 -080059void NoOpListener(quiche::QuicheStringPiece /*name*/,
60 quiche::QuicheStringPiece /*value*/) {}
QUICHE team82dee2f2019-01-18 12:35:12 -050061
62// The default HPACK indexing policy.
bnc7f82d042020-01-03 12:18:53 -080063bool DefaultPolicy(quiche::QuicheStringPiece name,
64 quiche::QuicheStringPiece /* value */) {
QUICHE team82dee2f2019-01-18 12:35:12 -050065 if (name.empty()) {
66 return false;
67 }
68 // :authority is always present and rarely changes, and has moderate
69 // length, therefore it makes a lot of sense to index (insert in the
70 // dynamic table).
71 if (name[0] == kPseudoHeaderPrefix) {
72 return name == ":authority";
73 }
74 return true;
75}
76
77} // namespace
78
79HpackEncoder::HpackEncoder(const HpackHuffmanTable& table)
80 : output_stream_(),
81 huffman_table_(table),
82 min_table_size_setting_received_(std::numeric_limits<size_t>::max()),
83 listener_(NoOpListener),
84 should_index_(DefaultPolicy),
85 enable_compression_(true),
86 should_emit_table_size_(false) {}
87
88HpackEncoder::~HpackEncoder() = default;
89
90bool HpackEncoder::EncodeHeaderSet(const SpdyHeaderBlock& header_set,
bnc44712912019-08-15 18:58:14 -070091 std::string* output) {
QUICHE team82dee2f2019-01-18 12:35:12 -050092 // Separate header set into pseudo-headers and regular headers.
93 Representations pseudo_headers;
94 Representations regular_headers;
95 bool found_cookie = false;
96 for (const auto& header : header_set) {
97 if (!found_cookie && header.first == "cookie") {
98 // Note that there can only be one "cookie" header, because header_set is
99 // a map.
100 found_cookie = true;
101 CookieToCrumbs(header, &regular_headers);
102 } else if (!header.first.empty() &&
103 header.first[0] == kPseudoHeaderPrefix) {
104 DecomposeRepresentation(header, &pseudo_headers);
105 } else {
106 DecomposeRepresentation(header, &regular_headers);
107 }
108 }
109
110 {
111 RepresentationIterator iter(pseudo_headers, regular_headers);
112 EncodeRepresentations(&iter, output);
113 }
114 return true;
115}
116
117void HpackEncoder::ApplyHeaderTableSizeSetting(size_t size_setting) {
118 if (size_setting == header_table_.settings_size_bound()) {
119 return;
120 }
121 if (size_setting < header_table_.settings_size_bound()) {
122 min_table_size_setting_received_ =
123 std::min(size_setting, min_table_size_setting_received_);
124 }
125 header_table_.SetSettingsHeaderTableSize(size_setting);
126 should_emit_table_size_ = true;
127}
128
129size_t HpackEncoder::EstimateMemoryUsage() const {
130 // |huffman_table_| is a singleton. It's accounted for in spdy_session_pool.cc
131 return SpdyEstimateMemoryUsage(header_table_) +
132 SpdyEstimateMemoryUsage(output_stream_);
133}
134
135void HpackEncoder::EncodeRepresentations(RepresentationIterator* iter,
bnc44712912019-08-15 18:58:14 -0700136 std::string* output) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500137 MaybeEmitTableSize();
138 while (iter->HasNext()) {
139 const auto header = iter->Next();
140 listener_(header.first, header.second);
141 if (enable_compression_) {
142 const HpackEntry* entry =
143 header_table_.GetByNameAndValue(header.first, header.second);
144 if (entry != nullptr) {
145 EmitIndex(entry);
146 } else if (should_index_(header.first, header.second)) {
147 EmitIndexedLiteral(header);
148 } else {
149 EmitNonIndexedLiteral(header);
150 }
151 } else {
152 EmitNonIndexedLiteral(header);
153 }
154 }
155
156 output_stream_.TakeString(output);
157}
158
159void HpackEncoder::EmitIndex(const HpackEntry* entry) {
QUICHE teamded03512019-03-07 14:45:11 -0800160 SPDY_DVLOG(2) << "Emitting index " << header_table_.IndexOf(entry);
QUICHE team82dee2f2019-01-18 12:35:12 -0500161 output_stream_.AppendPrefix(kIndexedOpcode);
162 output_stream_.AppendUint32(header_table_.IndexOf(entry));
163}
164
165void HpackEncoder::EmitIndexedLiteral(const Representation& representation) {
QUICHE teamded03512019-03-07 14:45:11 -0800166 SPDY_DVLOG(2) << "Emitting indexed literal: (" << representation.first << ", "
167 << representation.second << ")";
QUICHE team82dee2f2019-01-18 12:35:12 -0500168 output_stream_.AppendPrefix(kLiteralIncrementalIndexOpcode);
169 EmitLiteral(representation);
170 header_table_.TryAddEntry(representation.first, representation.second);
171}
172
173void HpackEncoder::EmitNonIndexedLiteral(const Representation& representation) {
QUICHE teamded03512019-03-07 14:45:11 -0800174 SPDY_DVLOG(2) << "Emitting nonindexed literal: (" << representation.first
175 << ", " << representation.second << ")";
QUICHE team82dee2f2019-01-18 12:35:12 -0500176 output_stream_.AppendPrefix(kLiteralNoIndexOpcode);
177 output_stream_.AppendUint32(0);
178 EmitString(representation.first);
179 EmitString(representation.second);
180}
181
182void HpackEncoder::EmitLiteral(const Representation& representation) {
183 const HpackEntry* name_entry = header_table_.GetByName(representation.first);
184 if (name_entry != nullptr) {
185 output_stream_.AppendUint32(header_table_.IndexOf(name_entry));
186 } else {
187 output_stream_.AppendUint32(0);
188 EmitString(representation.first);
189 }
190 EmitString(representation.second);
191}
192
bnc7f82d042020-01-03 12:18:53 -0800193void HpackEncoder::EmitString(quiche::QuicheStringPiece str) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500194 size_t encoded_size =
195 enable_compression_ ? huffman_table_.EncodedSize(str) : str.size();
196 if (encoded_size < str.size()) {
QUICHE teamded03512019-03-07 14:45:11 -0800197 SPDY_DVLOG(2) << "Emitted Huffman-encoded string of length "
198 << encoded_size;
QUICHE team82dee2f2019-01-18 12:35:12 -0500199 output_stream_.AppendPrefix(kStringLiteralHuffmanEncoded);
200 output_stream_.AppendUint32(encoded_size);
201 huffman_table_.EncodeString(str, &output_stream_);
202 } else {
QUICHE teamded03512019-03-07 14:45:11 -0800203 SPDY_DVLOG(2) << "Emitted literal string of length " << str.size();
QUICHE team82dee2f2019-01-18 12:35:12 -0500204 output_stream_.AppendPrefix(kStringLiteralIdentityEncoded);
205 output_stream_.AppendUint32(str.size());
206 output_stream_.AppendBytes(str);
207 }
208}
209
210void HpackEncoder::MaybeEmitTableSize() {
211 if (!should_emit_table_size_) {
212 return;
213 }
214 const size_t current_size = CurrentHeaderTableSizeSetting();
QUICHE teamded03512019-03-07 14:45:11 -0800215 SPDY_DVLOG(1) << "MaybeEmitTableSize current_size=" << current_size;
216 SPDY_DVLOG(1) << "MaybeEmitTableSize min_table_size_setting_received_="
217 << min_table_size_setting_received_;
QUICHE team82dee2f2019-01-18 12:35:12 -0500218 if (min_table_size_setting_received_ < current_size) {
219 output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
220 output_stream_.AppendUint32(min_table_size_setting_received_);
221 }
222 output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
223 output_stream_.AppendUint32(current_size);
224 min_table_size_setting_received_ = std::numeric_limits<size_t>::max();
225 should_emit_table_size_ = false;
226}
227
228// static
229void HpackEncoder::CookieToCrumbs(const Representation& cookie,
230 Representations* out) {
231 // See Section 8.1.2.5. "Compressing the Cookie Header Field" in the HTTP/2
232 // specification at https://tools.ietf.org/html/draft-ietf-httpbis-http2-14.
233 // Cookie values are split into individually-encoded HPACK representations.
bnc7f82d042020-01-03 12:18:53 -0800234 quiche::QuicheStringPiece cookie_value = cookie.second;
QUICHE team82dee2f2019-01-18 12:35:12 -0500235 // Consume leading and trailing whitespace if present.
bnc7f82d042020-01-03 12:18:53 -0800236 quiche::QuicheStringPiece::size_type first =
237 cookie_value.find_first_not_of(" \t");
238 quiche::QuicheStringPiece::size_type last =
239 cookie_value.find_last_not_of(" \t");
240 if (first == quiche::QuicheStringPiece::npos) {
241 cookie_value = quiche::QuicheStringPiece();
QUICHE team82dee2f2019-01-18 12:35:12 -0500242 } else {
243 cookie_value = cookie_value.substr(first, (last - first) + 1);
244 }
245 for (size_t pos = 0;;) {
246 size_t end = cookie_value.find(";", pos);
247
bnc7f82d042020-01-03 12:18:53 -0800248 if (end == quiche::QuicheStringPiece::npos) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500249 out->push_back(std::make_pair(cookie.first, cookie_value.substr(pos)));
250 break;
251 }
252 out->push_back(
253 std::make_pair(cookie.first, cookie_value.substr(pos, end - pos)));
254
255 // Consume next space if present.
256 pos = end + 1;
257 if (pos != cookie_value.size() && cookie_value[pos] == ' ') {
258 pos++;
259 }
260 }
261}
262
263// static
264void HpackEncoder::DecomposeRepresentation(const Representation& header_field,
265 Representations* out) {
266 size_t pos = 0;
267 size_t end = 0;
bnc7f82d042020-01-03 12:18:53 -0800268 while (end != quiche::QuicheStringPiece::npos) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500269 end = header_field.second.find('\0', pos);
270 out->push_back(std::make_pair(
271 header_field.first,
272 header_field.second.substr(
bnc7f82d042020-01-03 12:18:53 -0800273 pos, end == quiche::QuicheStringPiece::npos ? end : end - pos)));
QUICHE team82dee2f2019-01-18 12:35:12 -0500274 pos = end + 1;
275 }
276}
277
QUICHE team82dee2f2019-01-18 12:35:12 -0500278// Iteratively encodes a SpdyHeaderBlock.
279class HpackEncoder::Encoderator : public ProgressiveEncoder {
280 public:
281 Encoderator(const SpdyHeaderBlock& header_set, HpackEncoder* encoder);
QUICHE team6ddfcae2019-12-11 21:31:23 -0800282 Encoderator(const Representations& representations, HpackEncoder* encoder);
QUICHE team82dee2f2019-01-18 12:35:12 -0500283
284 // Encoderator is neither copyable nor movable.
285 Encoderator(const Encoderator&) = delete;
286 Encoderator& operator=(const Encoderator&) = delete;
287
288 // Returns true iff more remains to encode.
289 bool HasNext() const override { return has_next_; }
290
291 // Encodes up to max_encoded_bytes of the current header block into the
292 // given output string.
bnc44712912019-08-15 18:58:14 -0700293 void Next(size_t max_encoded_bytes, std::string* output) override;
QUICHE team82dee2f2019-01-18 12:35:12 -0500294
295 private:
296 HpackEncoder* encoder_;
297 std::unique_ptr<RepresentationIterator> header_it_;
298 Representations pseudo_headers_;
299 Representations regular_headers_;
300 bool has_next_;
301};
302
303HpackEncoder::Encoderator::Encoderator(const SpdyHeaderBlock& header_set,
304 HpackEncoder* encoder)
305 : encoder_(encoder), has_next_(true) {
306 // Separate header set into pseudo-headers and regular headers.
QUICHE team82dee2f2019-01-18 12:35:12 -0500307 bool found_cookie = false;
308 for (const auto& header : header_set) {
309 if (!found_cookie && header.first == "cookie") {
310 // Note that there can only be one "cookie" header, because header_set
311 // is a map.
312 found_cookie = true;
313 CookieToCrumbs(header, &regular_headers_);
314 } else if (!header.first.empty() &&
315 header.first[0] == kPseudoHeaderPrefix) {
QUICHE team992004d2019-12-12 09:40:29 -0800316 DecomposeRepresentation(header, &pseudo_headers_);
QUICHE team82dee2f2019-01-18 12:35:12 -0500317 } else {
QUICHE team992004d2019-12-12 09:40:29 -0800318 DecomposeRepresentation(header, &regular_headers_);
QUICHE team82dee2f2019-01-18 12:35:12 -0500319 }
320 }
bnc463f2352019-10-10 04:49:34 -0700321 header_it_ = std::make_unique<RepresentationIterator>(pseudo_headers_,
322 regular_headers_);
QUICHE team82dee2f2019-01-18 12:35:12 -0500323
324 encoder_->MaybeEmitTableSize();
325}
326
QUICHE team6ddfcae2019-12-11 21:31:23 -0800327HpackEncoder::Encoderator::Encoderator(const Representations& representations,
328 HpackEncoder* encoder)
329 : encoder_(encoder), has_next_(true) {
330 for (const auto& header : representations) {
331 if (header.first == "cookie") {
332 CookieToCrumbs(header, &regular_headers_);
333 } else if (!header.first.empty() &&
334 header.first[0] == kPseudoHeaderPrefix) {
335 pseudo_headers_.push_back(header);
336 } else {
337 regular_headers_.push_back(header);
338 }
339 }
340 header_it_ = std::make_unique<RepresentationIterator>(pseudo_headers_,
341 regular_headers_);
342
343 encoder_->MaybeEmitTableSize();
344}
345
QUICHE team82dee2f2019-01-18 12:35:12 -0500346void HpackEncoder::Encoderator::Next(size_t max_encoded_bytes,
bnc44712912019-08-15 18:58:14 -0700347 std::string* output) {
QUICHE team82dee2f2019-01-18 12:35:12 -0500348 SPDY_BUG_IF(!has_next_)
349 << "Encoderator::Next called with nothing left to encode.";
350 const bool use_compression = encoder_->enable_compression_;
351
352 // Encode up to max_encoded_bytes of headers.
353 while (header_it_->HasNext() &&
354 encoder_->output_stream_.size() <= max_encoded_bytes) {
355 const Representation header = header_it_->Next();
356 encoder_->listener_(header.first, header.second);
357 if (use_compression) {
358 const HpackEntry* entry = encoder_->header_table_.GetByNameAndValue(
359 header.first, header.second);
360 if (entry != nullptr) {
361 encoder_->EmitIndex(entry);
362 } else if (encoder_->should_index_(header.first, header.second)) {
363 encoder_->EmitIndexedLiteral(header);
364 } else {
365 encoder_->EmitNonIndexedLiteral(header);
366 }
367 } else {
368 encoder_->EmitNonIndexedLiteral(header);
369 }
370 }
371
372 has_next_ = encoder_->output_stream_.size() > max_encoded_bytes;
373 encoder_->output_stream_.BoundedTakeString(max_encoded_bytes, output);
374}
375
376std::unique_ptr<HpackEncoder::ProgressiveEncoder> HpackEncoder::EncodeHeaderSet(
377 const SpdyHeaderBlock& header_set) {
bnc463f2352019-10-10 04:49:34 -0700378 return std::make_unique<Encoderator>(header_set, this);
QUICHE team82dee2f2019-01-18 12:35:12 -0500379}
380
QUICHE team6ddfcae2019-12-11 21:31:23 -0800381std::unique_ptr<HpackEncoder::ProgressiveEncoder>
382HpackEncoder::EncodeRepresentations(const Representations& representations) {
383 return std::make_unique<Encoderator>(representations, this);
384}
385
QUICHE team82dee2f2019-01-18 12:35:12 -0500386} // namespace spdy