1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
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.
7 */
8
9 #include <proxygen/lib/http/codec/compress/QPACKEncoder.h>
10
11 #include <proxygen/lib/http/codec/compress/HPACKDecodeBuffer.h>
12
13 using std::vector;
14
15 namespace proxygen {
16
QPACKEncoder(bool huffman,uint32_t tableSize)17 QPACKEncoder::QPACKEncoder(bool huffman, uint32_t tableSize)
18 : HPACKEncoderBase(huffman),
19 QPACKContext(tableSize, true),
20 controlBuffer_(kBufferGrowth, huffman),
21 maxTableSize_(tableSize) {
22 // Default the encoder indexing strategy; it can be updated later as well
23 setHeaderIndexingStrategy(HeaderIndexingStrategy::getDefaultInstance());
24 }
25
encode(const vector<HPACKHeader> & headers,uint32_t headroom,uint64_t streamId,uint32_t maxEncoderStreamBytes)26 QPACKEncoder::EncodeResult QPACKEncoder::encode(
27 const vector<HPACKHeader>& headers,
28 uint32_t headroom,
29 uint64_t streamId,
30 uint32_t maxEncoderStreamBytes) {
31 // This routine is now used only for testing
32 folly::IOBufQueue controlQueue{folly::IOBufQueue::cacheChainLength()};
33 startEncode(controlQueue, headroom, maxEncoderStreamBytes);
34 auto baseIndex = table_.getInsertCount();
35
36 uint32_t requiredInsertCount = 0;
37 for (const auto& header : headers) {
38 encodeHeaderQ(HPACKHeaderName(header.name),
39 header.value,
40 baseIndex,
41 /*ref*/ requiredInsertCount);
42 }
43
44 return {controlQueue.move(),
45 completeEncode(streamId, baseIndex, requiredInsertCount)};
46 }
47
startEncode(folly::IOBufQueue & controlQueue,uint32_t headroom,uint32_t maxEncoderStreamBytes)48 uint32_t QPACKEncoder::startEncode(folly::IOBufQueue& controlQueue,
49 uint32_t headroom,
50 uint32_t maxEncoderStreamBytes) {
51 controlBuffer_.setWriteBuf(&controlQueue);
52 if (headroom) {
53 streamBuffer_.addHeadroom(headroom);
54 }
55 maxEncoderStreamBytes_ = maxEncoderStreamBytes;
56 maxEncoderStreamBytes_ -=
57 handlePendingContextUpdate(controlBuffer_, table_.capacity());
58
59 return table_.getInsertCount();
60 }
61
completeEncode(uint64_t streamId,uint32_t baseIndex,uint32_t requiredInsertCount)62 std::unique_ptr<folly::IOBuf> QPACKEncoder::completeEncode(
63 uint64_t streamId, uint32_t baseIndex, uint32_t requiredInsertCount) {
64 auto streamBlock = streamBuffer_.release();
65
66 // encode the prefix
67 if (requiredInsertCount == 0) {
68 streamBuffer_.encodeInteger(0); // required insert count
69 streamBuffer_.encodeInteger(0); // delta base
70 } else {
71 auto wireRIC =
72 (requiredInsertCount % (2 * getMaxEntries(maxTableSize_))) + 1;
73 streamBuffer_.encodeInteger(wireRIC);
74 if (requiredInsertCount > baseIndex) {
75 streamBuffer_.encodeInteger(requiredInsertCount - baseIndex - 1,
76 HPACK::Q_DELTA_BASE_NEG,
77 HPACK::Q_DELTA_BASE.prefixLength);
78 } else {
79 streamBuffer_.encodeInteger(baseIndex - requiredInsertCount,
80 HPACK::Q_DELTA_BASE_POS,
81 HPACK::Q_DELTA_BASE.prefixLength);
82 }
83 }
84 auto streamBuffer = streamBuffer_.release();
85 if (streamBlock) {
86 streamBuffer->prependChain(std::move(streamBlock));
87 }
88
89 // curOutstanding_.references could be empty, if the block encodes only static
90 // headers and/or literals. If so we don't track anything.
91 if (!curOutstanding_.references.empty()) {
92 if (curOutstanding_.vulnerable) {
93 DCHECK(allowVulnerable());
94 numVulnerable_++;
95 }
96 numOutstandingBlocks_++;
97 outstanding_[streamId].emplace_back(std::move(curOutstanding_));
98 curOutstanding_.vulnerable = false;
99 }
100
101 controlBuffer_.setWriteBuf(nullptr);
102 return streamBuffer;
103 }
104
encodeHeaderQ(HPACKHeaderName name,folly::StringPiece value,uint32_t baseIndex,uint32_t & requiredInsertCount)105 size_t QPACKEncoder::encodeHeaderQ(HPACKHeaderName name,
106 folly::StringPiece value,
107 uint32_t baseIndex,
108 uint32_t& requiredInsertCount) {
109 size_t uncompressed = HPACKHeader::realBytes(name.size(), value.size()) + 2;
110 uint32_t index = getStaticTable().getIndex(name, value);
111 if (index > 0) {
112 // static reference
113 staticRefs_++;
114 streamBuffer_.encodeInteger(index - 1,
115 HPACK::Q_INDEXED.code | HPACK::Q_INDEXED_STATIC,
116 HPACK::Q_INDEXED.prefixLength);
117 return uncompressed;
118 }
119
120 bool indexable = shouldIndex(name, value);
121 if (indexable) {
122 index = table_.getIndex(name, value, allowVulnerable());
123 if (index == QPACKHeaderTable::UNACKED) {
124 index = 0;
125 indexable = false;
126 }
127 }
128 if (index != 0) {
129 // dynamic reference
130 bool duplicated = false;
131 std::tie(duplicated, index) = maybeDuplicate(index);
132 // index is now 0 or absolute
133 indexable &= (duplicated && index == 0);
134 }
135 if (index == 0) {
136 // No valid entry matching header, see if there's a matching name
137 uint32_t nameIndex = 0;
138 uint32_t absoluteNameIndex = 0;
139 bool isStaticName = false;
140 std::tie(isStaticName, nameIndex, absoluteNameIndex) = getNameIndexQ(name);
141
142 // Now check if we should emit an insertion on the control stream
143 // Don't try to index if we're out of encoder flow control
144 indexable &= maxEncoderStreamBytes_ > 0;
145 if (indexable) {
146 if (table_.canIndex(name, value)) {
147 encodeInsertQ(name, value, isStaticName, nameIndex);
148 CHECK(table_.add(HPACKHeader(std::move(name), value)));
149 if (allowVulnerable() && lastEntryAvailable()) {
150 index = table_.getInsertCount();
151 // name is invalid on this branch, but index must be non-zero since
152 // we inserted just above.
153 } else {
154 // We still need name. Get it from the table
155 name = getHeader(false, 1, table_.getInsertCount(), false).name;
156 index = 0;
157 if (absoluteNameIndex > 0 &&
158 !table_.isValid(table_.absoluteToRelative(absoluteNameIndex))) {
159 // The insert may have invalidated the name index.
160 isStaticName = true;
161 nameIndex = 0;
162 absoluteNameIndex = 0;
163 }
164 }
165 } else {
166 blockedInsertions_++;
167 }
168 }
169 if (index == 0) {
170 // Couldn't insert it: table full, not indexable, or table contains
171 // vulnerable reference. Encode a literal on the request stream.
172 encodeStreamLiteralQ(name,
173 value,
174 isStaticName,
175 nameIndex,
176 absoluteNameIndex,
177 baseIndex,
178 requiredInsertCount);
179 return uncompressed;
180 }
181 }
182
183 // Encoding a dynamic index reference
184 DCHECK_NE(index, 0);
185 trackReference(index, requiredInsertCount);
186 if (index > baseIndex) {
187 streamBuffer_.encodeInteger(index - baseIndex - 1, HPACK::Q_INDEXED_POST);
188 } else {
189 streamBuffer_.encodeInteger(baseIndex - index, HPACK::Q_INDEXED);
190 }
191 return uncompressed;
192 }
193
shouldIndex(const HPACKHeaderName & name,folly::StringPiece value) const194 bool QPACKEncoder::shouldIndex(const HPACKHeaderName& name,
195 folly::StringPiece value) const {
196 return (HPACKHeader::bytes(name.size(), value.size()) <= table_.capacity()) &&
197 (!indexingStrat_ || indexingStrat_->indexHeader(name, value)) &&
198 dynamicReferenceAllowed();
199 }
200
dynamicReferenceAllowed() const201 bool QPACKEncoder::dynamicReferenceAllowed() const {
202 return numOutstandingBlocks_ < maxNumOutstandingBlocks_;
203 }
204
maybeDuplicate(uint32_t relativeIndex)205 std::pair<bool, uint32_t> QPACKEncoder::maybeDuplicate(uint32_t relativeIndex) {
206 auto res = table_.maybeDuplicate(relativeIndex, allowVulnerable());
207 if (res.first) {
208 VLOG(4) << "Encoded duplicate index=" << relativeIndex;
209 duplications_++;
210 encodeDuplicate(relativeIndex);
211 // Note we will emit duplications even when we are out of flow control,
212 // but we won't reference them (eg: like we were at vulnerable max).
213 if (!lastEntryAvailable()) {
214 VLOG(4) << "Duplicate is not usable because it overran encoder flow "
215 "control";
216 return {true, 0};
217 }
218 }
219 return res;
220 }
221
getNameIndexQ(const HPACKHeaderName & headerName)222 std::tuple<bool, uint32_t, uint32_t> QPACKEncoder::getNameIndexQ(
223 const HPACKHeaderName& headerName) {
224 uint32_t absoluteNameIndex = 0;
225 uint32_t nameIndex = getStaticTable().nameIndex(headerName);
226 bool isStatic = true;
227 if (nameIndex == 0 && dynamicReferenceAllowed()) {
228 // check dynamic table
229 nameIndex = table_.nameIndex(headerName, allowVulnerable());
230 if (nameIndex != 0) {
231 absoluteNameIndex = maybeDuplicate(nameIndex).second;
232 if (absoluteNameIndex) {
233 isStatic = false;
234 nameIndex = table_.absoluteToRelative(absoluteNameIndex);
235 } else {
236 nameIndex = 0;
237 absoluteNameIndex = 0;
238 }
239 }
240 }
241 return std::tuple<bool, uint32_t, uint32_t>(
242 isStatic, nameIndex, absoluteNameIndex);
243 }
244
encodeStreamLiteralQ(const HPACKHeaderName & name,folly::StringPiece value,bool isStaticName,uint32_t nameIndex,uint32_t absoluteNameIndex,uint32_t baseIndex,uint32_t & requiredInsertCount)245 size_t QPACKEncoder::encodeStreamLiteralQ(const HPACKHeaderName& name,
246 folly::StringPiece value,
247 bool isStaticName,
248 uint32_t nameIndex,
249 uint32_t absoluteNameIndex,
250 uint32_t baseIndex,
251 uint32_t& requiredInsertCount) {
252 if (absoluteNameIndex > 0) {
253 // Dynamic name reference, vulnerability checks already done
254 CHECK(absoluteNameIndex <= baseIndex || allowVulnerable());
255 trackReference(absoluteNameIndex, requiredInsertCount);
256 }
257 if (absoluteNameIndex > baseIndex) {
258 return encodeLiteralQ(name,
259 value,
260 false, /* not static */
261 true, /* post base */
262 absoluteNameIndex - baseIndex,
263 HPACK::Q_LITERAL_NAME_REF_POST);
264 } else {
265 return encodeLiteralQ(name,
266 value,
267 isStaticName,
268 false, /* not post base */
269 isStaticName ? nameIndex
270 : baseIndex - absoluteNameIndex + 1,
271 HPACK::Q_LITERAL_NAME_REF);
272 }
273 }
274
trackReference(uint32_t absoluteIndex,uint32_t & requiredInsertCount)275 void QPACKEncoder::trackReference(uint32_t absoluteIndex,
276 uint32_t& requiredInsertCount) {
277 CHECK_NE(absoluteIndex, 0);
278 if (absoluteIndex > requiredInsertCount) {
279 requiredInsertCount = absoluteIndex;
280 if (table_.isVulnerable(absoluteIndex)) {
281 curOutstanding_.vulnerable = true;
282 }
283 }
284 auto res = curOutstanding_.references.insert(absoluteIndex);
285 if (res.second) {
286 VLOG(5) << "Bumping refcount for absoluteIndex=" << absoluteIndex;
287 table_.addRef(absoluteIndex);
288 }
289 }
290
encodeDuplicate(uint32_t index)291 void QPACKEncoder::encodeDuplicate(uint32_t index) {
292 DCHECK_GT(index, 0);
293 maxEncoderStreamBytes_ -=
294 controlBuffer_.encodeInteger(index - 1, HPACK::Q_DUPLICATE);
295 }
296
encodeInsertQ(const HPACKHeaderName & name,folly::StringPiece value,bool isStaticName,uint32_t nameIndex)297 void QPACKEncoder::encodeInsertQ(const HPACKHeaderName& name,
298 folly::StringPiece value,
299 bool isStaticName,
300 uint32_t nameIndex) {
301 auto encoded = encodeLiteralQHelper(controlBuffer_,
302 name,
303 value,
304 isStaticName,
305 nameIndex,
306 HPACK::Q_INSERT_NAME_REF_STATIC,
307 HPACK::Q_INSERT_NAME_REF,
308 HPACK::Q_INSERT_NO_NAME_REF);
309 maxEncoderStreamBytes_ -= encoded;
310 }
311
encodeLiteralQ(const HPACKHeaderName & name,folly::StringPiece value,bool isStaticName,bool postBase,uint32_t nameIndex,const HPACK::Instruction & idxInstr)312 size_t QPACKEncoder::encodeLiteralQ(const HPACKHeaderName& name,
313 folly::StringPiece value,
314 bool isStaticName,
315 bool postBase,
316 uint32_t nameIndex,
317 const HPACK::Instruction& idxInstr) {
318 DCHECK(!isStaticName || !postBase);
319 return encodeLiteralQHelper(streamBuffer_,
320 name,
321 value,
322 isStaticName,
323 nameIndex,
324 HPACK::Q_LITERAL_STATIC,
325 idxInstr,
326 HPACK::Q_LITERAL);
327 }
328
encodeLiteralQHelper(HPACKEncodeBuffer & buffer,const HPACKHeaderName & name,folly::StringPiece value,bool isStaticName,uint32_t nameIndex,uint8_t staticFlag,const HPACK::Instruction & idxInstr,const HPACK::Instruction & litInstr)329 uint32_t QPACKEncoder::encodeLiteralQHelper(
330 HPACKEncodeBuffer& buffer,
331 const HPACKHeaderName& name,
332 folly::StringPiece value,
333 bool isStaticName,
334 uint32_t nameIndex,
335 uint8_t staticFlag,
336 const HPACK::Instruction& idxInstr,
337 const HPACK::Instruction& litInstr) {
338 uint32_t encoded = 0;
339 // name
340 if (nameIndex) {
341 VLOG(10) << "encoding name index=" << nameIndex;
342 DCHECK_NE(nameIndex, QPACKHeaderTable::UNACKED);
343 nameIndex -= 1; // we already know it's not 0
344 uint8_t byte = idxInstr.code;
345 if (isStaticName) {
346 // This counts static refs on the encoder stream
347 staticRefs_++;
348 byte |= staticFlag;
349 }
350 encoded += buffer.encodeInteger(nameIndex, byte, idxInstr.prefixLength);
351 } else {
352 encoded +=
353 buffer.encodeLiteral(litInstr.code, litInstr.prefixLength, name.get());
354 }
355 // value
356 encoded += buffer.encodeLiteral(value);
357 return encoded;
358 }
359
decodeDecoderStream(std::unique_ptr<folly::IOBuf> buf)360 HPACK::DecodeError QPACKEncoder::decodeDecoderStream(
361 std::unique_ptr<folly::IOBuf> buf) {
362 decoderIngress_.append(std::move(buf));
363 folly::io::Cursor cursor(decoderIngress_.front());
364 HPACKDecodeBuffer dbuf(cursor,
365 decoderIngress_.chainLength(),
366 0,
367 /* endOfBufferIsError= */ false);
368 HPACK::DecodeError err = HPACK::DecodeError::NONE;
369 uint32_t consumed = 0;
370 while (err == HPACK::DecodeError::NONE && !dbuf.empty()) {
371 consumed = dbuf.consumedBytes();
372 auto byte = dbuf.peek();
373 if (byte & HPACK::Q_HEADER_ACK.code) {
374 err = decodeHeaderAck(dbuf, HPACK::Q_HEADER_ACK.prefixLength, false);
375 } else if (byte & HPACK::Q_CANCEL_STREAM.code) {
376 err = decodeHeaderAck(dbuf, HPACK::Q_CANCEL_STREAM.prefixLength, true);
377 } else { // INSERT_COUNT_INC
378 uint64_t numInserts = 0;
379 err = dbuf.decodeInteger(HPACK::Q_INSERT_COUNT_INC.prefixLength,
380 numInserts);
381 if (err == HPACK::DecodeError::NONE) {
382 err = onInsertCountIncrement(numInserts);
383 } else if (err != HPACK::DecodeError::BUFFER_UNDERFLOW) {
384 LOG(ERROR) << "Failed to decode numInserts, err=" << err;
385 }
386 }
387 } // while
388 if (err == HPACK::DecodeError::BUFFER_UNDERFLOW) {
389 err = HPACK::DecodeError::NONE;
390 decoderIngress_.trimStart(consumed);
391 } else {
392 decoderIngress_.trimStart(dbuf.consumedBytes());
393 }
394 return err;
395 }
396
decoderStreamEnd()397 HPACK::DecodeError QPACKEncoder::decoderStreamEnd() {
398 if (!decoderIngress_.empty()) {
399 return HPACK::DecodeError::BUFFER_UNDERFLOW;
400 }
401 return HPACK::DecodeError::NONE;
402 }
403
decodeHeaderAck(HPACKDecodeBuffer & dbuf,uint8_t prefixLength,bool all)404 HPACK::DecodeError QPACKEncoder::decodeHeaderAck(HPACKDecodeBuffer& dbuf,
405 uint8_t prefixLength,
406 bool all) {
407 uint64_t streamId = 0;
408 auto err = dbuf.decodeInteger(prefixLength, streamId);
409 if (err == HPACK::DecodeError::NONE) {
410 err = onHeaderAck(streamId, all);
411 } else if (err != HPACK::DecodeError::BUFFER_UNDERFLOW) {
412 LOG(ERROR) << "Failed to decode streamId, err=" << err;
413 }
414 return err;
415 }
416
onInsertCountIncrement(uint32_t inserts)417 HPACK::DecodeError QPACKEncoder::onInsertCountIncrement(uint32_t inserts) {
418 if (inserts == 0 || !table_.onInsertCountIncrement(inserts)) {
419 return HPACK::DecodeError::INVALID_ACK;
420 }
421 return HPACK::DecodeError::NONE;
422 }
423
onHeaderAck(uint64_t streamId,bool all)424 HPACK::DecodeError QPACKEncoder::onHeaderAck(uint64_t streamId, bool all) {
425 auto it = outstanding_.find(streamId);
426 if (it == outstanding_.end()) {
427 if (!all) {
428 LOG(ERROR) << "Received an ack with no outstanding header blocks stream="
429 << streamId;
430 return HPACK::DecodeError::INVALID_ACK;
431 } else {
432 // all implies a reset, meaning it's not an error if there are no
433 // outstanding blocks
434 return HPACK::DecodeError::NONE;
435 }
436 }
437 DCHECK(!it->second.empty()) << "Invariant violation: no blocks in stream "
438 "record";
439 VLOG(5) << ((all) ? "onCancelStream" : "onHeaderAck")
440 << " streamId=" << streamId;
441 if (all) {
442 // Happens when a stream is reset
443 for (auto& block : it->second) {
444 for (auto i : block.references) {
445 VLOG(5) << "Decrementing refcount for absoluteIndex=" << i;
446 table_.subRef(i);
447 }
448 if (block.vulnerable) {
449 numVulnerable_--;
450 }
451 }
452 numOutstandingBlocks_ -= it->second.size();
453 it->second.clear();
454 } else {
455 auto block = std::move(it->second.front());
456 numOutstandingBlocks_--;
457 it->second.pop_front();
458 // a different stream, sub all the references
459 for (auto i : block.references) {
460 VLOG(5) << "Decrementing refcount for absoluteIndex=" << i;
461 table_.subRef(i);
462 }
463 if (block.vulnerable) {
464 numVulnerable_--;
465 }
466 // requiredInsertCount is implicitly acknowledged
467 if (!block.references.empty()) {
468 auto requiredInsertCount = *block.references.rbegin();
469 VLOG(5) << "Implicitly acknowledging requiredInsertCount="
470 << requiredInsertCount;
471 table_.setAcknowledgedInsertCount(requiredInsertCount);
472 }
473 }
474 if (it->second.empty()) {
475 outstanding_.erase(it);
476 }
477 return HPACK::DecodeError::NONE;
478 }
479
setMaxNumOutstandingBlocks(uint32_t value)480 void QPACKEncoder::setMaxNumOutstandingBlocks(uint32_t value) {
481 maxNumOutstandingBlocks_ = value;
482 }
483
484 } // namespace proxygen
485