blob: 4cafa19681546d502bc78c3eb70a9ddbcc18848e [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),
bnc20df1af2019-11-12 10:46:20 -080020 dropped_entry_count_(0),
21 dynamic_table_entry_referenced_(false) {}
QUICHE teama6ef0a62019-03-07 20:34:33 -050022
bnc45a573a2019-10-04 09:30:02 -070023QpackHeaderTable::~QpackHeaderTable() {
24 for (auto& entry : observers_) {
25 entry.second->Cancel();
26 }
27}
QUICHE teama6ef0a62019-03-07 20:34:33 -050028
29const QpackEntry* QpackHeaderTable::LookupEntry(bool is_static,
30 uint64_t index) const {
31 if (is_static) {
32 if (index >= static_entries_.size()) {
33 return nullptr;
34 }
35
36 return &static_entries_[index];
37 }
38
39 if (index < dropped_entry_count_) {
40 return nullptr;
41 }
42
43 index -= dropped_entry_count_;
44
45 if (index >= dynamic_entries_.size()) {
46 return nullptr;
47 }
48
49 return &dynamic_entries_[index];
50}
51
52QpackHeaderTable::MatchType QpackHeaderTable::FindHeaderField(
53 QuicStringPiece name,
54 QuicStringPiece value,
55 bool* is_static,
56 uint64_t* index) const {
57 QpackEntry query(name, value);
58
59 // Look for exact match in static table.
60 auto index_it = static_index_.find(&query);
61 if (index_it != static_index_.end()) {
62 DCHECK((*index_it)->IsStatic());
63 *index = (*index_it)->InsertionIndex();
64 *is_static = true;
65 return MatchType::kNameAndValue;
66 }
67
68 // Look for exact match in dynamic table.
69 index_it = dynamic_index_.find(&query);
70 if (index_it != dynamic_index_.end()) {
71 DCHECK(!(*index_it)->IsStatic());
72 *index = (*index_it)->InsertionIndex();
73 *is_static = false;
74 return MatchType::kNameAndValue;
75 }
76
77 // Look for name match in static table.
78 auto name_index_it = static_name_index_.find(name);
79 if (name_index_it != static_name_index_.end()) {
80 DCHECK(name_index_it->second->IsStatic());
81 *index = name_index_it->second->InsertionIndex();
82 *is_static = true;
83 return MatchType::kName;
84 }
85
86 // Look for name match in dynamic table.
87 name_index_it = dynamic_name_index_.find(name);
88 if (name_index_it != dynamic_name_index_.end()) {
89 DCHECK(!name_index_it->second->IsStatic());
90 *index = name_index_it->second->InsertionIndex();
91 *is_static = false;
92 return MatchType::kName;
93 }
94
95 return MatchType::kNoMatch;
96}
97
98const QpackEntry* QpackHeaderTable::InsertEntry(QuicStringPiece name,
99 QuicStringPiece value) {
bncb4025172019-07-12 18:09:59 -0700100 const uint64_t entry_size = QpackEntry::Size(name, value);
QUICHE teama6ef0a62019-03-07 20:34:33 -0500101 if (entry_size > dynamic_table_capacity_) {
102 return nullptr;
103 }
104
105 const uint64_t index = dropped_entry_count_ + dynamic_entries_.size();
106 dynamic_entries_.push_back({name, value, /* is_static = */ false, index});
107 QpackEntry* const new_entry = &dynamic_entries_.back();
108
109 // Evict entries after inserting the new entry instead of before
110 // in order to avoid invalidating |name| and |value|.
111 dynamic_table_size_ += entry_size;
112 EvictDownToCurrentCapacity();
113
114 auto index_result = dynamic_index_.insert(new_entry);
115 if (!index_result.second) {
116 // An entry with the same name and value already exists. It needs to be
117 // replaced, because |dynamic_index_| tracks the most recent entry for a
118 // given name and value.
119 DCHECK_GT(new_entry->InsertionIndex(),
120 (*index_result.first)->InsertionIndex());
121 dynamic_index_.erase(index_result.first);
122 auto result = dynamic_index_.insert(new_entry);
123 CHECK(result.second);
124 }
125
126 auto name_result = dynamic_name_index_.insert({new_entry->name(), new_entry});
127 if (!name_result.second) {
128 // An entry with the same name already exists. It needs to be replaced,
129 // because |dynamic_name_index_| tracks the most recent entry for a given
130 // name.
131 DCHECK_GT(new_entry->InsertionIndex(),
132 name_result.first->second->InsertionIndex());
133 dynamic_name_index_.erase(name_result.first);
134 auto result = dynamic_name_index_.insert({new_entry->name(), new_entry});
135 CHECK(result.second);
136 }
137
bnc7a1c21c2019-07-08 19:09:50 -0700138 // Notify and deregister observers whose threshold is met, if any.
bncbb40c7f2019-10-02 17:46:57 -0700139 while (!observers_.empty()) {
140 auto it = observers_.begin();
141 if (it->first > inserted_entry_count()) {
142 break;
143 }
144 Observer* observer = it->second;
145 observers_.erase(it);
bnc7a1c21c2019-07-08 19:09:50 -0700146 observer->OnInsertCountReachedThreshold();
147 }
148
QUICHE teama6ef0a62019-03-07 20:34:33 -0500149 return new_entry;
150}
151
bnc687c6e72019-07-29 10:57:40 -0700152uint64_t QpackHeaderTable::MaxInsertSizeWithoutEvictingGivenEntry(
153 uint64_t index) const {
154 DCHECK_LE(dropped_entry_count_, index);
155
156 if (index > inserted_entry_count()) {
157 // All entries are allowed to be evicted.
158 return dynamic_table_capacity_;
159 }
160
161 // Initialize to current available capacity.
162 uint64_t max_insert_size = dynamic_table_capacity_ - dynamic_table_size_;
163
164 for (const auto& entry : dynamic_entries_) {
165 if (entry.InsertionIndex() >= index) {
166 break;
167 }
168 max_insert_size += entry.Size();
169 }
170
171 return max_insert_size;
172}
173
QUICHE teama6ef0a62019-03-07 20:34:33 -0500174bool QpackHeaderTable::SetDynamicTableCapacity(uint64_t capacity) {
175 if (capacity > maximum_dynamic_table_capacity_) {
176 return false;
177 }
178
179 dynamic_table_capacity_ = capacity;
180 EvictDownToCurrentCapacity();
181
182 DCHECK_LE(dynamic_table_size_, dynamic_table_capacity_);
183
184 return true;
185}
186
187void QpackHeaderTable::SetMaximumDynamicTableCapacity(
188 uint64_t maximum_dynamic_table_capacity) {
189 // This method can only be called once: in the decoding context, shortly after
190 // construction; in the encoding context, upon receiving the SETTINGS frame.
191 DCHECK_EQ(0u, dynamic_table_capacity_);
192 DCHECK_EQ(0u, maximum_dynamic_table_capacity_);
193 DCHECK_EQ(0u, max_entries_);
194
QUICHE teama6ef0a62019-03-07 20:34:33 -0500195 maximum_dynamic_table_capacity_ = maximum_dynamic_table_capacity;
196 max_entries_ = maximum_dynamic_table_capacity / 32;
197}
198
bncbb40c7f2019-10-02 17:46:57 -0700199void QpackHeaderTable::RegisterObserver(uint64_t required_insert_count,
200 Observer* observer) {
bnc7a1c21c2019-07-08 19:09:50 -0700201 DCHECK_GT(required_insert_count, 0u);
bncbb40c7f2019-10-02 17:46:57 -0700202 observers_.insert({required_insert_count, observer});
bnc7a1c21c2019-07-08 19:09:50 -0700203}
204
bnc2f2b7422019-10-02 18:10:43 -0700205void QpackHeaderTable::UnregisterObserver(uint64_t required_insert_count,
206 Observer* observer) {
207 auto it = observers_.lower_bound(required_insert_count);
208 while (it != observers_.end() && it->first == required_insert_count) {
209 if (it->second == observer) {
210 observers_.erase(it);
211 return;
212 }
213 ++it;
214 }
215
216 // |observer| must have been registered.
217 QUIC_NOTREACHED();
218}
219
bncb34a7ec2019-07-25 08:53:44 -0700220uint64_t QpackHeaderTable::draining_index(float draining_fraction) const {
221 DCHECK_LE(0.0, draining_fraction);
222 DCHECK_LE(draining_fraction, 1.0);
223
224 const uint64_t required_space = draining_fraction * dynamic_table_capacity_;
225 uint64_t space_above_draining_index =
226 dynamic_table_capacity_ - dynamic_table_size_;
227
228 if (dynamic_entries_.empty() ||
229 space_above_draining_index >= required_space) {
230 return dropped_entry_count_;
231 }
232
233 auto it = dynamic_entries_.begin();
234 while (space_above_draining_index < required_space) {
235 space_above_draining_index += it->Size();
236 ++it;
237 if (it == dynamic_entries_.end()) {
238 return inserted_entry_count();
239 }
240 }
241
242 return it->InsertionIndex();
243}
244
QUICHE teama6ef0a62019-03-07 20:34:33 -0500245void QpackHeaderTable::EvictDownToCurrentCapacity() {
246 while (dynamic_table_size_ > dynamic_table_capacity_) {
247 DCHECK(!dynamic_entries_.empty());
248
249 QpackEntry* const entry = &dynamic_entries_.front();
bncb4025172019-07-12 18:09:59 -0700250 const uint64_t entry_size = entry->Size();
QUICHE teama6ef0a62019-03-07 20:34:33 -0500251
252 DCHECK_GE(dynamic_table_size_, entry_size);
253 dynamic_table_size_ -= entry_size;
254
255 auto index_it = dynamic_index_.find(entry);
256 // Remove |dynamic_index_| entry only if it points to the same
257 // QpackEntry in |dynamic_entries_|. Note that |dynamic_index_| has a
258 // comparison function that only considers name and value, not actual
259 // QpackEntry* address, so find() can return a different entry if name and
260 // value match.
261 if (index_it != dynamic_index_.end() && *index_it == entry) {
262 dynamic_index_.erase(index_it);
263 }
264
265 auto name_it = dynamic_name_index_.find(entry->name());
266 // Remove |dynamic_name_index_| entry only if it points to the same
267 // QpackEntry in |dynamic_entries_|.
268 if (name_it != dynamic_name_index_.end() && name_it->second == entry) {
269 dynamic_name_index_.erase(name_it);
270 }
271
272 dynamic_entries_.pop_front();
273 ++dropped_entry_count_;
274 }
275}
276
277} // namespace quic