• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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