• 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_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