blob: 3c9296d0830ab801dbc8397fe3076a12c839c1e3 [file] [log] [blame]
QUICHE teama6ef0a62019-03-07 20:34:33 -05001// 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 "net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h"
6
QUICHE teama6ef0a62019-03-07 20:34:33 -05007#include "net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h"
vasilvv0fb44432019-03-13 22:47:36 -07008#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
QUICHE teama6ef0a62019-03-07 20:34:33 -05009
10namespace quic {
11
QUICHE teama6ef0a62019-03-07 20:34:33 -050012QpackHeaderTable::QpackHeaderTable()
13 : static_entries_(ObtainQpackStaticTable().GetStaticEntries()),
14 static_index_(ObtainQpackStaticTable().GetStaticIndex()),
15 static_name_index_(ObtainQpackStaticTable().GetStaticNameIndex()),
16 dynamic_table_size_(0),
17 dynamic_table_capacity_(0),
18 maximum_dynamic_table_capacity_(0),
19 max_entries_(0),
20 dropped_entry_count_(0) {}
21
22QpackHeaderTable::~QpackHeaderTable() = default;
23
24const QpackEntry* QpackHeaderTable::LookupEntry(bool is_static,
25 uint64_t index) const {
26 if (is_static) {
27 if (index >= static_entries_.size()) {
28 return nullptr;
29 }
30
31 return &static_entries_[index];
32 }
33
34 if (index < dropped_entry_count_) {
35 return nullptr;
36 }
37
38 index -= dropped_entry_count_;
39
40 if (index >= dynamic_entries_.size()) {
41 return nullptr;
42 }
43
44 return &dynamic_entries_[index];
45}
46
47QpackHeaderTable::MatchType QpackHeaderTable::FindHeaderField(
48 QuicStringPiece name,
49 QuicStringPiece value,
50 bool* is_static,
51 uint64_t* index) const {
52 QpackEntry query(name, value);
53
54 // Look for exact match in static table.
55 auto index_it = static_index_.find(&query);
56 if (index_it != static_index_.end()) {
57 DCHECK((*index_it)->IsStatic());
58 *index = (*index_it)->InsertionIndex();
59 *is_static = true;
60 return MatchType::kNameAndValue;
61 }
62
63 // Look for exact match in dynamic table.
64 index_it = dynamic_index_.find(&query);
65 if (index_it != dynamic_index_.end()) {
66 DCHECK(!(*index_it)->IsStatic());
67 *index = (*index_it)->InsertionIndex();
68 *is_static = false;
69 return MatchType::kNameAndValue;
70 }
71
72 // Look for name match in static table.
73 auto name_index_it = static_name_index_.find(name);
74 if (name_index_it != static_name_index_.end()) {
75 DCHECK(name_index_it->second->IsStatic());
76 *index = name_index_it->second->InsertionIndex();
77 *is_static = true;
78 return MatchType::kName;
79 }
80
81 // Look for name match in dynamic table.
82 name_index_it = dynamic_name_index_.find(name);
83 if (name_index_it != dynamic_name_index_.end()) {
84 DCHECK(!name_index_it->second->IsStatic());
85 *index = name_index_it->second->InsertionIndex();
86 *is_static = false;
87 return MatchType::kName;
88 }
89
90 return MatchType::kNoMatch;
91}
92
93const QpackEntry* QpackHeaderTable::InsertEntry(QuicStringPiece name,
94 QuicStringPiece value) {
bncb4025172019-07-12 18:09:59 -070095 const uint64_t entry_size = QpackEntry::Size(name, value);
QUICHE teama6ef0a62019-03-07 20:34:33 -050096 if (entry_size > dynamic_table_capacity_) {
97 return nullptr;
98 }
99
100 const uint64_t index = dropped_entry_count_ + dynamic_entries_.size();
101 dynamic_entries_.push_back({name, value, /* is_static = */ false, index});
102 QpackEntry* const new_entry = &dynamic_entries_.back();
103
104 // Evict entries after inserting the new entry instead of before
105 // in order to avoid invalidating |name| and |value|.
106 dynamic_table_size_ += entry_size;
107 EvictDownToCurrentCapacity();
108
109 auto index_result = dynamic_index_.insert(new_entry);
110 if (!index_result.second) {
111 // An entry with the same name and value already exists. It needs to be
112 // replaced, because |dynamic_index_| tracks the most recent entry for a
113 // given name and value.
114 DCHECK_GT(new_entry->InsertionIndex(),
115 (*index_result.first)->InsertionIndex());
116 dynamic_index_.erase(index_result.first);
117 auto result = dynamic_index_.insert(new_entry);
118 CHECK(result.second);
119 }
120
121 auto name_result = dynamic_name_index_.insert({new_entry->name(), new_entry});
122 if (!name_result.second) {
123 // An entry with the same name already exists. It needs to be replaced,
124 // because |dynamic_name_index_| tracks the most recent entry for a given
125 // name.
126 DCHECK_GT(new_entry->InsertionIndex(),
127 name_result.first->second->InsertionIndex());
128 dynamic_name_index_.erase(name_result.first);
129 auto result = dynamic_name_index_.insert({new_entry->name(), new_entry});
130 CHECK(result.second);
131 }
132
bnc7a1c21c2019-07-08 19:09:50 -0700133 // Notify and deregister observers whose threshold is met, if any.
134 while (!observers_.empty() &&
135 observers_.top().required_insert_count <= inserted_entry_count()) {
136 Observer* observer = observers_.top().observer;
137 observers_.pop();
138 observer->OnInsertCountReachedThreshold();
139 }
140
QUICHE teama6ef0a62019-03-07 20:34:33 -0500141 return new_entry;
142}
143
bnc687c6e72019-07-29 10:57:40 -0700144uint64_t QpackHeaderTable::MaxInsertSizeWithoutEvictingGivenEntry(
145 uint64_t index) const {
146 DCHECK_LE(dropped_entry_count_, index);
147
148 if (index > inserted_entry_count()) {
149 // All entries are allowed to be evicted.
150 return dynamic_table_capacity_;
151 }
152
153 // Initialize to current available capacity.
154 uint64_t max_insert_size = dynamic_table_capacity_ - dynamic_table_size_;
155
156 for (const auto& entry : dynamic_entries_) {
157 if (entry.InsertionIndex() >= index) {
158 break;
159 }
160 max_insert_size += entry.Size();
161 }
162
163 return max_insert_size;
164}
165
QUICHE teama6ef0a62019-03-07 20:34:33 -0500166bool QpackHeaderTable::SetDynamicTableCapacity(uint64_t capacity) {
167 if (capacity > maximum_dynamic_table_capacity_) {
168 return false;
169 }
170
171 dynamic_table_capacity_ = capacity;
172 EvictDownToCurrentCapacity();
173
174 DCHECK_LE(dynamic_table_size_, dynamic_table_capacity_);
175
176 return true;
177}
178
179void QpackHeaderTable::SetMaximumDynamicTableCapacity(
180 uint64_t maximum_dynamic_table_capacity) {
181 // This method can only be called once: in the decoding context, shortly after
182 // construction; in the encoding context, upon receiving the SETTINGS frame.
183 DCHECK_EQ(0u, dynamic_table_capacity_);
184 DCHECK_EQ(0u, maximum_dynamic_table_capacity_);
185 DCHECK_EQ(0u, max_entries_);
186
187 dynamic_table_capacity_ = maximum_dynamic_table_capacity;
188 maximum_dynamic_table_capacity_ = maximum_dynamic_table_capacity;
189 max_entries_ = maximum_dynamic_table_capacity / 32;
190}
191
bnc7a1c21c2019-07-08 19:09:50 -0700192void QpackHeaderTable::RegisterObserver(Observer* observer,
193 uint64_t required_insert_count) {
194 DCHECK_GT(required_insert_count, 0u);
195 observers_.push({observer, required_insert_count});
196}
197
bncb34a7ec2019-07-25 08:53:44 -0700198uint64_t QpackHeaderTable::draining_index(float draining_fraction) const {
199 DCHECK_LE(0.0, draining_fraction);
200 DCHECK_LE(draining_fraction, 1.0);
201
202 const uint64_t required_space = draining_fraction * dynamic_table_capacity_;
203 uint64_t space_above_draining_index =
204 dynamic_table_capacity_ - dynamic_table_size_;
205
206 if (dynamic_entries_.empty() ||
207 space_above_draining_index >= required_space) {
208 return dropped_entry_count_;
209 }
210
211 auto it = dynamic_entries_.begin();
212 while (space_above_draining_index < required_space) {
213 space_above_draining_index += it->Size();
214 ++it;
215 if (it == dynamic_entries_.end()) {
216 return inserted_entry_count();
217 }
218 }
219
220 return it->InsertionIndex();
221}
222
bnc7a1c21c2019-07-08 19:09:50 -0700223bool QpackHeaderTable::ObserverWithThreshold::operator>(
224 const ObserverWithThreshold& other) const {
225 return required_insert_count > other.required_insert_count;
226}
227
QUICHE teama6ef0a62019-03-07 20:34:33 -0500228void QpackHeaderTable::EvictDownToCurrentCapacity() {
229 while (dynamic_table_size_ > dynamic_table_capacity_) {
230 DCHECK(!dynamic_entries_.empty());
231
232 QpackEntry* const entry = &dynamic_entries_.front();
bncb4025172019-07-12 18:09:59 -0700233 const uint64_t entry_size = entry->Size();
QUICHE teama6ef0a62019-03-07 20:34:33 -0500234
235 DCHECK_GE(dynamic_table_size_, entry_size);
236 dynamic_table_size_ -= entry_size;
237
238 auto index_it = dynamic_index_.find(entry);
239 // Remove |dynamic_index_| entry only if it points to the same
240 // QpackEntry in |dynamic_entries_|. Note that |dynamic_index_| has a
241 // comparison function that only considers name and value, not actual
242 // QpackEntry* address, so find() can return a different entry if name and
243 // value match.
244 if (index_it != dynamic_index_.end() && *index_it == entry) {
245 dynamic_index_.erase(index_it);
246 }
247
248 auto name_it = dynamic_name_index_.find(entry->name());
249 // Remove |dynamic_name_index_| entry only if it points to the same
250 // QpackEntry in |dynamic_entries_|.
251 if (name_it != dynamic_name_index_.end() && name_it->second == entry) {
252 dynamic_name_index_.erase(name_it);
253 }
254
255 dynamic_entries_.pop_front();
256 ++dropped_entry_count_;
257 }
258}
259
260} // namespace quic