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