proxygen
QPACKHeaderTable.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-present, Facebook, Inc.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * LICENSE file in the root directory of this source tree. An additional grant
7  * of patent rights can be found in the PATENTS file in the same directory.
8  *
9  */
11 
12 #include <glog/logging.h>
13 
14 using std::list;
15 using std::pair;
16 using std::string;
17 
18 namespace {
19 // For tables 0..384 minFree = 48
20 // For tables 385..4096 minFree = tableSize/8
21 // For tables 4096+ minFree = 512
22 
23 // Determines how much space in the table should be reserved for new inserts.
24 // Min Free is set to tableSize / kMinFreeSlice
25 const uint32_t kMinFreeSlice = 8;
26 
27 const uint32_t kMinMinFree = 48;
28 const uint32_t kMaxMinFree = 512;
29 
30 uint32_t getMinFree(uint32_t tableSize) {
31  return std::min(std::max(tableSize / kMinFreeSlice, kMinMinFree),
32  kMaxMinFree);
33 }
34 
35 }
36 
37 namespace proxygen {
38 
39 QPACKHeaderTable::QPACKHeaderTable(uint32_t capacityVal, bool trackReferences)
40  : HeaderTable(capacityVal) {
41  if (trackReferences) {
42  refCount_ = std::make_unique<std::vector<uint16_t>>(table_.size(), 0);
43  minFree_ = getMinFree(capacityVal);
44  } else {
45  minFree_ = 0;
46  }
47 }
48 
51  LOG(ERROR) << "Cowardly refusing to add more entries since baseIndex_ "
52  " would wrap";
53  return false;
54  }
55 
56  VLOG(6) << "Adding header=" << header;
57  if (!HeaderTable::add(std::move(header))) {
58  return false;
59  }
60  if (refCount_) {
61  (*refCount_)[head_] = 0;
62  }
63  ++baseIndex_;
64  DCHECK_EQ(internalToAbsolute(head_), baseIndex_);
65  // Increase minUsable_ until the free space + drainedBytes is >= minFree.
66  // For HPACK, minFree is 0 and this is a no-op.
67  while (capacity_ - bytes_ + drainedBytes_ < minFree_ &&
69  auto bytes = table_[absoluteToInternal(minUsable_)].bytes();
70  VLOG(5) << "Draining absolute index " << minUsable_ << " bytes="
71  << bytes << " drainedBytes_= " << (drainedBytes_ + bytes);
73  minUsable_++;
74  }
75  return true;
76 }
77 
79  if (!HeaderTable::setCapacity(capacity)) {
80  return false;
81  }
82  if (refCount_) {
83  minFree_ = getMinFree(capacity);
84  } // else minFree is always 0
85  return true;
86 }
87 
89  bool allowVulnerable) const {
90  return getIndexImpl(header.name, header.value, false, allowVulnerable);
91 }
92 
94  const folly::fbstring& value,
95  bool nameOnly,
96  bool allowVulnerable) const {
97  auto it = names_.find(headerName);
98  if (it == names_.end()) {
99  return 0;
100  }
101  bool encoderHasUnackedEntry = false;
102  // Searching backwards gives smallest index, but more likely vulnerable
103  // Searching forwards least likely vulnerable but could prevent eviction
104  for (auto indexIt = it->second.rbegin(); indexIt != it->second.rend();
105  ++indexIt) {
106  auto i = *indexIt;
107  if (nameOnly || table_[i].value == value) {
108  // allow vulnerable or not vulnerable
109  if (allowVulnerable || internalToAbsolute(i) <= maxAcked_) {
110  // index *may* be draining, caller has to check
111  return toExternal(i);
112  } else {
113  encoderHasUnackedEntry = true;
114  }
115  }
116  }
117  if (encoderHasUnackedEntry) {
118  return UNACKED;
119  }
120  return 0;
121 }
122 
124  bool allowVulnerable) const {
126  return getIndexImpl(headerName, value, true /* name only */, allowVulnerable);
127 }
128 
130  uint32_t base) const {
131  CHECK(isValid(index, base));
132  return table_[toInternal(index, base)];
133 }
134 
136  auto idx = tail();
137  if (refCount_) {
138  CHECK_EQ((*refCount_)[idx], 0) << "Removed header with nonzero references";
139  }
140  auto removedBytes = HeaderTable::removeLast();
141  // Only non-zero when minUsable_ > baseIndex_ - size_.
142  if (drainedBytes_ > 0) {
143  VLOG(5) << "Removing draining entry=" << idx << " size=" << removedBytes
144  << " drainedBytes_=" << drainedBytes_ << " new drainedBytes_="
145  << (int32_t(drainedBytes_) - removedBytes);
146  CHECK_GE(drainedBytes_, removedBytes);
147  drainedBytes_ -= removedBytes;
148  } else {
149  // Keep minUsable_ as a valid index when evicting an undrained header
150  if (size() > 0) {
152  } else {
153  minUsable_ = baseIndex_ + 1;
154  }
155  }
156  return removedBytes;
157 }
158 
161  if (size_ > 0) {
162  DCHECK_EQ(internalToAbsolute(head_), baseIndex_);
163  DCHECK_EQ(internalToAbsolute(tail()), baseIndex_ - size_ + 1);
164  }
165 }
166 
168  HeaderTable::resizeTable(newLength);
169  if (refCount_) {
170  refCount_->resize(newLength);
171  }
172 }
173 
175  uint32_t oldTail, uint32_t oldLength, uint32_t newLength) {
176  HeaderTable::updateResizedTable(oldTail, oldLength, newLength);
177  if (refCount_) {
178  std::move_backward(refCount_->begin() + oldTail,
179  refCount_->begin() + oldLength,
180  refCount_->begin() + newLength);
181  }
182 }
183 
185  if (bytes_ + needed < desiredCapacity ||
186  !canEvict(bytes_ + needed - desiredCapacity)) {
187  return 0;
188  }
189  return HeaderTable::evict(needed, desiredCapacity);
190 }
191 
193  if (size_ == 0 || !refCount_) {
194  return needed <= capacity_;
195  }
196  uint32_t freeable = 0;
197  uint32_t i = tail();
198  uint32_t nChecked = 0;
199  while (nChecked++ < size() && freeable < needed && ((*refCount_)[i] == 0) &&
200  internalToAbsolute(i) <= maxAcked_) { // don't evict unacked headers
201  freeable += table_[i].bytes();
202  i = next(i);
203  }
204  if (freeable < needed) {
205  VLOG(5) << "header=" << table_[i].name << " blocked eviction, recount="
206  << (*refCount_)[i];
207  return false;
208  }
209  return true;
210 }
211 
212 bool QPACKHeaderTable::isValid(uint32_t index, uint32_t base) const {
213  int64_t testIndex = index;
214  if (base > 0) {
215  auto baseOffset = ((int64_t)base - (int64_t)baseIndex_);
216  // recompute relative to current baseIndex_. testIndex may go negative
217  // if this is a reference to an entry that hasn't arrived yet
218  testIndex -= baseOffset;
219  }
220  return HeaderTable::isValid(testIndex);
221 }
222 
223 // Checks if relativeIndex is draining. If not, returns the corresponding
224 // absolute index. Otherwise, attempt to duplicate. If duplication is
225 // successful, and vulnerable references are allowed, return absolute index of
226 // the duplicate. If duplication is unsuccessful, or vulnerable references are
227 // not allowed, return 0.
228 std::pair<bool, uint32_t> QPACKHeaderTable::maybeDuplicate(
229  uint32_t relativeIndex,
230  bool allowVulnerable) {
231  if (relativeIndex == UNACKED) {
232  return {false, 0};
233  }
234  DCHECK(isValid(relativeIndex));
235  uint32_t absIndex = relativeToAbsolute(relativeIndex);
236  DCHECK(!isVulnerable(absIndex) || allowVulnerable);
237  if (absIndex < minUsable_) {
238  // draining
239  const HPACKHeader& header = getHeader(relativeIndex);
240  if (canIndex(header)) {
241  CHECK(add(header.copy()));
242  if (allowVulnerable) {
243  return {true, baseIndex_};
244  } else {
245  return {true, 0};
246  }
247  } else {
248  return {false, 0};
249  }
250  }
251  return {false, absIndex};
252 }
253 
255  // refCount is 16 bits. It should really never get this big in practice,
256  // unless a decoder is not sending HEADER_ACK in a timely way.
257  CHECK(refCount_);
258  (*refCount_)[absoluteToInternal(absIndex)]++;
259 }
260 
262  CHECK(refCount_);
263  uint32_t index = absoluteToInternal(absIndex);
264  CHECK_GT((*refCount_)[index], 0);
265  (*refCount_)[index]--;
266 }
267 
268 // Converts an array index in [0..table_.size() - 1] to an absolute
269 // external index
271  uint32_t internalIndex) const {
272  return relativeToAbsolute(toExternal(internalIndex));
273 }
274 
275 // Converts an absolute index to an array index in [0..table_.size() - 1]
277  uint32_t absoluteIndex) const {
278  return toInternal(absoluteToRelative(absoluteIndex), 0);
279 }
280 
282  uint32_t externalIndex, uint32_t base) const {
283  if (base > 0) {
284  uint32_t absIndex = base - externalIndex + 1;
285  externalIndex = absoluteToRelative(absIndex);
286  }
287  return HeaderTable::toInternal(externalIndex);
288 }
289 
290 }
void updateResizedTable(uint32_t oldTail, uint32_t oldLength, uint32_t newLength) override
void resizeTable(uint32_t newLength) override
uint32_t capacity() const
Definition: HeaderTable.h:86
virtual uint32_t evict(uint32_t needed, uint32_t desiredCapacity)
QPACKHeaderTable(uint32_t capacityVal, bool trackReferences)
LogLevel max
Definition: LogLevel.cpp:31
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
uint32_t internalToAbsolute(uint32_t internalIndex) const
uint32_t toInternal(uint32_t externalIndex, uint32_t base) const
uint32_t absoluteToInternal(uint32_t absoluteIndex) const
uint32_t bytes() const
Definition: HeaderTable.h:112
bool isValid(uint32_t index) const
uint32_t removeLast() override
bool isVulnerable(uint32_t absIndex) const
std::unique_ptr< std::vector< uint16_t > > refCount_
uint32_t next(uint32_t i) const
static uint32_t toExternal(uint32_t head, uint32_t length, uint32_t internalIndex)
virtual void increaseTableLengthTo(uint32_t newLength)
bool add(HPACKHeader header) override
LogLevel min
Definition: LogLevel.cpp:30
uint32_t absoluteToRelative(uint32_t absIndex) const
bool canIndex(const HPACKHeader &header)
std::pair< bool, uint32_t > maybeDuplicate(uint32_t relativeIndex, bool allowVulnerable)
Encoder::MutableCompressedList list
static uint32_t toInternal(uint32_t head, uint32_t length, uint32_t externalIndex)
HPACKHeaderName name
Definition: HPACKHeader.h:82
static const char *const value
Definition: Conv.cpp:50
uint32_t getIndexImpl(const HPACKHeaderName &header, const folly::fbstring &value, bool nameOnly, bool allowVulnerable=true) const
bool isValid(uint32_t index, uint32_t base=0) const
virtual void updateResizedTable(uint32_t oldTail, uint32_t oldLength, uint32_t newLength)
virtual uint32_t removeLast()
folly::fbstring value
Definition: HPACKHeader.h:83
virtual void resizeTable(uint32_t newLength)
uint32_t evict(uint32_t needed, uint32_t desiredCapacity) override
void addRef(uint32_t absIndex)
const char * string
Definition: Conv.cpp:212
const HPACKHeader & getHeader(uint32_t index, uint32_t base=0) const
uint32_t relativeToAbsolute(uint32_t relativeIndex) const
uint32_t getIndex(const HPACKHeader &header, bool allowVulnerable=true) const
void increaseTableLengthTo(uint32_t newLength) override
void subRef(uint32_t absIndex)
std::vector< HPACKHeader > table_
Definition: HeaderTable.h:189
HPACKHeader copy() const
Definition: HPACKHeader.h:71
virtual bool setCapacity(uint32_t capacity)
uint32_t size() const
Definition: HeaderTable.h:105
uint32_t tail() const
bool setCapacity(uint32_t capacity) override
virtual bool add(HPACKHeader header)
Definition: HeaderTable.cpp:33
bool canEvict(uint32_t needed)
uint32_t nameIndex(const HPACKHeaderName &headerName, bool allowVulnerable=true) const