| // Copyright (c) 2018 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef QUICHE_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_ | |
| #define QUICHE_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_ | |
| #include <cstdint> | |
| #include <deque> | |
| #include "absl/strings/string_view.h" | |
| #include "quiche/quic/platform/api/quic_export.h" | |
| #include "quiche/common/quiche_circular_deque.h" | |
| #include "quiche/spdy/core/hpack/hpack_entry.h" | |
| #include "quiche/spdy/core/hpack/hpack_header_table.h" | |
| namespace quic { | |
| using QpackEntry = spdy::HpackEntry; | |
| using QpackLookupEntry = spdy::HpackLookupEntry; | |
| constexpr size_t kQpackEntrySizeOverhead = spdy::kHpackEntrySizeOverhead; | |
| // Encoder needs pointer stability for |dynamic_index_| and | |
| // |dynamic_name_index_|. However, it does not need random access. | |
| using QpackEncoderDynamicTable = | |
| quiche::QuicheCircularDeque<std::unique_ptr<QpackEntry>>; | |
| // Decoder needs random access for LookupEntry(). | |
| // However, it does not need pointer stability. | |
| using QpackDecoderDynamicTable = quiche::QuicheCircularDeque<QpackEntry>; | |
| // This is a base class for encoder and decoder classes that manage the QPACK | |
| // static and dynamic tables. For dynamic entries, it only has a concept of | |
| // absolute indices. The caller needs to perform the necessary transformations | |
| // to and from relative indices and post-base indices. | |
| template <typename DynamicEntryTable> | |
| class QUIC_EXPORT_PRIVATE QpackHeaderTableBase { | |
| public: | |
| QpackHeaderTableBase(); | |
| QpackHeaderTableBase(const QpackHeaderTableBase&) = delete; | |
| QpackHeaderTableBase& operator=(const QpackHeaderTableBase&) = delete; | |
| virtual ~QpackHeaderTableBase() = default; | |
| // Returns whether an entry with |name| and |value| has a size (including | |
| // overhead) that is smaller than or equal to the capacity of the dynamic | |
| // table. | |
| bool EntryFitsDynamicTableCapacity(absl::string_view name, | |
| absl::string_view value) const; | |
| // Inserts (name, value) into the dynamic table. Entry must not be larger | |
| // than the capacity of the dynamic table. May evict entries. |name| and | |
| // |value| are copied first, therefore it is safe for them to point to an | |
| // entry in the dynamic table, even if it is about to be evicted, or even if | |
| // the underlying container might move entries around when resizing for | |
| // insertion. | |
| // Returns the absolute index of the inserted dynamic table entry. | |
| virtual uint64_t InsertEntry(absl::string_view name, absl::string_view value); | |
| // Change dynamic table capacity to |capacity|. Returns true on success. | |
| // Returns false is |capacity| exceeds maximum dynamic table capacity. | |
| bool SetDynamicTableCapacity(uint64_t capacity); | |
| // Set |maximum_dynamic_table_capacity_|. The initial value is zero. The | |
| // final value is determined by the decoder and is sent to the encoder as | |
| // SETTINGS_HEADER_TABLE_SIZE. Therefore in the decoding context the final | |
| // value can be set upon connection establishment, whereas in the encoding | |
| // context it can be set when the SETTINGS frame is received. | |
| // This method must only be called at most once. | |
| // Returns true if |maximum_dynamic_table_capacity| is set for the first time | |
| // or if it doesn't change current value. The setting is not changed when | |
| // returning false. | |
| bool SetMaximumDynamicTableCapacity(uint64_t maximum_dynamic_table_capacity); | |
| uint64_t dynamic_table_size() const { return dynamic_table_size_; } | |
| uint64_t dynamic_table_capacity() const { return dynamic_table_capacity_; } | |
| uint64_t maximum_dynamic_table_capacity() const { | |
| return maximum_dynamic_table_capacity_; | |
| } | |
| uint64_t max_entries() const { return max_entries_; } | |
| // The number of entries inserted to the dynamic table (including ones that | |
| // were dropped since). Used for relative indexing on the encoder stream. | |
| uint64_t inserted_entry_count() const { | |
| return dynamic_entries_.size() + dropped_entry_count_; | |
| } | |
| // The number of entries dropped from the dynamic table. | |
| uint64_t dropped_entry_count() const { return dropped_entry_count_; } | |
| void set_dynamic_table_entry_referenced() { | |
| dynamic_table_entry_referenced_ = true; | |
| } | |
| bool dynamic_table_entry_referenced() const { | |
| return dynamic_table_entry_referenced_; | |
| } | |
| protected: | |
| // Removes a single entry from the end of the dynamic table, updates | |
| // |dynamic_table_size_| and |dropped_entry_count_|. | |
| virtual void RemoveEntryFromEnd(); | |
| const DynamicEntryTable& dynamic_entries() const { return dynamic_entries_; } | |
| private: | |
| // Evict entries from the dynamic table until table size is less than or equal | |
| // to |capacity|. | |
| void EvictDownToCapacity(uint64_t capacity); | |
| // Dynamic Table entries. | |
| DynamicEntryTable dynamic_entries_; | |
| // Size of the dynamic table. This is the sum of the size of its entries. | |
| uint64_t dynamic_table_size_; | |
| // Dynamic Table Capacity is the maximum allowed value of | |
| // |dynamic_table_size_|. Entries are evicted if necessary before inserting a | |
| // new entry to ensure that dynamic table size never exceeds capacity. | |
| // Initial value is |maximum_dynamic_table_capacity_|. Capacity can be | |
| // changed by the encoder, as long as it does not exceed | |
| // |maximum_dynamic_table_capacity_|. | |
| uint64_t dynamic_table_capacity_; | |
| // Maximum allowed value of |dynamic_table_capacity|. The initial value is | |
| // zero. Can be changed by SetMaximumDynamicTableCapacity(). | |
| uint64_t maximum_dynamic_table_capacity_; | |
| // MaxEntries, see Section 3.2.2. Calculated based on | |
| // |maximum_dynamic_table_capacity_|. Used on request streams to encode and | |
| // decode Required Insert Count. | |
| uint64_t max_entries_; | |
| // The number of entries dropped from the dynamic table. | |
| uint64_t dropped_entry_count_; | |
| // True if any dynamic table entries have been referenced from a header block. | |
| // Set directly by the encoder or decoder. Used for stats. | |
| bool dynamic_table_entry_referenced_; | |
| }; | |
| template <typename DynamicEntryTable> | |
| QpackHeaderTableBase<DynamicEntryTable>::QpackHeaderTableBase() | |
| : dynamic_table_size_(0), | |
| dynamic_table_capacity_(0), | |
| maximum_dynamic_table_capacity_(0), | |
| max_entries_(0), | |
| dropped_entry_count_(0), | |
| dynamic_table_entry_referenced_(false) {} | |
| template <typename DynamicEntryTable> | |
| bool QpackHeaderTableBase<DynamicEntryTable>::EntryFitsDynamicTableCapacity( | |
| absl::string_view name, absl::string_view value) const { | |
| return QpackEntry::Size(name, value) <= dynamic_table_capacity_; | |
| } | |
| namespace internal { | |
| QUIC_NO_EXPORT inline size_t GetSize(const QpackEntry& entry) { | |
| return entry.Size(); | |
| } | |
| QUIC_NO_EXPORT inline size_t GetSize(const std::unique_ptr<QpackEntry>& entry) { | |
| return entry->Size(); | |
| } | |
| QUIC_NO_EXPORT inline std::unique_ptr<QpackEntry> NewEntry( | |
| std::string name, std::string value, QpackEncoderDynamicTable& /*t*/) { | |
| return std::make_unique<QpackEntry>(std::move(name), std::move(value)); | |
| } | |
| QUIC_NO_EXPORT inline QpackEntry NewEntry(std::string name, std::string value, | |
| QpackDecoderDynamicTable& /*t*/) { | |
| return QpackEntry{std::move(name), std::move(value)}; | |
| } | |
| } // namespace internal | |
| template <typename DynamicEntryTable> | |
| uint64_t QpackHeaderTableBase<DynamicEntryTable>::InsertEntry( | |
| absl::string_view name, absl::string_view value) { | |
| QUICHE_DCHECK(EntryFitsDynamicTableCapacity(name, value)); | |
| const uint64_t index = dropped_entry_count_ + dynamic_entries_.size(); | |
| // Copy name and value before modifying the container, because evicting | |
| // entries or even inserting a new one might invalidate |name| or |value| if | |
| // they point to an entry. | |
| auto new_entry = internal::NewEntry(std::string(name), std::string(value), | |
| dynamic_entries_); | |
| const size_t entry_size = internal::GetSize(new_entry); | |
| EvictDownToCapacity(dynamic_table_capacity_ - entry_size); | |
| dynamic_table_size_ += entry_size; | |
| dynamic_entries_.push_back(std::move(new_entry)); | |
| return index; | |
| } | |
| template <typename DynamicEntryTable> | |
| bool QpackHeaderTableBase<DynamicEntryTable>::SetDynamicTableCapacity( | |
| uint64_t capacity) { | |
| if (capacity > maximum_dynamic_table_capacity_) { | |
| return false; | |
| } | |
| dynamic_table_capacity_ = capacity; | |
| EvictDownToCapacity(capacity); | |
| QUICHE_DCHECK_LE(dynamic_table_size_, dynamic_table_capacity_); | |
| return true; | |
| } | |
| template <typename DynamicEntryTable> | |
| bool QpackHeaderTableBase<DynamicEntryTable>::SetMaximumDynamicTableCapacity( | |
| uint64_t maximum_dynamic_table_capacity) { | |
| if (maximum_dynamic_table_capacity_ == 0) { | |
| maximum_dynamic_table_capacity_ = maximum_dynamic_table_capacity; | |
| max_entries_ = maximum_dynamic_table_capacity / 32; | |
| return true; | |
| } | |
| // If the value is already set, it should not be changed. | |
| return maximum_dynamic_table_capacity == maximum_dynamic_table_capacity_; | |
| } | |
| template <typename DynamicEntryTable> | |
| void QpackHeaderTableBase<DynamicEntryTable>::RemoveEntryFromEnd() { | |
| const uint64_t entry_size = internal::GetSize(dynamic_entries_.front()); | |
| QUICHE_DCHECK_GE(dynamic_table_size_, entry_size); | |
| dynamic_table_size_ -= entry_size; | |
| dynamic_entries_.pop_front(); | |
| ++dropped_entry_count_; | |
| } | |
| template <typename DynamicEntryTable> | |
| void QpackHeaderTableBase<DynamicEntryTable>::EvictDownToCapacity( | |
| uint64_t capacity) { | |
| while (dynamic_table_size_ > capacity) { | |
| QUICHE_DCHECK(!dynamic_entries_.empty()); | |
| RemoveEntryFromEnd(); | |
| } | |
| } | |
| class QUIC_EXPORT_PRIVATE QpackEncoderHeaderTable | |
| : public QpackHeaderTableBase<QpackEncoderDynamicTable> { | |
| public: | |
| // Result of header table lookup. | |
| enum class MatchType { kNameAndValue, kName, kNoMatch }; | |
| QpackEncoderHeaderTable(); | |
| ~QpackEncoderHeaderTable() override = default; | |
| uint64_t InsertEntry(absl::string_view name, | |
| absl::string_view value) override; | |
| // Returns the absolute index of an entry with matching name and value if such | |
| // exists, otherwise one with matching name is such exists. |index| is zero | |
| // based for both the static and the dynamic table. | |
| MatchType FindHeaderField(absl::string_view name, absl::string_view value, | |
| bool* is_static, uint64_t* index) const; | |
| // Returns the size of the largest entry that could be inserted into the | |
| // dynamic table without evicting entry |index|. |index| might be larger than | |
| // inserted_entry_count(), in which case the capacity of the table is | |
| // returned. |index| must not be smaller than dropped_entry_count(). | |
| uint64_t MaxInsertSizeWithoutEvictingGivenEntry(uint64_t index) const; | |
| // Returns the draining index described at | |
| // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#avoiding-blocked-insertions. | |
| // Entries with an index larger than or equal to the draining index take up | |
| // approximately |1.0 - draining_fraction| of dynamic table capacity. The | |
| // remaining capacity is taken up by draining entries and unused space. | |
| // The returned index might not be the index of a valid entry. | |
| uint64_t draining_index(float draining_fraction) const; | |
| protected: | |
| void RemoveEntryFromEnd() override; | |
| private: | |
| using NameValueToEntryMap = spdy::HpackHeaderTable::NameValueToEntryMap; | |
| using NameToEntryMap = spdy::HpackHeaderTable::NameToEntryMap; | |
| // Static Table | |
| // |static_index_| and |static_name_index_| are owned by QpackStaticTable | |
| // singleton. | |
| // Tracks the unique static entry for a given header name and value. | |
| const NameValueToEntryMap& static_index_; | |
| // Tracks the first static entry for a given header name. | |
| const NameToEntryMap& static_name_index_; | |
| // Dynamic Table | |
| // An unordered set of QpackEntry pointers with a comparison operator that | |
| // only cares about name and value. This allows fast lookup of the most | |
| // recently inserted dynamic entry for a given header name and value pair. | |
| // Entries point to entries owned by |QpackHeaderTableBase::dynamic_entries_|. | |
| NameValueToEntryMap dynamic_index_; | |
| // An unordered map of QpackEntry pointers keyed off header name. This allows | |
| // fast lookup of the most recently inserted dynamic entry for a given header | |
| // name. Entries point to entries owned by | |
| // |QpackHeaderTableBase::dynamic_entries_|. | |
| NameToEntryMap dynamic_name_index_; | |
| }; | |
| class QUIC_EXPORT_PRIVATE QpackDecoderHeaderTable | |
| : public QpackHeaderTableBase<QpackDecoderDynamicTable> { | |
| public: | |
| // Observer interface for dynamic table insertion. | |
| class QUIC_EXPORT_PRIVATE Observer { | |
| public: | |
| virtual ~Observer() = default; | |
| // Called when inserted_entry_count() reaches the threshold the Observer was | |
| // registered with. After this call the Observer automatically gets | |
| // deregistered. | |
| virtual void OnInsertCountReachedThreshold() = 0; | |
| // Called when QpackDecoderHeaderTable is destroyed to let the Observer know | |
| // that it must not call UnregisterObserver(). | |
| virtual void Cancel() = 0; | |
| }; | |
| QpackDecoderHeaderTable(); | |
| ~QpackDecoderHeaderTable() override; | |
| uint64_t InsertEntry(absl::string_view name, | |
| absl::string_view value) override; | |
| // Returns the entry at absolute index |index| from the static or dynamic | |
| // table according to |is_static|. |index| is zero based for both the static | |
| // and the dynamic table. The returned pointer is valid until the entry is | |
| // evicted, even if other entries are inserted into the dynamic table. | |
| // Returns nullptr if entry does not exist. | |
| const QpackEntry* LookupEntry(bool is_static, uint64_t index) const; | |
| // Register an observer to be notified when inserted_entry_count() reaches | |
| // |required_insert_count|. After the notification, |observer| automatically | |
| // gets unregistered. Each observer must only be registered at most once. | |
| void RegisterObserver(uint64_t required_insert_count, Observer* observer); | |
| // Unregister previously registered observer. Must be called with the same | |
| // |required_insert_count| value that |observer| was registered with. Must be | |
| // called before an observer still waiting for notification is destroyed, | |
| // unless QpackDecoderHeaderTable already called Observer::Cancel(), in which | |
| // case this method must not be called. | |
| void UnregisterObserver(uint64_t required_insert_count, Observer* observer); | |
| private: | |
| // Static Table entries. Owned by QpackStaticTable singleton. | |
| using StaticEntryTable = spdy::HpackHeaderTable::StaticEntryTable; | |
| const StaticEntryTable& static_entries_; | |
| // Observers waiting to be notified, sorted by required insert count. | |
| std::multimap<uint64_t, Observer*> observers_; | |
| }; | |
| } // namespace quic | |
| #endif // QUICHE_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_ |