1 // 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 "quiche/quic/core/qpack/qpack_encoder.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "absl/strings/str_cat.h"
11 #include "absl/strings/string_view.h"
12 #include "quiche/quic/core/qpack/qpack_index_conversions.h"
13 #include "quiche/quic/core/qpack/qpack_instruction_encoder.h"
14 #include "quiche/quic/core/qpack/qpack_required_insert_count.h"
15 #include "quiche/quic/core/qpack/value_splitting_header_list.h"
16 #include "quiche/quic/platform/api/quic_logging.h"
17
18 namespace quic {
19
20 namespace {
21
22 // Fraction to calculate draining index. The oldest |kDrainingFraction| entries
23 // will not be referenced in header blocks. A new entry (duplicate or literal
24 // with name reference) will be added to the dynamic table instead. This allows
25 // the number of references to the draining entry to go to zero faster, so that
26 // it can be evicted. See
27 // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#avoiding-blocked-insertions.
28 // TODO(bnc): Fine tune.
29 const float kDrainingFraction = 0.25;
30
31 } // anonymous namespace
32
QpackEncoder(DecoderStreamErrorDelegate * decoder_stream_error_delegate)33 QpackEncoder::QpackEncoder(
34 DecoderStreamErrorDelegate* decoder_stream_error_delegate)
35 : decoder_stream_error_delegate_(decoder_stream_error_delegate),
36 decoder_stream_receiver_(this),
37 maximum_blocked_streams_(0),
38 header_list_count_(0) {
39 QUICHE_DCHECK(decoder_stream_error_delegate_);
40 }
41
~QpackEncoder()42 QpackEncoder::~QpackEncoder() {}
43
44 // static
EncodeIndexedHeaderField(bool is_static,uint64_t index,QpackBlockingManager::IndexSet * referred_indices)45 QpackEncoder::Representation QpackEncoder::EncodeIndexedHeaderField(
46 bool is_static, uint64_t index,
47 QpackBlockingManager::IndexSet* referred_indices) {
48 // Add |index| to |*referred_indices| only if entry is in the dynamic table.
49 if (!is_static) {
50 referred_indices->insert(index);
51 }
52 return Representation::IndexedHeaderField(is_static, index);
53 }
54
55 // static
56 QpackEncoder::Representation
EncodeLiteralHeaderFieldWithNameReference(bool is_static,uint64_t index,absl::string_view value,QpackBlockingManager::IndexSet * referred_indices)57 QpackEncoder::EncodeLiteralHeaderFieldWithNameReference(
58 bool is_static, uint64_t index, absl::string_view value,
59 QpackBlockingManager::IndexSet* referred_indices) {
60 // Add |index| to |*referred_indices| only if entry is in the dynamic table.
61 if (!is_static) {
62 referred_indices->insert(index);
63 }
64 return Representation::LiteralHeaderFieldNameReference(is_static, index,
65 value);
66 }
67
68 // static
EncodeLiteralHeaderField(absl::string_view name,absl::string_view value)69 QpackEncoder::Representation QpackEncoder::EncodeLiteralHeaderField(
70 absl::string_view name, absl::string_view value) {
71 return Representation::LiteralHeaderField(name, value);
72 }
73
FirstPassEncode(QuicStreamId stream_id,const spdy::Http2HeaderBlock & header_list,QpackBlockingManager::IndexSet * referred_indices,QuicByteCount * encoder_stream_sent_byte_count)74 QpackEncoder::Representations QpackEncoder::FirstPassEncode(
75 QuicStreamId stream_id, const spdy::Http2HeaderBlock& header_list,
76 QpackBlockingManager::IndexSet* referred_indices,
77 QuicByteCount* encoder_stream_sent_byte_count) {
78 // If previous instructions are buffered in |encoder_stream_sender_|,
79 // do not count them towards the current header block.
80 const QuicByteCount initial_encoder_stream_buffered_byte_count =
81 encoder_stream_sender_.BufferedByteCount();
82
83 const bool can_write_to_encoder_stream = encoder_stream_sender_.CanWrite();
84
85 Representations representations;
86 representations.reserve(header_list.size());
87
88 // The index of the oldest entry that must not be evicted.
89 uint64_t smallest_blocking_index =
90 blocking_manager_.smallest_blocking_index();
91 // Entries with index larger than or equal to |known_received_count| are
92 // blocking.
93 const uint64_t known_received_count =
94 blocking_manager_.known_received_count();
95 // Only entries with index greater than or equal to |draining_index| are
96 // allowed to be referenced.
97 const uint64_t draining_index =
98 header_table_.draining_index(kDrainingFraction);
99 // Blocking references are allowed if the number of blocked streams is less
100 // than the limit.
101 const bool blocking_allowed = blocking_manager_.blocking_allowed_on_stream(
102 stream_id, maximum_blocked_streams_);
103
104 // Track events for histograms.
105 bool dynamic_table_insertion_blocked = false;
106 bool blocked_stream_limit_exhausted = false;
107
108 for (const auto& header : ValueSplittingHeaderList(&header_list)) {
109 // These strings are owned by |header_list|.
110 absl::string_view name = header.first;
111 absl::string_view value = header.second;
112
113 bool is_static;
114 uint64_t index;
115
116 auto match_type =
117 header_table_.FindHeaderField(name, value, &is_static, &index);
118
119 switch (match_type) {
120 case QpackEncoderHeaderTable::MatchType::kNameAndValue:
121 if (is_static) {
122 // Refer to entry directly.
123 representations.push_back(
124 EncodeIndexedHeaderField(is_static, index, referred_indices));
125
126 break;
127 }
128
129 if (index >= draining_index) {
130 // If allowed, refer to entry directly.
131 if (!blocking_allowed && index >= known_received_count) {
132 blocked_stream_limit_exhausted = true;
133 } else {
134 representations.push_back(
135 EncodeIndexedHeaderField(is_static, index, referred_indices));
136 smallest_blocking_index = std::min(smallest_blocking_index, index);
137 header_table_.set_dynamic_table_entry_referenced();
138
139 break;
140 }
141 } else {
142 // Entry is draining, needs to be duplicated.
143 if (!blocking_allowed) {
144 blocked_stream_limit_exhausted = true;
145 } else if (QpackEntry::Size(name, value) >
146 header_table_.MaxInsertSizeWithoutEvictingGivenEntry(
147 std::min(smallest_blocking_index, index))) {
148 dynamic_table_insertion_blocked = true;
149 } else {
150 if (can_write_to_encoder_stream) {
151 // If allowed, duplicate entry and refer to it.
152 encoder_stream_sender_.SendDuplicate(
153 QpackAbsoluteIndexToEncoderStreamRelativeIndex(
154 index, header_table_.inserted_entry_count()));
155 uint64_t new_index = header_table_.InsertEntry(name, value);
156 representations.push_back(EncodeIndexedHeaderField(
157 is_static, new_index, referred_indices));
158 smallest_blocking_index =
159 std::min(smallest_blocking_index, index);
160 header_table_.set_dynamic_table_entry_referenced();
161
162 break;
163 }
164 }
165 }
166
167 // Encode entry as string literals.
168 // TODO(b/112770235): Use already acknowledged entry with lower index if
169 // exists.
170 // TODO(b/112770235): Use static entry name with literal value if
171 // dynamic entry exists but cannot be used.
172 representations.push_back(EncodeLiteralHeaderField(name, value));
173
174 break;
175
176 case QpackEncoderHeaderTable::MatchType::kName:
177 if (is_static) {
178 if (blocking_allowed &&
179 QpackEntry::Size(name, value) <=
180 header_table_.MaxInsertSizeWithoutEvictingGivenEntry(
181 smallest_blocking_index)) {
182 // If allowed, insert entry into dynamic table and refer to it.
183 if (can_write_to_encoder_stream) {
184 encoder_stream_sender_.SendInsertWithNameReference(is_static,
185 index, value);
186 uint64_t new_index = header_table_.InsertEntry(name, value);
187 representations.push_back(EncodeIndexedHeaderField(
188 /* is_static = */ false, new_index, referred_indices));
189 smallest_blocking_index =
190 std::min<uint64_t>(smallest_blocking_index, new_index);
191
192 break;
193 }
194 }
195
196 // Emit literal field with name reference.
197 representations.push_back(EncodeLiteralHeaderFieldWithNameReference(
198 is_static, index, value, referred_indices));
199
200 break;
201 }
202
203 if (!blocking_allowed) {
204 blocked_stream_limit_exhausted = true;
205 } else if (QpackEntry::Size(name, value) >
206 header_table_.MaxInsertSizeWithoutEvictingGivenEntry(
207 std::min(smallest_blocking_index, index))) {
208 dynamic_table_insertion_blocked = true;
209 } else {
210 // If allowed, insert entry with name reference and refer to it.
211 if (can_write_to_encoder_stream) {
212 encoder_stream_sender_.SendInsertWithNameReference(
213 is_static,
214 QpackAbsoluteIndexToEncoderStreamRelativeIndex(
215 index, header_table_.inserted_entry_count()),
216 value);
217 uint64_t new_index = header_table_.InsertEntry(name, value);
218 representations.push_back(EncodeIndexedHeaderField(
219 is_static, new_index, referred_indices));
220 smallest_blocking_index = std::min(smallest_blocking_index, index);
221 header_table_.set_dynamic_table_entry_referenced();
222
223 break;
224 }
225 }
226
227 if ((blocking_allowed || index < known_received_count) &&
228 index >= draining_index) {
229 // If allowed, refer to entry name directly, with literal value.
230 representations.push_back(EncodeLiteralHeaderFieldWithNameReference(
231 is_static, index, value, referred_indices));
232 smallest_blocking_index = std::min(smallest_blocking_index, index);
233 header_table_.set_dynamic_table_entry_referenced();
234
235 break;
236 }
237
238 // Encode entry as string literals.
239 // TODO(b/112770235): Use already acknowledged entry with lower index if
240 // exists.
241 // TODO(b/112770235): Use static entry name with literal value if
242 // dynamic entry exists but cannot be used.
243 representations.push_back(EncodeLiteralHeaderField(name, value));
244
245 break;
246
247 case QpackEncoderHeaderTable::MatchType::kNoMatch:
248 // If allowed, insert entry and refer to it.
249 if (!blocking_allowed) {
250 blocked_stream_limit_exhausted = true;
251 } else if (QpackEntry::Size(name, value) >
252 header_table_.MaxInsertSizeWithoutEvictingGivenEntry(
253 smallest_blocking_index)) {
254 dynamic_table_insertion_blocked = true;
255 } else {
256 if (can_write_to_encoder_stream) {
257 encoder_stream_sender_.SendInsertWithoutNameReference(name, value);
258 uint64_t new_index = header_table_.InsertEntry(name, value);
259 representations.push_back(EncodeIndexedHeaderField(
260 /* is_static = */ false, new_index, referred_indices));
261 smallest_blocking_index =
262 std::min<uint64_t>(smallest_blocking_index, new_index);
263
264 break;
265 }
266 }
267
268 // Encode entry as string literals.
269 // TODO(b/112770235): Consider also adding to dynamic table to improve
270 // compression ratio for subsequent header blocks with peers that do not
271 // allow any blocked streams.
272 representations.push_back(EncodeLiteralHeaderField(name, value));
273
274 break;
275 }
276 }
277
278 const QuicByteCount encoder_stream_buffered_byte_count =
279 encoder_stream_sender_.BufferedByteCount();
280 QUICHE_DCHECK_GE(encoder_stream_buffered_byte_count,
281 initial_encoder_stream_buffered_byte_count);
282
283 if (encoder_stream_sent_byte_count) {
284 *encoder_stream_sent_byte_count =
285 encoder_stream_buffered_byte_count -
286 initial_encoder_stream_buffered_byte_count;
287 }
288 if (can_write_to_encoder_stream) {
289 encoder_stream_sender_.Flush();
290 } else {
291 QUICHE_DCHECK_EQ(encoder_stream_buffered_byte_count,
292 initial_encoder_stream_buffered_byte_count);
293 }
294
295 ++header_list_count_;
296
297 if (dynamic_table_insertion_blocked) {
298 QUIC_HISTOGRAM_COUNTS(
299 "QuicSession.Qpack.HeaderListCountWhenInsertionBlocked",
300 header_list_count_, /* min = */ 1, /* max = */ 1000,
301 /* bucket_count = */ 50,
302 "The ordinality of a header list within a connection during the "
303 "encoding of which at least one dynamic table insertion was "
304 "blocked.");
305 } else {
306 QUIC_HISTOGRAM_COUNTS(
307 "QuicSession.Qpack.HeaderListCountWhenInsertionNotBlocked",
308 header_list_count_, /* min = */ 1, /* max = */ 1000,
309 /* bucket_count = */ 50,
310 "The ordinality of a header list within a connection during the "
311 "encoding of which no dynamic table insertion was blocked.");
312 }
313
314 if (blocked_stream_limit_exhausted) {
315 QUIC_HISTOGRAM_COUNTS(
316 "QuicSession.Qpack.HeaderListCountWhenBlockedStreamLimited",
317 header_list_count_, /* min = */ 1, /* max = */ 1000,
318 /* bucket_count = */ 50,
319 "The ordinality of a header list within a connection during the "
320 "encoding of which unacknowledged dynamic table entries could not be "
321 "referenced due to the limit on the number of blocked streams.");
322 } else {
323 QUIC_HISTOGRAM_COUNTS(
324 "QuicSession.Qpack.HeaderListCountWhenNotBlockedStreamLimited",
325 header_list_count_, /* min = */ 1, /* max = */ 1000,
326 /* bucket_count = */ 50,
327 "The ordinality of a header list within a connection during the "
328 "encoding of which the limit on the number of blocked streams did "
329 "not "
330 "prevent referencing unacknowledged dynamic table entries.");
331 }
332
333 return representations;
334 }
335
SecondPassEncode(QpackEncoder::Representations representations,uint64_t required_insert_count) const336 std::string QpackEncoder::SecondPassEncode(
337 QpackEncoder::Representations representations,
338 uint64_t required_insert_count) const {
339 QpackInstructionEncoder instruction_encoder;
340 std::string encoded_headers;
341
342 // Header block prefix.
343 instruction_encoder.Encode(
344 Representation::Prefix(QpackEncodeRequiredInsertCount(
345 required_insert_count, header_table_.max_entries())),
346 &encoded_headers);
347
348 const uint64_t base = required_insert_count;
349
350 for (auto& representation : representations) {
351 // Dynamic table references must be transformed from absolute to relative
352 // indices.
353 if ((representation.instruction() == QpackIndexedHeaderFieldInstruction() ||
354 representation.instruction() ==
355 QpackLiteralHeaderFieldNameReferenceInstruction()) &&
356 !representation.s_bit()) {
357 representation.set_varint(QpackAbsoluteIndexToRequestStreamRelativeIndex(
358 representation.varint(), base));
359 }
360 instruction_encoder.Encode(representation, &encoded_headers);
361 }
362
363 return encoded_headers;
364 }
365
EncodeHeaderList(QuicStreamId stream_id,const spdy::Http2HeaderBlock & header_list,QuicByteCount * encoder_stream_sent_byte_count)366 std::string QpackEncoder::EncodeHeaderList(
367 QuicStreamId stream_id, const spdy::Http2HeaderBlock& header_list,
368 QuicByteCount* encoder_stream_sent_byte_count) {
369 // Keep track of all dynamic table indices that this header block refers to so
370 // that it can be passed to QpackBlockingManager.
371 QpackBlockingManager::IndexSet referred_indices;
372
373 // First pass: encode into |representations|.
374 Representations representations =
375 FirstPassEncode(stream_id, header_list, &referred_indices,
376 encoder_stream_sent_byte_count);
377
378 const uint64_t required_insert_count =
379 referred_indices.empty()
380 ? 0
381 : QpackBlockingManager::RequiredInsertCount(referred_indices);
382 if (!referred_indices.empty()) {
383 blocking_manager_.OnHeaderBlockSent(stream_id, std::move(referred_indices));
384 }
385
386 // Second pass.
387 return SecondPassEncode(std::move(representations), required_insert_count);
388 }
389
SetMaximumDynamicTableCapacity(uint64_t maximum_dynamic_table_capacity)390 bool QpackEncoder::SetMaximumDynamicTableCapacity(
391 uint64_t maximum_dynamic_table_capacity) {
392 return header_table_.SetMaximumDynamicTableCapacity(
393 maximum_dynamic_table_capacity);
394 }
395
SetDynamicTableCapacity(uint64_t dynamic_table_capacity)396 void QpackEncoder::SetDynamicTableCapacity(uint64_t dynamic_table_capacity) {
397 encoder_stream_sender_.SendSetDynamicTableCapacity(dynamic_table_capacity);
398 // Do not flush encoder stream. This write can safely be delayed until more
399 // instructions are written.
400
401 bool success = header_table_.SetDynamicTableCapacity(dynamic_table_capacity);
402 QUICHE_DCHECK(success);
403 }
404
SetMaximumBlockedStreams(uint64_t maximum_blocked_streams)405 bool QpackEncoder::SetMaximumBlockedStreams(uint64_t maximum_blocked_streams) {
406 if (maximum_blocked_streams < maximum_blocked_streams_) {
407 return false;
408 }
409 maximum_blocked_streams_ = maximum_blocked_streams;
410 return true;
411 }
412
OnInsertCountIncrement(uint64_t increment)413 void QpackEncoder::OnInsertCountIncrement(uint64_t increment) {
414 if (increment == 0) {
415 OnErrorDetected(QUIC_QPACK_DECODER_STREAM_INVALID_ZERO_INCREMENT,
416 "Invalid increment value 0.");
417 return;
418 }
419
420 if (!blocking_manager_.OnInsertCountIncrement(increment)) {
421 OnErrorDetected(QUIC_QPACK_DECODER_STREAM_INCREMENT_OVERFLOW,
422 "Insert Count Increment instruction causes overflow.");
423 }
424
425 if (blocking_manager_.known_received_count() >
426 header_table_.inserted_entry_count()) {
427 OnErrorDetected(QUIC_QPACK_DECODER_STREAM_IMPOSSIBLE_INSERT_COUNT,
428 absl::StrCat("Increment value ", increment,
429 " raises known received count to ",
430 blocking_manager_.known_received_count(),
431 " exceeding inserted entry count ",
432 header_table_.inserted_entry_count()));
433 }
434 }
435
OnHeaderAcknowledgement(QuicStreamId stream_id)436 void QpackEncoder::OnHeaderAcknowledgement(QuicStreamId stream_id) {
437 if (!blocking_manager_.OnHeaderAcknowledgement(stream_id)) {
438 OnErrorDetected(
439 QUIC_QPACK_DECODER_STREAM_INCORRECT_ACKNOWLEDGEMENT,
440 absl::StrCat("Header Acknowledgement received for stream ", stream_id,
441 " with no outstanding header blocks."));
442 }
443 }
444
OnStreamCancellation(QuicStreamId stream_id)445 void QpackEncoder::OnStreamCancellation(QuicStreamId stream_id) {
446 blocking_manager_.OnStreamCancellation(stream_id);
447 }
448
OnErrorDetected(QuicErrorCode error_code,absl::string_view error_message)449 void QpackEncoder::OnErrorDetected(QuicErrorCode error_code,
450 absl::string_view error_message) {
451 decoder_stream_error_delegate_->OnDecoderStreamError(error_code,
452 error_message);
453 }
454
455 } // namespace quic
456