proxygen
QPACKEncoder.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  */
12 
13 using std::vector;
14 
15 namespace proxygen {
16 
17 QPACKEncoder::QPACKEncoder(bool huffman, uint32_t tableSize) :
18  // We only need the 'QPACK' table if we are using base index
19  HPACKEncoderBase(huffman),
20  QPACKContext(tableSize, true),
21  controlBuffer_(kBufferGrowth, huffman) {
22  // Default the encoder indexing strategy; it can be updated later as well
24 }
25 
27 QPACKEncoder::encode(const vector<HPACKHeader>& headers,
28  uint32_t headroom,
29  uint64_t streamId) {
30  if (headroom) {
31  streamBuffer_.addHeadroom(headroom);
32  }
34  return encodeQ(headers, streamId);
35 }
36 
38 QPACKEncoder::encodeQ(const vector<HPACKHeader>& headers, uint64_t streamId) {
39  auto& outstandingBlocks = outstanding_[streamId];
40  outstandingBlocks.emplace_back();
41  curOutstanding_ = &outstandingBlocks.back();
42  auto baseIndex = table_.getBaseIndex();
43 
44  uint32_t largestReference = 0;
45  for (const auto& header: headers) {
46  encodeHeaderQ(header, baseIndex, &largestReference);
47  }
48 
49  auto streamBlock = streamBuffer_.release();
50 
51  // encode the prefix
52  streamBuffer_.encodeInteger(largestReference);
53  if (largestReference > baseIndex) {
54  streamBuffer_.encodeInteger(largestReference - baseIndex,
56  HPACK::Q_DELTA_BASE.prefixLength);
57  } else {
58  streamBuffer_.encodeInteger(baseIndex - largestReference,
60  HPACK::Q_DELTA_BASE.prefixLength);
61  }
62  auto streamBuffer = streamBuffer_.release();
63  streamBuffer->prependChain(std::move(streamBlock));
64 
65  auto controlBuf = controlBuffer_.release();
66  // curOutstanding_.references could be empty, if the block encodes only static
67  // headers and/or literals
69  DCHECK(allowVulnerable());
71  }
72 
73  return { std::move(controlBuf), std::move(streamBuffer) };
74 }
75 
77  const HPACKHeader& header, uint32_t baseIndex, uint32_t* largestReference) {
78  uint32_t index = getStaticTable().getIndex(header);
79  if (index > 0) {
80  // static reference
83  HPACK::Q_INDEXED.prefixLength);
84  return;
85  }
86 
87  bool indexable = shouldIndex(header);
88  if (indexable) {
89  index = table_.getIndex(header, allowVulnerable());
90  if (index == QPACKHeaderTable::UNACKED) {
91  index = 0;
92  indexable = false;
93  }
94  }
95  if (index != 0) {
96  // dynamic reference
97  bool duplicated = false;
98  std::tie(duplicated, index) = maybeDuplicate(index);
99  // index is now 0 or absolute
100  indexable &= (duplicated && index == 0);
101  }
102  if (index == 0) {
103  // No valid entry matching header, see if there's a matching name
104  uint32_t nameIndex = 0;
105  uint32_t absoluteNameIndex = 0;
106  bool isStaticName = false;
107  std::tie(isStaticName, nameIndex, absoluteNameIndex) =
108  getNameIndexQ(header.name);
109 
110  // Now check if we should emit an insertion on the control stream
111  if (indexable && table_.canIndex(header)) {
112  encodeInsertQ(header, isStaticName, nameIndex);
113  CHECK(table_.add(header.copy()));
114  if (allowVulnerable()) {
115  index = table_.getBaseIndex();
116  } else {
117  index = 0;
118  if (!table_.isValid(table_.absoluteToRelative(absoluteNameIndex))) {
119  // The insert may have invalidated the name index.
120  isStaticName = true;
121  nameIndex = 0;
122  absoluteNameIndex = 0;
123  }
124  }
125  }
126 
127  if (index == 0) {
128  // Couldn't insert it: table full, not indexable, or table contains
129  // vulnerable reference. Encode a literal on the request stream.
130  encodeStreamLiteralQ(header, isStaticName, nameIndex, absoluteNameIndex,
131  baseIndex, largestReference);
132  return;
133  }
134  }
135 
136  // Encoding a dynamic index reference
137  DCHECK_NE(index, 0);
138  trackReference(index, largestReference);
139  if (index > baseIndex) {
140  streamBuffer_.encodeInteger(index - baseIndex - 1, HPACK::Q_INDEXED_POST);
141  } else {
142  streamBuffer_.encodeInteger(baseIndex - index, HPACK::Q_INDEXED);
143  }
144 }
145 
146 bool QPACKEncoder::shouldIndex(const HPACKHeader& header) const {
147  return (header.bytes() <= table_.capacity()) &&
149 }
150 
151 std::pair<bool, uint32_t> QPACKEncoder::maybeDuplicate(
152  uint32_t relativeIndex) {
153  auto res = table_.maybeDuplicate(relativeIndex, allowVulnerable());
154  if (res.first) {
155  VLOG(4) << "Encoded duplicate index=" << relativeIndex;
156  encodeDuplicate(relativeIndex);
157  }
158  return res;
159 }
160 
161 std::tuple<bool, uint32_t, uint32_t> QPACKEncoder::getNameIndexQ(
162  const HPACKHeaderName& headerName) {
163  uint32_t absoluteNameIndex = 0;
164  uint32_t nameIndex = getStaticTable().nameIndex(headerName);
165  bool isStatic = true;
166  if (nameIndex == 0) {
167  // check dynamic table
168  nameIndex = table_.nameIndex(headerName, allowVulnerable());
169  if (nameIndex != 0) {
170  absoluteNameIndex = maybeDuplicate(nameIndex).second;
171  if (absoluteNameIndex) {
172  isStatic = false;
173  nameIndex = table_.absoluteToRelative(absoluteNameIndex);
174  } else {
175  nameIndex = 0;
176  absoluteNameIndex = 0;
177  }
178  }
179  }
180  return std::tuple<bool, uint32_t, uint32_t>(
181  isStatic, nameIndex, absoluteNameIndex);
182 }
183 
185  const HPACKHeader& header, bool isStaticName, uint32_t nameIndex,
186  uint32_t absoluteNameIndex, uint32_t baseIndex, uint32_t* largestReference) {
187  if (absoluteNameIndex > 0) {
188  // Dynamic name reference, vulnerability checks already done
189  CHECK(absoluteNameIndex <= baseIndex || allowVulnerable());
190  trackReference(absoluteNameIndex, largestReference);
191  }
192  if (absoluteNameIndex > baseIndex) {
193  encodeLiteralQ(header,
194  false, /* not static */
195  true, /* post base */
196  absoluteNameIndex - baseIndex,
198  } else {
199  encodeLiteralQ(header,
200  isStaticName,
201  false, /* not post base */
202  isStaticName ? nameIndex : baseIndex - absoluteNameIndex + 1,
204  }
205 }
206 
208  uint32_t* largestReference) {
209  CHECK_NE(absoluteIndex, 0);
210  CHECK(curOutstanding_);
211  if (absoluteIndex > *largestReference) {
212  *largestReference = absoluteIndex;
213  if (table_.isVulnerable(absoluteIndex)) {
214  curOutstanding_->vulnerable = true;
215  }
216  }
217  auto res = curOutstanding_->references.insert(absoluteIndex);
218  if (res.second) {
219  VLOG(5) << "Bumping refcount for absoluteIndex=" << absoluteIndex;
220  table_.addRef(absoluteIndex);
221  }
222 }
223 
225  DCHECK_GT(index, 0);
227 }
228 
230  bool isStaticName,
231  uint32_t nameIndex) {
233  controlBuffer_, header, isStaticName, nameIndex,
236 }
237 
239  bool isStaticName,
240  bool postBase,
241  uint32_t nameIndex,
242  const HPACK::Instruction& idxInstr) {
243  DCHECK(!isStaticName || !postBase);
245  streamBuffer_, header, isStaticName, nameIndex,
246  HPACK::Q_LITERAL_STATIC, idxInstr,
248 }
249 
251  const HPACKHeader& header,
252  bool isStaticName,
253  uint32_t nameIndex,
254  uint8_t staticFlag,
255  const HPACK::Instruction& idxInstr,
256  const HPACK::Instruction& litInstr) {
257  // name
258  if (nameIndex) {
259  VLOG(10) << "encoding name index=" << nameIndex;
260  DCHECK_NE(nameIndex, QPACKHeaderTable::UNACKED);
261  uint8_t byte = idxInstr.code;
262  if (isStaticName) {
263  byte |= staticFlag;
264  } else {
265  DCHECK_GE(nameIndex, 1);
266  nameIndex -= 1;
267  }
268  buffer.encodeInteger(nameIndex, byte, idxInstr.prefixLength);
269  } else {
270  buffer.encodeLiteral(litInstr.code, litInstr.prefixLength,
271  header.name.get());
272  }
273  // value
274  buffer.encodeLiteral(header.value);
275 }
276 
278  std::unique_ptr<folly::IOBuf> buf) {
281  HPACKDecodeBuffer dbuf(cursor, decoderIngress_.chainLength(), 0);
283  uint32_t consumed = 0;
284  while (err == HPACK::DecodeError::NONE && !dbuf.empty()) {
285  consumed = dbuf.consumedBytes();
286  auto byte = dbuf.peek();
287  if (byte & HPACK::Q_HEADER_ACK.code) {
288  err = decodeHeaderAck(dbuf, HPACK::Q_HEADER_ACK.prefixLength, false);
289  } else if (byte & HPACK::Q_CANCEL_STREAM.code) {
290  err = decodeHeaderAck(dbuf, HPACK::Q_CANCEL_STREAM.prefixLength, true);
291  } else { // TABLE_STATE_SYNC
292  uint64_t inserts = 0;
293  err = dbuf.decodeInteger(HPACK::Q_TABLE_STATE_SYNC.prefixLength, inserts);
294  if (err == HPACK::DecodeError::NONE) {
295  err = onTableStateSync(inserts);
296  } else if (err != HPACK::DecodeError::BUFFER_UNDERFLOW) {
297  LOG(ERROR) << "Failed to decode num inserts, err=" << err;
298  }
299  }
300  } // while
303  decoderIngress_.trimStart(consumed);
304  } else {
305  decoderIngress_.trimStart(dbuf.consumedBytes());
306  }
307  return err;
308 }
309 
311  uint8_t prefixLength,
312  bool all) {
313  uint64_t streamId = 0;
314  auto err = dbuf.decodeInteger(prefixLength, streamId);
315  if (err == HPACK::DecodeError::NONE) {
316  err = onHeaderAck(streamId, all);
317  } else if (err != HPACK::DecodeError::BUFFER_UNDERFLOW) {
318  LOG(ERROR) << "Failed to decode streamId, err=" << err;
319  }
320  return err;
321 }
322 
324  if (!table_.onTableStateSync(inserts)) {
326  }
328 }
329 
331  auto it = outstanding_.find(streamId);
332  if (it == outstanding_.end()) {
333  if (!all) {
334  LOG(ERROR) << "Received an ack with no outstanding header blocks stream="
335  << streamId;
337  } else {
338  // all implies a reset, meaning it's not an error if there are no
339  // outstanding blocks
341  }
342  }
343  VLOG(5) << ((all) ? "onCancelStream" : "onHeaderAck") << " streamId="
344  << streamId;
345  if (all) {
346  // Happens when a stream is reset
347  for (auto& block: it->second) {
348  for (auto i: block.references) {
349  table_.subRef(i);
350  }
351  if (block.vulnerable) {
352  numVulnerable_--;
353  }
354  }
355  it->second.clear();
356  } else {
357  auto block = std::move(it->second.front());
358  it->second.pop_front();
359  // a different stream, sub all the references
360  for (auto i: block.references) {
361  VLOG(5) << "Decrementing refcount for absoluteIndex=" << i;
362  table_.subRef(i);
363  }
364  if (block.vulnerable) {
365  numVulnerable_--;
366  }
367  // largest reference is implicitly acknowledged
368  if (!block.references.empty()) {
369  auto largestReference = *block.references.rbegin();
370  VLOG(5) << "Implicitly acknowledging absoluteIndex=" << largestReference;
371  table_.setMaxAcked(largestReference);
372  }
373  }
374  if (it->second.empty()) {
375  outstanding_.erase(it);
376  }
378 }
379 
380 }
const Instruction Q_TABLE_STATE_SYNC
std::vector< uint8_t > buffer(kBufferSize+16)
const Instruction Q_INSERT_NO_NAME_REF
const folly::IOBuf * front() const
Definition: IOBufQueue.h:476
void append(std::unique_ptr< folly::IOBuf > &&buf, bool pack=false)
Definition: IOBufQueue.cpp:143
QPACKHeaderTable table_
Definition: QPACKContext.h:54
void encodeInsertQ(const HPACKHeader &header, bool isStaticName, uint32_t nameIndex)
size_t chainLength() const
Definition: IOBufQueue.h:492
std::pair< bool, uint32_t > maybeDuplicate(uint32_t relativeIndex)
uint32_t encodeLiteral(folly::StringPiece literal)
uint32_t capacity() const
Definition: HeaderTable.h:86
std::unique_ptr< folly::IOBuf > release()
const uint8_t Q_DELTA_BASE_POS
HPACKEncodeBuffer controlBuffer_
Definition: QPACKEncoder.h:115
bool shouldIndex(const HPACKHeader &header) const
void setHeaderIndexingStrategy(const HeaderIndexingStrategy *indexingStrat)
const Instruction Q_DUPLICATE
const Instruction Q_INDEXED_POST
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
void encodeLiteralQHelper(HPACKEncodeBuffer &buffer, const HPACKHeader &header, bool isStaticName, uint32_t nameIndex, uint8_t staticFlag, const HPACK::Instruction &idxInstr, const HPACK::Instruction &litInstr)
const Instruction Q_LITERAL_NAME_REF_POST
HPACKEncodeBuffer streamBuffer_
void addHeadroom(uint32_t bytes)
HPACK::DecodeError onHeaderAck(uint64_t streamId, bool all)
void encodeDuplicate(uint32_t index)
const Instruction Q_LITERAL
uint32_t getBaseIndex() const
void encodeStreamLiteralQ(const HPACKHeader &header, bool isStaticName, uint32_t nameIndex, uint32_t absoluteNameIndex, uint32_t baseIndex, uint32_t *largestReference)
OutstandingBlock * curOutstanding_
Definition: QPACKEncoder.h:123
bool isVulnerable(uint32_t absIndex) const
HPACK::DecodeError decodeDecoderStream(std::unique_ptr< folly::IOBuf > buf)
void setMaxAcked(uint32_t maxAcked)
bool onTableStateSync(uint32_t inserts)
EncodeResult encode(const std::vector< HPACKHeader > &headers, uint32_t headroom, uint64_t streamId)
void trackReference(uint32_t index, uint32_t *largestReference)
QPACKEncoder(bool huffman, uint32_t tableSize=HPACK::kTableSize)
uint32_t bytes() const
Definition: HPACKHeader.h:49
bool add(HPACKHeader header) override
void encodeHeaderQ(const HPACKHeader &header, uint32_t baseIndex, uint32_t *largestReference)
bool allowVulnerable() const
Definition: QPACKEncoder.h:66
virtual bool indexHeader(const HPACKHeader &header) const
const uint8_t Q_LITERAL_STATIC
uint32_t absoluteToRelative(uint32_t absIndex) const
const uint8_t Q_INSERT_NAME_REF_STATIC
bool canIndex(const HPACKHeader &header)
std::pair< bool, uint32_t > maybeDuplicate(uint32_t relativeIndex, bool allowVulnerable)
void encodeLiteralQ(const HPACKHeader &header, bool isStaticName, bool postBase, uint32_t nameIndex, const HPACK::Instruction &idxInstr)
void handlePendingContextUpdate(HPACKEncodeBuffer &buf, uint32_t tableCapacity)
const Instruction Q_HEADER_ACK
const Instruction Q_INSERT_NAME_REF
HPACKHeaderName name
Definition: HPACKHeader.h:82
uint32_t encodeInteger(uint64_t value, uint8_t instruction, uint8_t nbit)
static const HeaderIndexingStrategy * getDefaultInstance()
const Instruction Q_DELTA_BASE
uint32_t nameIndex(const HPACKHeaderName &headerName) const
Definition: HeaderTable.cpp:88
bool isValid(uint32_t index, uint32_t base=0) const
folly::fbstring value
Definition: HPACKHeader.h:83
HPACK::DecodeError decodeHeaderAck(HPACKDecodeBuffer &dbuf, uint8_t prefixLength, bool all)
const StaticHeaderTable & getStaticTable() const
Definition: QPACKContext.h:50
void addRef(uint32_t absIndex)
const uint8_t Q_INDEXED_STATIC
folly::IOBufQueue decoderIngress_
Definition: QPACKEncoder.h:127
HPACK::DecodeError onTableStateSync(uint32_t inserts)
const HeaderIndexingStrategy * indexingStrat_
uint32_t getIndex(const HPACKHeader &header, bool allowVulnerable=true) const
void trimStart(size_t amount)
Definition: IOBufQueue.cpp:255
void subRef(uint32_t absIndex)
HPACK::DecodeError decodeInteger(uint8_t nbit, uint64_t &integer)
HPACKHeader copy() const
Definition: HPACKHeader.h:71
const uint8_t Q_DELTA_BASE_NEG
std::unordered_map< uint64_t, std::list< OutstandingBlock > > outstanding_
Definition: QPACKEncoder.h:122
uint32_t getIndex(const HPACKHeader &header) const
Definition: HeaderTable.cpp:63
std::tuple< bool, uint32_t, uint32_t > getNameIndexQ(const HPACKHeaderName &headerName)
const Instruction Q_INDEXED
const Instruction Q_LITERAL_NAME_REF
QPACKEncoder::EncodeResult encodeQ(const std::vector< HPACKHeader > &headers, uint64_t streamId)
const Instruction Q_CANCEL_STREAM
uint32_t nameIndex(const HPACKHeaderName &headerName, bool allowVulnerable=true) const
Composed all(Predicate pred=Predicate())
Definition: Base.h:786
const std::string & get() const