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 | |
| 5 | #include "net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h" |
| 6 | |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 7 | #include "net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h" |
vasilvv | 0fb4443 | 2019-03-13 22:47:36 -0700 | [diff] [blame] | 8 | #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 9 | |
| 10 | namespace quic { |
| 11 | |
| 12 | namespace { |
| 13 | |
| 14 | const uint64_t kEntrySizeOverhead = 32; |
| 15 | |
| 16 | uint64_t EntrySize(QuicStringPiece name, QuicStringPiece value) { |
| 17 | return name.size() + value.size() + kEntrySizeOverhead; |
| 18 | } |
| 19 | |
| 20 | } // anonymous namespace |
| 21 | |
| 22 | QpackHeaderTable::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 | |
| 32 | QpackHeaderTable::~QpackHeaderTable() = default; |
| 33 | |
| 34 | const 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 | |
| 57 | QpackHeaderTable::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 | |
| 103 | const 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 | |
bnc | 7a1c21c | 2019-07-08 19:09:50 -0700 | [diff] [blame] | 143 | // 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 team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 151 | return new_entry; |
| 152 | } |
| 153 | |
| 154 | bool 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 | |
| 167 | void 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 | |
bnc | 7a1c21c | 2019-07-08 19:09:50 -0700 | [diff] [blame] | 180 | void 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 | |
| 186 | bool QpackHeaderTable::ObserverWithThreshold::operator>( |
| 187 | const ObserverWithThreshold& other) const { |
| 188 | return required_insert_count > other.required_insert_count; |
| 189 | } |
| 190 | |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 191 | void 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 |