QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 1 | // 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 | |
QUICHE team | 5be974e | 2020-12-29 18:35:24 -0500 | [diff] [blame] | 5 | #include "quic/core/qpack/qpack_header_table.h" |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 6 | |
vasilvv | 7cac7b0 | 2020-10-08 12:32:10 -0700 | [diff] [blame] | 7 | #include "absl/strings/string_view.h" |
QUICHE team | 5be974e | 2020-12-29 18:35:24 -0500 | [diff] [blame] | 8 | #include "quic/core/qpack/qpack_static_table.h" |
| 9 | #include "quic/platform/api/quic_logging.h" |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 10 | |
| 11 | namespace quic { |
| 12 | |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 13 | QpackHeaderTable::QpackHeaderTable() |
| 14 | : static_entries_(ObtainQpackStaticTable().GetStaticEntries()), |
| 15 | static_index_(ObtainQpackStaticTable().GetStaticIndex()), |
| 16 | static_name_index_(ObtainQpackStaticTable().GetStaticNameIndex()), |
| 17 | dynamic_table_size_(0), |
| 18 | dynamic_table_capacity_(0), |
| 19 | maximum_dynamic_table_capacity_(0), |
| 20 | max_entries_(0), |
bnc | 20df1af | 2019-11-12 10:46:20 -0800 | [diff] [blame] | 21 | dropped_entry_count_(0), |
| 22 | dynamic_table_entry_referenced_(false) {} |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 23 | |
bnc | 45a573a | 2019-10-04 09:30:02 -0700 | [diff] [blame] | 24 | QpackHeaderTable::~QpackHeaderTable() { |
| 25 | for (auto& entry : observers_) { |
| 26 | entry.second->Cancel(); |
| 27 | } |
| 28 | } |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 29 | |
| 30 | const QpackEntry* QpackHeaderTable::LookupEntry(bool is_static, |
| 31 | uint64_t index) const { |
| 32 | if (is_static) { |
| 33 | if (index >= static_entries_.size()) { |
| 34 | return nullptr; |
| 35 | } |
| 36 | |
| 37 | return &static_entries_[index]; |
| 38 | } |
| 39 | |
| 40 | if (index < dropped_entry_count_) { |
| 41 | return nullptr; |
| 42 | } |
| 43 | |
| 44 | index -= dropped_entry_count_; |
| 45 | |
| 46 | if (index >= dynamic_entries_.size()) { |
| 47 | return nullptr; |
| 48 | } |
| 49 | |
| 50 | return &dynamic_entries_[index]; |
| 51 | } |
| 52 | |
| 53 | QpackHeaderTable::MatchType QpackHeaderTable::FindHeaderField( |
vasilvv | 7cac7b0 | 2020-10-08 12:32:10 -0700 | [diff] [blame] | 54 | absl::string_view name, |
| 55 | absl::string_view value, |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 56 | bool* is_static, |
| 57 | uint64_t* index) const { |
| 58 | QpackEntry query(name, value); |
| 59 | |
| 60 | // Look for exact match in static table. |
| 61 | auto index_it = static_index_.find(&query); |
| 62 | if (index_it != static_index_.end()) { |
| 63 | DCHECK((*index_it)->IsStatic()); |
| 64 | *index = (*index_it)->InsertionIndex(); |
| 65 | *is_static = true; |
| 66 | return MatchType::kNameAndValue; |
| 67 | } |
| 68 | |
| 69 | // Look for exact match in dynamic table. |
| 70 | index_it = dynamic_index_.find(&query); |
| 71 | if (index_it != dynamic_index_.end()) { |
| 72 | DCHECK(!(*index_it)->IsStatic()); |
| 73 | *index = (*index_it)->InsertionIndex(); |
| 74 | *is_static = false; |
| 75 | return MatchType::kNameAndValue; |
| 76 | } |
| 77 | |
| 78 | // Look for name match in static table. |
| 79 | auto name_index_it = static_name_index_.find(name); |
| 80 | if (name_index_it != static_name_index_.end()) { |
| 81 | DCHECK(name_index_it->second->IsStatic()); |
| 82 | *index = name_index_it->second->InsertionIndex(); |
| 83 | *is_static = true; |
| 84 | return MatchType::kName; |
| 85 | } |
| 86 | |
| 87 | // Look for name match in dynamic table. |
| 88 | name_index_it = dynamic_name_index_.find(name); |
| 89 | if (name_index_it != dynamic_name_index_.end()) { |
| 90 | DCHECK(!name_index_it->second->IsStatic()); |
| 91 | *index = name_index_it->second->InsertionIndex(); |
| 92 | *is_static = false; |
| 93 | return MatchType::kName; |
| 94 | } |
| 95 | |
| 96 | return MatchType::kNoMatch; |
| 97 | } |
| 98 | |
vasilvv | 7cac7b0 | 2020-10-08 12:32:10 -0700 | [diff] [blame] | 99 | const QpackEntry* QpackHeaderTable::InsertEntry(absl::string_view name, |
| 100 | absl::string_view value) { |
bnc | b402517 | 2019-07-12 18:09:59 -0700 | [diff] [blame] | 101 | const uint64_t entry_size = QpackEntry::Size(name, value); |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 102 | if (entry_size > dynamic_table_capacity_) { |
| 103 | return nullptr; |
| 104 | } |
| 105 | |
| 106 | const uint64_t index = dropped_entry_count_ + dynamic_entries_.size(); |
| 107 | dynamic_entries_.push_back({name, value, /* is_static = */ false, index}); |
| 108 | QpackEntry* const new_entry = &dynamic_entries_.back(); |
| 109 | |
| 110 | // Evict entries after inserting the new entry instead of before |
| 111 | // in order to avoid invalidating |name| and |value|. |
| 112 | dynamic_table_size_ += entry_size; |
| 113 | EvictDownToCurrentCapacity(); |
| 114 | |
| 115 | auto index_result = dynamic_index_.insert(new_entry); |
| 116 | if (!index_result.second) { |
| 117 | // An entry with the same name and value already exists. It needs to be |
| 118 | // replaced, because |dynamic_index_| tracks the most recent entry for a |
| 119 | // given name and value. |
| 120 | DCHECK_GT(new_entry->InsertionIndex(), |
| 121 | (*index_result.first)->InsertionIndex()); |
| 122 | dynamic_index_.erase(index_result.first); |
| 123 | auto result = dynamic_index_.insert(new_entry); |
| 124 | CHECK(result.second); |
| 125 | } |
| 126 | |
| 127 | auto name_result = dynamic_name_index_.insert({new_entry->name(), new_entry}); |
| 128 | if (!name_result.second) { |
| 129 | // An entry with the same name already exists. It needs to be replaced, |
| 130 | // because |dynamic_name_index_| tracks the most recent entry for a given |
| 131 | // name. |
| 132 | DCHECK_GT(new_entry->InsertionIndex(), |
| 133 | name_result.first->second->InsertionIndex()); |
| 134 | dynamic_name_index_.erase(name_result.first); |
| 135 | auto result = dynamic_name_index_.insert({new_entry->name(), new_entry}); |
| 136 | CHECK(result.second); |
| 137 | } |
| 138 | |
bnc | 7a1c21c | 2019-07-08 19:09:50 -0700 | [diff] [blame] | 139 | // Notify and deregister observers whose threshold is met, if any. |
bnc | bb40c7f | 2019-10-02 17:46:57 -0700 | [diff] [blame] | 140 | while (!observers_.empty()) { |
| 141 | auto it = observers_.begin(); |
| 142 | if (it->first > inserted_entry_count()) { |
| 143 | break; |
| 144 | } |
| 145 | Observer* observer = it->second; |
| 146 | observers_.erase(it); |
bnc | 7a1c21c | 2019-07-08 19:09:50 -0700 | [diff] [blame] | 147 | observer->OnInsertCountReachedThreshold(); |
| 148 | } |
| 149 | |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 150 | return new_entry; |
| 151 | } |
| 152 | |
bnc | 687c6e7 | 2019-07-29 10:57:40 -0700 | [diff] [blame] | 153 | uint64_t QpackHeaderTable::MaxInsertSizeWithoutEvictingGivenEntry( |
| 154 | uint64_t index) const { |
| 155 | DCHECK_LE(dropped_entry_count_, index); |
| 156 | |
| 157 | if (index > inserted_entry_count()) { |
| 158 | // All entries are allowed to be evicted. |
| 159 | return dynamic_table_capacity_; |
| 160 | } |
| 161 | |
| 162 | // Initialize to current available capacity. |
| 163 | uint64_t max_insert_size = dynamic_table_capacity_ - dynamic_table_size_; |
| 164 | |
| 165 | for (const auto& entry : dynamic_entries_) { |
| 166 | if (entry.InsertionIndex() >= index) { |
| 167 | break; |
| 168 | } |
| 169 | max_insert_size += entry.Size(); |
| 170 | } |
| 171 | |
| 172 | return max_insert_size; |
| 173 | } |
| 174 | |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 175 | bool QpackHeaderTable::SetDynamicTableCapacity(uint64_t capacity) { |
| 176 | if (capacity > maximum_dynamic_table_capacity_) { |
| 177 | return false; |
| 178 | } |
| 179 | |
| 180 | dynamic_table_capacity_ = capacity; |
| 181 | EvictDownToCurrentCapacity(); |
| 182 | |
| 183 | DCHECK_LE(dynamic_table_size_, dynamic_table_capacity_); |
| 184 | |
| 185 | return true; |
| 186 | } |
| 187 | |
renjietang | 033ca77 | 2020-06-12 10:28:22 -0700 | [diff] [blame] | 188 | bool QpackHeaderTable::SetMaximumDynamicTableCapacity( |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 189 | uint64_t maximum_dynamic_table_capacity) { |
renjietang | 033ca77 | 2020-06-12 10:28:22 -0700 | [diff] [blame] | 190 | if (maximum_dynamic_table_capacity_ == 0) { |
| 191 | maximum_dynamic_table_capacity_ = maximum_dynamic_table_capacity; |
| 192 | max_entries_ = maximum_dynamic_table_capacity / 32; |
| 193 | return true; |
| 194 | } |
| 195 | // If the value is already set, it should not be changed. |
| 196 | return maximum_dynamic_table_capacity == maximum_dynamic_table_capacity_; |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 197 | } |
| 198 | |
bnc | bb40c7f | 2019-10-02 17:46:57 -0700 | [diff] [blame] | 199 | void QpackHeaderTable::RegisterObserver(uint64_t required_insert_count, |
| 200 | Observer* observer) { |
bnc | 7a1c21c | 2019-07-08 19:09:50 -0700 | [diff] [blame] | 201 | DCHECK_GT(required_insert_count, 0u); |
bnc | bb40c7f | 2019-10-02 17:46:57 -0700 | [diff] [blame] | 202 | observers_.insert({required_insert_count, observer}); |
bnc | 7a1c21c | 2019-07-08 19:09:50 -0700 | [diff] [blame] | 203 | } |
| 204 | |
bnc | 2f2b742 | 2019-10-02 18:10:43 -0700 | [diff] [blame] | 205 | void 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 | |
bnc | b34a7ec | 2019-07-25 08:53:44 -0700 | [diff] [blame] | 220 | uint64_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 team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 245 | void QpackHeaderTable::EvictDownToCurrentCapacity() { |
| 246 | while (dynamic_table_size_ > dynamic_table_capacity_) { |
| 247 | DCHECK(!dynamic_entries_.empty()); |
| 248 | |
| 249 | QpackEntry* const entry = &dynamic_entries_.front(); |
bnc | b402517 | 2019-07-12 18:09:59 -0700 | [diff] [blame] | 250 | const uint64_t entry_size = entry->Size(); |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 251 | |
| 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 |