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