blob: 9522666dc7941d84cf272700d18004089da6a55e [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
12namespace {
13
14const uint64_t kEntrySizeOverhead = 32;
15
16uint64_t EntrySize(QuicStringPiece name, QuicStringPiece value) {
17 return name.size() + value.size() + kEntrySizeOverhead;
18}
19
20} // anonymous namespace
21
22QpackHeaderTable::QpackHeaderTable()
23 : static_entries_(ObtainQpackStaticTable().GetStaticEntries()),
24 static_index_(ObtainQpackStaticTable().GetStaticIndex()),
25 static_name_index_(ObtainQpackStaticTable().GetStaticNameIndex()),
26 dynamic_table_size_(0),
27 dynamic_table_capacity_(0),
28 maximum_dynamic_table_capacity_(0),
29 max_entries_(0),
30 dropped_entry_count_(0) {}
31
32QpackHeaderTable::~QpackHeaderTable() = default;
33
34const QpackEntry* QpackHeaderTable::LookupEntry(bool is_static,
35 uint64_t index) const {
36 if (is_static) {
37 if (index >= static_entries_.size()) {
38 return nullptr;
39 }
40
41 return &static_entries_[index];
42 }
43
44 if (index < dropped_entry_count_) {
45 return nullptr;
46 }
47
48 index -= dropped_entry_count_;
49
50 if (index >= dynamic_entries_.size()) {
51 return nullptr;
52 }
53
54 return &dynamic_entries_[index];
55}
56
57QpackHeaderTable::MatchType QpackHeaderTable::FindHeaderField(
58 QuicStringPiece name,
59 QuicStringPiece value,
60 bool* is_static,
61 uint64_t* index) const {
62 QpackEntry query(name, value);
63
64 // Look for exact match in static table.
65 auto index_it = static_index_.find(&query);
66 if (index_it != static_index_.end()) {
67 DCHECK((*index_it)->IsStatic());
68 *index = (*index_it)->InsertionIndex();
69 *is_static = true;
70 return MatchType::kNameAndValue;
71 }
72
73 // Look for exact match in dynamic table.
74 index_it = dynamic_index_.find(&query);
75 if (index_it != dynamic_index_.end()) {
76 DCHECK(!(*index_it)->IsStatic());
77 *index = (*index_it)->InsertionIndex();
78 *is_static = false;
79 return MatchType::kNameAndValue;
80 }
81
82 // Look for name match in static table.
83 auto name_index_it = static_name_index_.find(name);
84 if (name_index_it != static_name_index_.end()) {
85 DCHECK(name_index_it->second->IsStatic());
86 *index = name_index_it->second->InsertionIndex();
87 *is_static = true;
88 return MatchType::kName;
89 }
90
91 // Look for name match in dynamic table.
92 name_index_it = dynamic_name_index_.find(name);
93 if (name_index_it != dynamic_name_index_.end()) {
94 DCHECK(!name_index_it->second->IsStatic());
95 *index = name_index_it->second->InsertionIndex();
96 *is_static = false;
97 return MatchType::kName;
98 }
99
100 return MatchType::kNoMatch;
101}
102
103const QpackEntry* QpackHeaderTable::InsertEntry(QuicStringPiece name,
104 QuicStringPiece value) {
105 const uint64_t entry_size = EntrySize(name, value);
106 if (entry_size > dynamic_table_capacity_) {
107 return nullptr;
108 }
109
110 const uint64_t index = dropped_entry_count_ + dynamic_entries_.size();
111 dynamic_entries_.push_back({name, value, /* is_static = */ false, index});
112 QpackEntry* const new_entry = &dynamic_entries_.back();
113
114 // Evict entries after inserting the new entry instead of before
115 // in order to avoid invalidating |name| and |value|.
116 dynamic_table_size_ += entry_size;
117 EvictDownToCurrentCapacity();
118
119 auto index_result = dynamic_index_.insert(new_entry);
120 if (!index_result.second) {
121 // An entry with the same name and value already exists. It needs to be
122 // replaced, because |dynamic_index_| tracks the most recent entry for a
123 // given name and value.
124 DCHECK_GT(new_entry->InsertionIndex(),
125 (*index_result.first)->InsertionIndex());
126 dynamic_index_.erase(index_result.first);
127 auto result = dynamic_index_.insert(new_entry);
128 CHECK(result.second);
129 }
130
131 auto name_result = dynamic_name_index_.insert({new_entry->name(), new_entry});
132 if (!name_result.second) {
133 // An entry with the same name already exists. It needs to be replaced,
134 // because |dynamic_name_index_| tracks the most recent entry for a given
135 // name.
136 DCHECK_GT(new_entry->InsertionIndex(),
137 name_result.first->second->InsertionIndex());
138 dynamic_name_index_.erase(name_result.first);
139 auto result = dynamic_name_index_.insert({new_entry->name(), new_entry});
140 CHECK(result.second);
141 }
142
bnc7a1c21c2019-07-08 19:09:50 -0700143 // Notify and deregister observers whose threshold is met, if any.
144 while (!observers_.empty() &&
145 observers_.top().required_insert_count <= inserted_entry_count()) {
146 Observer* observer = observers_.top().observer;
147 observers_.pop();
148 observer->OnInsertCountReachedThreshold();
149 }
150
QUICHE teama6ef0a62019-03-07 20:34:33 -0500151 return new_entry;
152}
153
154bool QpackHeaderTable::SetDynamicTableCapacity(uint64_t capacity) {
155 if (capacity > maximum_dynamic_table_capacity_) {
156 return false;
157 }
158
159 dynamic_table_capacity_ = capacity;
160 EvictDownToCurrentCapacity();
161
162 DCHECK_LE(dynamic_table_size_, dynamic_table_capacity_);
163
164 return true;
165}
166
167void QpackHeaderTable::SetMaximumDynamicTableCapacity(
168 uint64_t maximum_dynamic_table_capacity) {
169 // This method can only be called once: in the decoding context, shortly after
170 // construction; in the encoding context, upon receiving the SETTINGS frame.
171 DCHECK_EQ(0u, dynamic_table_capacity_);
172 DCHECK_EQ(0u, maximum_dynamic_table_capacity_);
173 DCHECK_EQ(0u, max_entries_);
174
175 dynamic_table_capacity_ = maximum_dynamic_table_capacity;
176 maximum_dynamic_table_capacity_ = maximum_dynamic_table_capacity;
177 max_entries_ = maximum_dynamic_table_capacity / 32;
178}
179
bnc7a1c21c2019-07-08 19:09:50 -0700180void QpackHeaderTable::RegisterObserver(Observer* observer,
181 uint64_t required_insert_count) {
182 DCHECK_GT(required_insert_count, 0u);
183 observers_.push({observer, required_insert_count});
184}
185
186bool QpackHeaderTable::ObserverWithThreshold::operator>(
187 const ObserverWithThreshold& other) const {
188 return required_insert_count > other.required_insert_count;
189}
190
QUICHE teama6ef0a62019-03-07 20:34:33 -0500191void QpackHeaderTable::EvictDownToCurrentCapacity() {
192 while (dynamic_table_size_ > dynamic_table_capacity_) {
193 DCHECK(!dynamic_entries_.empty());
194
195 QpackEntry* const entry = &dynamic_entries_.front();
196 const uint64_t entry_size = EntrySize(entry->name(), entry->value());
197
198 DCHECK_GE(dynamic_table_size_, entry_size);
199 dynamic_table_size_ -= entry_size;
200
201 auto index_it = dynamic_index_.find(entry);
202 // Remove |dynamic_index_| entry only if it points to the same
203 // QpackEntry in |dynamic_entries_|. Note that |dynamic_index_| has a
204 // comparison function that only considers name and value, not actual
205 // QpackEntry* address, so find() can return a different entry if name and
206 // value match.
207 if (index_it != dynamic_index_.end() && *index_it == entry) {
208 dynamic_index_.erase(index_it);
209 }
210
211 auto name_it = dynamic_name_index_.find(entry->name());
212 // Remove |dynamic_name_index_| entry only if it points to the same
213 // QpackEntry in |dynamic_entries_|.
214 if (name_it != dynamic_name_index_.end() && name_it->second == entry) {
215 dynamic_name_index_.erase(name_it);
216 }
217
218 dynamic_entries_.pop_front();
219 ++dropped_entry_count_;
220 }
221}
222
223} // namespace quic