blob: f882e751cb7ac0334ab12e20a121488717e343c5 [file] [log] [blame]
// 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_