xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/quic/core/qpack/qpack_header_table.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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 #ifndef QUICHE_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_
6 #define QUICHE_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_
7 
8 #include <cstdint>
9 #include <deque>
10 
11 #include "absl/strings/string_view.h"
12 #include "quiche/quic/platform/api/quic_export.h"
13 #include "quiche/common/quiche_circular_deque.h"
14 #include "quiche/spdy/core/hpack/hpack_entry.h"
15 #include "quiche/spdy/core/hpack/hpack_header_table.h"
16 
17 namespace quic {
18 
19 using QpackEntry = spdy::HpackEntry;
20 using QpackLookupEntry = spdy::HpackLookupEntry;
21 constexpr size_t kQpackEntrySizeOverhead = spdy::kHpackEntrySizeOverhead;
22 
23 // Encoder needs pointer stability for |dynamic_index_| and
24 // |dynamic_name_index_|.  However, it does not need random access.
25 using QpackEncoderDynamicTable =
26     quiche::QuicheCircularDeque<std::unique_ptr<QpackEntry>>;
27 
28 // Decoder needs random access for LookupEntry().
29 // However, it does not need pointer stability.
30 using QpackDecoderDynamicTable = quiche::QuicheCircularDeque<QpackEntry>;
31 
32 // This is a base class for encoder and decoder classes that manage the QPACK
33 // static and dynamic tables.  For dynamic entries, it only has a concept of
34 // absolute indices.  The caller needs to perform the necessary transformations
35 // to and from relative indices and post-base indices.
36 template <typename DynamicEntryTable>
37 class QUICHE_EXPORT QpackHeaderTableBase {
38  public:
39   QpackHeaderTableBase();
40   QpackHeaderTableBase(const QpackHeaderTableBase&) = delete;
41   QpackHeaderTableBase& operator=(const QpackHeaderTableBase&) = delete;
42 
43   virtual ~QpackHeaderTableBase() = default;
44 
45   // Returns whether an entry with |name| and |value| has a size (including
46   // overhead) that is smaller than or equal to the capacity of the dynamic
47   // table.
48   bool EntryFitsDynamicTableCapacity(absl::string_view name,
49                                      absl::string_view value) const;
50 
51   // Inserts (name, value) into the dynamic table.  Entry must not be larger
52   // than the capacity of the dynamic table.  May evict entries.  |name| and
53   // |value| are copied first, therefore it is safe for them to point to an
54   // entry in the dynamic table, even if it is about to be evicted, or even if
55   // the underlying container might move entries around when resizing for
56   // insertion.
57   // Returns the absolute index of the inserted dynamic table entry.
58   virtual uint64_t InsertEntry(absl::string_view name, absl::string_view value);
59 
60   // Change dynamic table capacity to |capacity|.  Returns true on success.
61   // Returns false is |capacity| exceeds maximum dynamic table capacity.
62   bool SetDynamicTableCapacity(uint64_t capacity);
63 
64   // Set |maximum_dynamic_table_capacity_|.  The initial value is zero.  The
65   // final value is determined by the decoder and is sent to the encoder as
66   // SETTINGS_HEADER_TABLE_SIZE.  Therefore in the decoding context the final
67   // value can be set upon connection establishment, whereas in the encoding
68   // context it can be set when the SETTINGS frame is received.
69   // This method must only be called at most once.
70   // Returns true if |maximum_dynamic_table_capacity| is set for the first time
71   // or if it doesn't change current value. The setting is not changed when
72   // returning false.
73   bool SetMaximumDynamicTableCapacity(uint64_t maximum_dynamic_table_capacity);
74 
dynamic_table_size()75   uint64_t dynamic_table_size() const { return dynamic_table_size_; }
dynamic_table_capacity()76   uint64_t dynamic_table_capacity() const { return dynamic_table_capacity_; }
maximum_dynamic_table_capacity()77   uint64_t maximum_dynamic_table_capacity() const {
78     return maximum_dynamic_table_capacity_;
79   }
max_entries()80   uint64_t max_entries() const { return max_entries_; }
81 
82   // The number of entries inserted to the dynamic table (including ones that
83   // were dropped since).  Used for relative indexing on the encoder stream.
inserted_entry_count()84   uint64_t inserted_entry_count() const {
85     return dynamic_entries_.size() + dropped_entry_count_;
86   }
87 
88   // The number of entries dropped from the dynamic table.
dropped_entry_count()89   uint64_t dropped_entry_count() const { return dropped_entry_count_; }
90 
set_dynamic_table_entry_referenced()91   void set_dynamic_table_entry_referenced() {
92     dynamic_table_entry_referenced_ = true;
93   }
dynamic_table_entry_referenced()94   bool dynamic_table_entry_referenced() const {
95     return dynamic_table_entry_referenced_;
96   }
97 
98  protected:
99   // Removes a single entry from the end of the dynamic table, updates
100   // |dynamic_table_size_| and |dropped_entry_count_|.
101   virtual void RemoveEntryFromEnd();
102 
dynamic_entries()103   const DynamicEntryTable& dynamic_entries() const { return dynamic_entries_; }
104 
105  private:
106   // Evict entries from the dynamic table until table size is less than or equal
107   // to |capacity|.
108   void EvictDownToCapacity(uint64_t capacity);
109 
110   // Dynamic Table entries.
111   DynamicEntryTable dynamic_entries_;
112 
113   // Size of the dynamic table.  This is the sum of the size of its entries.
114   uint64_t dynamic_table_size_;
115 
116   // Dynamic Table Capacity is the maximum allowed value of
117   // |dynamic_table_size_|.  Entries are evicted if necessary before inserting a
118   // new entry to ensure that dynamic table size never exceeds capacity.
119   // Initial value is |maximum_dynamic_table_capacity_|.  Capacity can be
120   // changed by the encoder, as long as it does not exceed
121   // |maximum_dynamic_table_capacity_|.
122   uint64_t dynamic_table_capacity_;
123 
124   // Maximum allowed value of |dynamic_table_capacity|.  The initial value is
125   // zero.  Can be changed by SetMaximumDynamicTableCapacity().
126   uint64_t maximum_dynamic_table_capacity_;
127 
128   // MaxEntries, see Section 3.2.2.  Calculated based on
129   // |maximum_dynamic_table_capacity_|.  Used on request streams to encode and
130   // decode Required Insert Count.
131   uint64_t max_entries_;
132 
133   // The number of entries dropped from the dynamic table.
134   uint64_t dropped_entry_count_;
135 
136   // True if any dynamic table entries have been referenced from a header block.
137   // Set directly by the encoder or decoder.  Used for stats.
138   bool dynamic_table_entry_referenced_;
139 };
140 
141 template <typename DynamicEntryTable>
QpackHeaderTableBase()142 QpackHeaderTableBase<DynamicEntryTable>::QpackHeaderTableBase()
143     : dynamic_table_size_(0),
144       dynamic_table_capacity_(0),
145       maximum_dynamic_table_capacity_(0),
146       max_entries_(0),
147       dropped_entry_count_(0),
148       dynamic_table_entry_referenced_(false) {}
149 
150 template <typename DynamicEntryTable>
EntryFitsDynamicTableCapacity(absl::string_view name,absl::string_view value)151 bool QpackHeaderTableBase<DynamicEntryTable>::EntryFitsDynamicTableCapacity(
152     absl::string_view name, absl::string_view value) const {
153   return QpackEntry::Size(name, value) <= dynamic_table_capacity_;
154 }
155 
156 namespace internal {
157 
GetSize(const QpackEntry & entry)158 QUICHE_EXPORT inline size_t GetSize(const QpackEntry& entry) {
159   return entry.Size();
160 }
161 
GetSize(const std::unique_ptr<QpackEntry> & entry)162 QUICHE_EXPORT inline size_t GetSize(const std::unique_ptr<QpackEntry>& entry) {
163   return entry->Size();
164 }
165 
NewEntry(std::string name,std::string value,QpackEncoderDynamicTable &)166 QUICHE_EXPORT inline std::unique_ptr<QpackEntry> NewEntry(
167     std::string name, std::string value, QpackEncoderDynamicTable& /*t*/) {
168   return std::make_unique<QpackEntry>(std::move(name), std::move(value));
169 }
170 
NewEntry(std::string name,std::string value,QpackDecoderDynamicTable &)171 QUICHE_EXPORT inline QpackEntry NewEntry(std::string name, std::string value,
172                                          QpackDecoderDynamicTable& /*t*/) {
173   return QpackEntry{std::move(name), std::move(value)};
174 }
175 
176 }  // namespace internal
177 
178 template <typename DynamicEntryTable>
InsertEntry(absl::string_view name,absl::string_view value)179 uint64_t QpackHeaderTableBase<DynamicEntryTable>::InsertEntry(
180     absl::string_view name, absl::string_view value) {
181   QUICHE_DCHECK(EntryFitsDynamicTableCapacity(name, value));
182 
183   const uint64_t index = dropped_entry_count_ + dynamic_entries_.size();
184 
185   // Copy name and value before modifying the container, because evicting
186   // entries or even inserting a new one might invalidate |name| or |value| if
187   // they point to an entry.
188   auto new_entry = internal::NewEntry(std::string(name), std::string(value),
189                                       dynamic_entries_);
190   const size_t entry_size = internal::GetSize(new_entry);
191   EvictDownToCapacity(dynamic_table_capacity_ - entry_size);
192 
193   dynamic_table_size_ += entry_size;
194   dynamic_entries_.push_back(std::move(new_entry));
195 
196   return index;
197 }
198 
199 template <typename DynamicEntryTable>
SetDynamicTableCapacity(uint64_t capacity)200 bool QpackHeaderTableBase<DynamicEntryTable>::SetDynamicTableCapacity(
201     uint64_t capacity) {
202   if (capacity > maximum_dynamic_table_capacity_) {
203     return false;
204   }
205 
206   dynamic_table_capacity_ = capacity;
207   EvictDownToCapacity(capacity);
208 
209   QUICHE_DCHECK_LE(dynamic_table_size_, dynamic_table_capacity_);
210 
211   return true;
212 }
213 
214 template <typename DynamicEntryTable>
SetMaximumDynamicTableCapacity(uint64_t maximum_dynamic_table_capacity)215 bool QpackHeaderTableBase<DynamicEntryTable>::SetMaximumDynamicTableCapacity(
216     uint64_t maximum_dynamic_table_capacity) {
217   if (maximum_dynamic_table_capacity_ == 0) {
218     maximum_dynamic_table_capacity_ = maximum_dynamic_table_capacity;
219     max_entries_ = maximum_dynamic_table_capacity / 32;
220     return true;
221   }
222   // If the value is already set, it should not be changed.
223   return maximum_dynamic_table_capacity == maximum_dynamic_table_capacity_;
224 }
225 
226 template <typename DynamicEntryTable>
RemoveEntryFromEnd()227 void QpackHeaderTableBase<DynamicEntryTable>::RemoveEntryFromEnd() {
228   const uint64_t entry_size = internal::GetSize(dynamic_entries_.front());
229   QUICHE_DCHECK_GE(dynamic_table_size_, entry_size);
230   dynamic_table_size_ -= entry_size;
231 
232   dynamic_entries_.pop_front();
233   ++dropped_entry_count_;
234 }
235 
236 template <typename DynamicEntryTable>
EvictDownToCapacity(uint64_t capacity)237 void QpackHeaderTableBase<DynamicEntryTable>::EvictDownToCapacity(
238     uint64_t capacity) {
239   while (dynamic_table_size_ > capacity) {
240     QUICHE_DCHECK(!dynamic_entries_.empty());
241     RemoveEntryFromEnd();
242   }
243 }
244 
245 class QUICHE_EXPORT QpackEncoderHeaderTable
246     : public QpackHeaderTableBase<QpackEncoderDynamicTable> {
247  public:
248   // Result of header table lookup.
249   enum class MatchType { kNameAndValue, kName, kNoMatch };
250 
251   QpackEncoderHeaderTable();
252   ~QpackEncoderHeaderTable() override = default;
253 
254   uint64_t InsertEntry(absl::string_view name,
255                        absl::string_view value) override;
256 
257   // Returns the absolute index of an entry with matching name and value if such
258   // exists, otherwise one with matching name is such exists.  |index| is zero
259   // based for both the static and the dynamic table.
260   MatchType FindHeaderField(absl::string_view name, absl::string_view value,
261                             bool* is_static, uint64_t* index) const;
262 
263   // Returns the size of the largest entry that could be inserted into the
264   // dynamic table without evicting entry |index|.  |index| might be larger than
265   // inserted_entry_count(), in which case the capacity of the table is
266   // returned.  |index| must not be smaller than dropped_entry_count().
267   uint64_t MaxInsertSizeWithoutEvictingGivenEntry(uint64_t index) const;
268 
269   // Returns the draining index described at
270   // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#avoiding-blocked-insertions.
271   // Entries with an index larger than or equal to the draining index take up
272   // approximately |1.0 - draining_fraction| of dynamic table capacity.  The
273   // remaining capacity is taken up by draining entries and unused space.
274   // The returned index might not be the index of a valid entry.
275   uint64_t draining_index(float draining_fraction) const;
276 
277  protected:
278   void RemoveEntryFromEnd() override;
279 
280  private:
281   using NameValueToEntryMap = spdy::HpackHeaderTable::NameValueToEntryMap;
282   using NameToEntryMap = spdy::HpackHeaderTable::NameToEntryMap;
283 
284   // Static Table
285 
286   // |static_index_| and |static_name_index_| are owned by QpackStaticTable
287   // singleton.
288 
289   // Tracks the unique static entry for a given header name and value.
290   const NameValueToEntryMap& static_index_;
291 
292   // Tracks the first static entry for a given header name.
293   const NameToEntryMap& static_name_index_;
294 
295   // Dynamic Table
296 
297   // An unordered set of QpackEntry pointers with a comparison operator that
298   // only cares about name and value.  This allows fast lookup of the most
299   // recently inserted dynamic entry for a given header name and value pair.
300   // Entries point to entries owned by |QpackHeaderTableBase::dynamic_entries_|.
301   NameValueToEntryMap dynamic_index_;
302 
303   // An unordered map of QpackEntry pointers keyed off header name.  This allows
304   // fast lookup of the most recently inserted dynamic entry for a given header
305   // name.  Entries point to entries owned by
306   // |QpackHeaderTableBase::dynamic_entries_|.
307   NameToEntryMap dynamic_name_index_;
308 };
309 
310 class QUICHE_EXPORT QpackDecoderHeaderTable
311     : public QpackHeaderTableBase<QpackDecoderDynamicTable> {
312  public:
313   // Observer interface for dynamic table insertion.
314   class QUICHE_EXPORT Observer {
315    public:
316     virtual ~Observer() = default;
317 
318     // Called when inserted_entry_count() reaches the threshold the Observer was
319     // registered with.  After this call the Observer automatically gets
320     // deregistered.
321     virtual void OnInsertCountReachedThreshold() = 0;
322 
323     // Called when QpackDecoderHeaderTable is destroyed to let the Observer know
324     // that it must not call UnregisterObserver().
325     virtual void Cancel() = 0;
326   };
327 
328   QpackDecoderHeaderTable();
329   ~QpackDecoderHeaderTable() override;
330 
331   uint64_t InsertEntry(absl::string_view name,
332                        absl::string_view value) override;
333 
334   // Returns the entry at absolute index |index| from the static or dynamic
335   // table according to |is_static|.  |index| is zero based for both the static
336   // and the dynamic table.  The returned pointer is valid until the entry is
337   // evicted, even if other entries are inserted into the dynamic table.
338   // Returns nullptr if entry does not exist.
339   const QpackEntry* LookupEntry(bool is_static, uint64_t index) const;
340 
341   // Register an observer to be notified when inserted_entry_count() reaches
342   // |required_insert_count|.  After the notification, |observer| automatically
343   // gets unregistered.  Each observer must only be registered at most once.
344   void RegisterObserver(uint64_t required_insert_count, Observer* observer);
345 
346   // Unregister previously registered observer.  Must be called with the same
347   // |required_insert_count| value that |observer| was registered with.  Must be
348   // called before an observer still waiting for notification is destroyed,
349   // unless QpackDecoderHeaderTable already called Observer::Cancel(), in which
350   // case this method must not be called.
351   void UnregisterObserver(uint64_t required_insert_count, Observer* observer);
352 
353  private:
354   // Static Table entries.  Owned by QpackStaticTable singleton.
355   using StaticEntryTable = spdy::HpackHeaderTable::StaticEntryTable;
356   const StaticEntryTable& static_entries_;
357 
358   // Observers waiting to be notified, sorted by required insert count.
359   std::multimap<uint64_t, Observer*> observers_;
360 };
361 
362 }  // namespace quic
363 
364 #endif  // QUICHE_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_
365