proxygen
GzipHeaderCodec.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 <folly/Memory.h>
14 #include <folly/String.h>
15 #include <folly/ThreadLocal.h>
16 #include <folly/io/IOBuf.h>
20 #include <string>
21 
22 using folly::IOBuf;
24 using folly::io::Cursor;
25 using namespace proxygen;
30 using std::string;
31 using std::unique_ptr;
32 using std::vector;
33 
34 namespace {
35 
36 // Maximum size of header names+values after expanding multi-value headers
37 const size_t kMaxExpandedHeaderLineBytes = 80 * 1024;
38 
39 // Pre-initialized compression contexts seeded with the
40 // starting dictionary for different SPDY versions - cloning
41 // one of these is faster than initializing and seeding a
42 // brand new deflate context.
43 struct ZlibConfig {
44 
45  ZlibConfig(SPDYVersion inVersion, int inCompressionLevel)
46  : version(inVersion), compressionLevel(inCompressionLevel) {}
47 
48  bool operator==(const ZlibConfig& lhs) const {
49  return (version == lhs.version) &&
50  (compressionLevel == lhs.compressionLevel);
51  }
52 
53  bool operator<(const ZlibConfig& lhs) const {
54  return (version < lhs.version) ||
55  ((version == lhs.version) &&
56  (compressionLevel < lhs.compressionLevel));
57  }
59  int compressionLevel;
60 };
61 
62 struct ZlibContext {
63  ~ZlibContext() {
64  deflateEnd(&deflater);
65  inflateEnd(&inflater);
66  }
67 
68  z_stream deflater;
69  z_stream inflater;
70 };
71 
72 folly::IOBuf& getStaticHeaderBufSpace(size_t size) {
73  struct BufferTag {};
75  if (!buf) {
76  buf = std::make_unique<IOBuf>(IOBuf::CREATE, size);
77  } else {
78  if (size > buf->capacity()) {
79  buf = std::make_unique<IOBuf>(IOBuf::CREATE, size);
80  } else {
81  buf->clear();
82  }
83  }
84  DCHECK(!buf->isShared());
85  return *buf;
86 }
87 
88 void appendString(uint8_t*& dst, const string& str) {
89  size_t len = str.length();
90  memcpy(dst, str.data(), len);
91  dst += len;
92 }
93 
97 static const ZlibContext* getZlibContext(SPDYVersionSettings versionSettings,
98  int compressionLevel) {
99  struct ContextTag {};
100  using ZlibContextMap = std::map<ZlibConfig, std::unique_ptr<ZlibContext>>;
102  ZlibConfig zlibConfig(versionSettings.version, compressionLevel);
103  auto match = ctxmap.find(zlibConfig);
104  if (match != ctxmap.end()) {
105  return match->second.get();
106  } else {
107  // This is the first request for the specified SPDY version and compression
108  // level in this thread, so we need to construct the initial compressor and
109  // decompressor contexts.
110  auto newContext = std::make_unique<ZlibContext>();
111  newContext->deflater.zalloc = Z_NULL;
112  newContext->deflater.zfree = Z_NULL;
113  newContext->deflater.opaque = Z_NULL;
114  newContext->deflater.avail_in = 0;
115  newContext->deflater.next_in = Z_NULL;
116  int windowBits = (compressionLevel == Z_NO_COMPRESSION) ? 8 : 11;
117  int r = deflateInit2(
118  &(newContext->deflater),
119  compressionLevel,
120  Z_DEFLATED, // compression method
121  windowBits, // log2 of the compression window size, negative value
122  // means raw deflate output format w/o libz header
123  1, // memory size for internal compression state, 1-9
124  Z_DEFAULT_STRATEGY);
125  CHECK_EQ(r, Z_OK);
126  if (compressionLevel != Z_NO_COMPRESSION) {
127  r = deflateSetDictionary(&(newContext->deflater), versionSettings.dict,
128  versionSettings.dictSize);
129  CHECK_EQ(r, Z_OK);
130  }
131 
132  newContext->inflater.zalloc = Z_NULL;
133  newContext->inflater.zfree = Z_NULL;
134  newContext->inflater.opaque = Z_NULL;
135  newContext->inflater.avail_in = 0;
136  newContext->inflater.next_in = Z_NULL;
137 // TODO (t6405700): Port the zlib optimization forward to 1.2.8 for gcc 4.9
138 #if ZLIB_VERNUM == 0x1250
139  // set zlib's reserved flag to allocate smaller initial sliding window, then
140  // double it if necessary
141  newContext->inflater.reserved = 0x01;
142 #endif
143  r = inflateInit2(&(newContext->inflater), MAX_WBITS);
144  CHECK_EQ(r, Z_OK);
145 
146  auto result = newContext.get();
147  ctxmap.emplace(zlibConfig, std::move(newContext));
148  return result;
149  }
150 }
151 
152 } // anonymous namespace
153 
154 namespace proxygen {
155 
156 GzipHeaderCodec::GzipHeaderCodec(int compressionLevel,
157  const SPDYVersionSettings& versionSettings)
158  : versionSettings_(versionSettings) {
159  // Create compression and decompression contexts by cloning thread-local
160  // copies of the initial SPDY compression state
161  auto context = getZlibContext(versionSettings, compressionLevel);
162  deflateCopy(&deflater_, const_cast<z_stream*>(&(context->deflater)));
163  inflateCopy(&inflater_, const_cast<z_stream*>(&(context->inflater)));
164 }
165 
166 GzipHeaderCodec::GzipHeaderCodec(int compressionLevel,
168  : GzipHeaderCodec(
169  compressionLevel,
170  SPDYCodec::getVersionSettings(version)) {}
171 
173  deflateEnd(&deflater_);
174  inflateEnd(&inflater_);
175 }
176 
178  return getStaticHeaderBufSpace(maxUncompressed_);
179 }
180 
181 unique_ptr<IOBuf> GzipHeaderCodec::encode(vector<Header>& headers) noexcept {
182  // Build a sequence of the header names and values, sorted by name.
183  // The purpose of the sort is to make it easier to combine the
184  // values of multiple headers with the same name. The SPDY spec
185  // prohibits any header name from appearing more than once in the
186  // Name/Value list, so we must combine values when serializing.
187  std::sort(headers.begin(), headers.end());
188 
189  auto& uncompressed = getHeaderBuf();
190  // Compute the amount of space needed to hold the uncompressed
191  // representation of the headers. This is an upper bound on the
192  // amount of space we'll actually need, because if we end up
193  // combining any headers with the same name, the combined
194  // representation will be smaller than the original.
195  size_t maxUncompressedSize = versionSettings_.nameValueSize;
196  for (const Header& header : headers) {
197  maxUncompressedSize += versionSettings_.nameValueSize;
198  maxUncompressedSize += header.name->length();
199  maxUncompressedSize += versionSettings_.nameValueSize;
200  maxUncompressedSize += header.value->length();
201  }
202 
203  // TODO: give on 'onError()' callback if the space in uncompressed buf
204  // cannot fit the headers and then skip the "reserve" code below. We
205  // have already reserved the maximum legal amount of space for
206  // uncompressed headers.
207 
208  VLOG(5) << "reserving " << maxUncompressedSize
209  << " bytes for uncompressed headers";
210  uncompressed.reserve(0, maxUncompressedSize);
211 
212  // Serialize the uncompressed representation of the headers.
213  uint8_t* dst = uncompressed.writableData();
214  dst += versionSettings_.nameValueSize; // Leave space for count of headers.
216  const string* lastName = &empty_string;
217  uint8_t* lastValueLenPtr = nullptr;
218  size_t lastValueLen = 0;
219  unsigned numHeaders = 0;
220  for (const Header& header : headers) {
221  if ((header.code != lastCode) || (*header.name != *lastName)) {
222  // Simple case: this header name is different from the previous
223  // one, so we don't need to combine values.
224  numHeaders++;
225  versionSettings_.appendSizeFun(dst, header.name->length());
226 
227  // lowercasing the header name inline
228  char* nameBegin = (char *)dst;
229  appendString(dst, *header.name);
230  folly::toLowerAscii((char *)nameBegin, header.name->size());
231 
232  lastValueLenPtr = dst;
233  lastValueLen = header.value->length();
234  versionSettings_.appendSizeFun(dst, header.value->length());
235  appendString(dst, *header.value);
236  lastCode = header.code;
237  lastName = header.name;
238  } else if (header.value->length() > 0) {
239  // More complicated case: we do need to combine values.
240  if (lastValueLen > 0) {
241  // Only nul terminate if previous value was non-empty
242  *dst++ = 0; // SPDY uses a null byte as a separator
243  lastValueLen++;
244  }
245  appendString(dst, *header.value);
246  // Go back and rewrite the length field in front of the value
247  lastValueLen += header.value->length();
248  uint8_t* tmp = lastValueLenPtr;
249  versionSettings_.appendSizeFun(tmp, lastValueLen);
250  }
251  }
252 
253  // Compute the uncompressed length; if we combined any header values,
254  // we will have used less space than originally estimated.
255  size_t uncompressedLen = dst - uncompressed.writableData();
256 
257  // Go back and write the count of unique header names at the start.
258  dst = uncompressed.writableData();
259  versionSettings_.appendSizeFun(dst, numHeaders);
260 
261  // Allocate a contiguous space big enough to hold the compressed headers,
262  // plus any headroom requested by the caller.
263  size_t maxDeflatedSize = deflateBound(&deflater_, uncompressedLen);
264  unique_ptr<IOBuf> out(IOBuf::create(maxDeflatedSize + encodeHeadroom_));
265  out->advance(encodeHeadroom_);
266 
267  // Compress
268  deflater_.next_in = uncompressed.writableData();
269  deflater_.avail_in = uncompressedLen;
270  deflater_.next_out = out->writableData();
271  deflater_.avail_out = maxDeflatedSize;
272  int r = deflate(&deflater_, Z_SYNC_FLUSH);
273  CHECK_EQ(r, Z_OK);
274  CHECK_EQ(deflater_.avail_in, 0);
275  out->append(maxDeflatedSize - deflater_.avail_out);
276 
277  VLOG(4) << "header size orig=" << uncompressedLen
278  << ", max deflated=" << maxDeflatedSize
279  << ", actual deflated=" << out->length();
280 
281  encodedSize_.compressed = out->length();
282  encodedSize_.uncompressed = uncompressedLen;
283  if (stats_) {
285  }
286 
287  return out;
288 }
289 
292  outHeaders_.clear();
293 
294  // empty header block
295  if (length == 0) {
296  return HeaderDecodeResult{outHeaders_, 0};
297  }
298 
299  // Get the thread local buffer space to use
300  auto& uncompressed = getHeaderBuf();
301  uint32_t consumed = 0;
302  // Decompress the headers
303  while (length > 0) {
304  auto next = cursor.peek();
305  uint32_t chunkLen = std::min((uint32_t)next.second, length);
306  inflater_.avail_in = chunkLen;
307  inflater_.next_in = (uint8_t *)next.first;
308  do {
309  if (uncompressed.tailroom() == 0) {
310  // This code should not execute, since we throw an error if the
311  // decompressed size of the headers is too large and we initialize
312  // the buffer to that size.
313  LOG(ERROR) << "Doubling capacity of SPDY headers buffer";
314  uncompressed.reserve(0, uncompressed.capacity());
315  }
316 
317  inflater_.next_out = uncompressed.writableTail();
318  inflater_.avail_out = uncompressed.tailroom();
319  int r = inflate(&inflater_, Z_NO_FLUSH);
320  if (r == Z_NEED_DICT) {
321  // we cannot initialize the inflater dictionary before calling inflate()
322  // as it checks the adler-32 checksum of the supplied dictionary
323  r = inflateSetDictionary(&inflater_, versionSettings_.dict,
325  if (r != Z_OK) {
326  LOG(ERROR) << "inflate set dictionary failed with error=" << r;
328  }
329  inflater_.avail_out = 0;
330  continue;
331  }
332  if (r != 0) {
333  // probably bad encoding
334  LOG(ERROR) << "inflate failed with error=" << r;
336  }
337  uncompressed.append(uncompressed.tailroom() - inflater_.avail_out);
338  if (uncompressed.length() > maxUncompressed_) {
339  LOG(ERROR) << "Decompressed headers too large";
341  }
342  } while (inflater_.avail_in > 0 && inflater_.avail_out == 0);
343  length -= chunkLen;
344  consumed += chunkLen;
345  cursor.skip(chunkLen);
346  }
347 
348  decodedSize_.compressed = consumed;
349  decodedSize_.uncompressed = uncompressed.computeChainDataLength();
350  if (stats_) {
352  }
353 
354  size_t expandedHeaderLineBytes = 0;
355  auto result = parseNameValues(uncompressed, decodedSize_.uncompressed);
356  if (result.hasError()) {
357  return folly::makeUnexpected(result.error());
358  }
359  expandedHeaderLineBytes = *result;
360 
361  if (UNLIKELY(expandedHeaderLineBytes > kMaxExpandedHeaderLineBytes)) {
362  LOG(ERROR) << "expanded headers too large";
364  }
365 
366  return HeaderDecodeResult{outHeaders_, consumed};
367 }
368 
371  uint32_t uncompressedLength) noexcept {
372 
373  size_t expandedHeaderLineBytes = 0;
374  Cursor headerCursor(&uncompressed);
375  uint32_t numNV = 0;
376  const HeaderPiece* headerName = nullptr;
377 
378  try {
379  numNV = versionSettings_.parseSizeFun(&headerCursor);
380  } catch (const std::out_of_range& ex) {
382  }
383 
384  for (uint32_t i = 0; i < numNV * 2; i++) {
385  uint32_t len = 0;
386  try {
387  len = versionSettings_.parseSizeFun(&headerCursor);
388  uncompressedLength -= versionSettings_.nameValueSize;
389  } catch (const std::out_of_range& ex) {
391  }
392 
393  if (len == 0 && !headerName) {
394  LOG(ERROR) << "empty header name";
396  }
397  auto next = headerCursor.peek();
398  try {
399  if (len > uncompressedLength) {
400  throw std::out_of_range(
401  folly::to<string>("bad length=", len, " uncompressedLength=",
402  uncompressedLength));
403  } else if (next.second >= len) {
404  // string is contiguous, just put a pointer into the headers structure
405  outHeaders_.emplace_back((char *)next.first, len, false, false);
406  headerCursor.skip(len);
407  } else {
408  // string is not contiguous, allocate a buffer and pull into it
409  unique_ptr<char[]> data (new char[len]);
410  headerCursor.pull(data.get(), len);
411  outHeaders_.emplace_back(data.release(), len, true, false);
412  }
413  uncompressedLength -= len;
414  } catch (const std::out_of_range& ex) {
415  LOG(ERROR) << "bad encoding for nv=" << i << ": "
416  << folly::exceptionStr(ex);
417  VLOG(3) << IOBufPrinter::printHexFolly(&uncompressed, true);
419  }
420  if (i % 2 == 0) {
421  headerName = &outHeaders_.back();
422  for (const char c: headerName->str) {
423  if (c < 0x20 || c > 0x7e || ('A' <= c && c <= 'Z')) {
424  LOG(ERROR) << "invalid header value";
426  }
427  }
428  } else {
429  HeaderPiece& headerValue = outHeaders_.back();
430  bool first = true;
431  const char* valueStart = headerValue.str.data();
432  const char* pos = valueStart;
433  const char* stop = valueStart + headerValue.str.size();
434  while(pos < stop) {
435  if (*pos == '\0') {
436  if (pos - valueStart == 0) {
437  LOG(ERROR) << "empty header value for header=" << headerName;
439  }
440  if (first) {
441  headerValue.str.reset(valueStart, pos - valueStart);
442  first = false;
443  } else {
444  outHeaders_.emplace_back(headerName->str.data(),
445  headerName->str.size(),
446  false, true);
447  outHeaders_.emplace_back(valueStart, pos - valueStart, false, true);
448  expandedHeaderLineBytes += ((pos - valueStart) +
449  headerName->str.size());
450  }
451  valueStart = pos + 1;
452  }
453  pos++;
454  }
455  if (!first) {
456  // value contained at least one \0, add the last value
457  if (pos - valueStart == 0) {
458  LOG(ERROR) << "empty header value for header=" << headerName;
460  }
461  outHeaders_.emplace_back(headerName->str.data(),
462  headerName->str.size(),
463  false, true);
464  outHeaders_.emplace_back(valueStart, pos - valueStart, false, true);
465  expandedHeaderLineBytes += (pos - valueStart) + headerName->str.size();
466  }
467  headerName = nullptr;
468  }
469  }
470  return expandedHeaderLineBytes;
471 }
472 
473 }
const uint32_t kMaxFrameLength
static std::string printHexFolly(const folly::IOBuf *buf, bool coalesce=false)
Definition: Logging.h:127
context
Definition: CMakeCache.txt:563
fbstring exceptionStr(const std::exception &e)
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
const SPDYVersionSettings & versionSettings_
uint64_t maxUncompressed_
Definition: HeaderCodec.h:92
constexpr size_type size() const
Definition: Range.h:431
bool operator==(const HTTPMessage &msg1, const HTTPMessage &msg2)
void advance(std::size_t amount)
Definition: IOBuf.h:632
requires E e noexcept(noexcept(s.error(std::move(e))))
void pull(void *buf, size_t len)
Definition: Cursor.h:418
bool operator<(const AcceptorAddress &lv, const AcceptorAddress &rv)
std::pair< const uint8_t *, size_t > peek()
Definition: Cursor.h:451
HTTPHeaderSize encodedSize_
Definition: HeaderCodec.h:90
ProtocolVersion version
constexpr auto size(C const &c) -> decltype(c.size())
Definition: Access.h:45
static void stop()
LogLevel min
Definition: LogLevel.cpp:30
uint32_t encodeHeadroom_
Definition: HeaderCodec.h:91
std::deque< HeaderPiece > HeaderPieceList
Definition: HeaderPiece.h:59
constexpr Unexpected< typename std::decay< Error >::type > makeUnexpected(Error &&)
Definition: Expected.h:785
uint8_t * writableData()
Definition: IOBuf.h:509
constexpr Iter data() const
Definition: Range.h:446
std::size_t length() const
Definition: IOBuf.h:533
void(* appendSizeFun)(uint8_t *&, size_t)
if(FOLLY_USE_SYMBOLIZER) add_library(folly_exception_tracer_base ExceptionTracer.cpp StackTrace.cpp) apply_folly_compile_options_to_target(folly_exception_tracer_base) target_link_libraries(folly_exception_tracer_base PUBLIC folly) add_library(folly_exception_tracer ExceptionStackTraceLib.cpp ExceptionTracerLib.cpp) apply_folly_compile_options_to_target(folly_exception_tracer) target_link_libraries(folly_exception_tracer PUBLIC folly_exception_tracer_base) add_library(folly_exception_counter ExceptionCounterLib.cpp) apply_folly_compile_options_to_target(folly_exception_counter) target_link_libraries(folly_exception_counter PUBLIC folly_exception_tracer) install(FILES ExceptionAbi.h ExceptionCounterLib.h ExceptionTracer.h ExceptionTracerLib.h StackTrace.h DESTINATION $
Definition: CMakeLists.txt:1
virtual void recordDecode(Type type, HTTPHeaderSize &size)=0
const std::string empty_string
Definition: HTTPHeaders.cpp:23
void toLowerAscii(char *str, size_t length)
Definition: String.cpp:601
FOLLY_EXPORT static FOLLY_ALWAYS_INLINE T & get()
void skip(size_t len)
Definition: Cursor.h:371
const char * string
Definition: Conv.cpp:212
compress::HeaderPieceList outHeaders_
std::unique_ptr< folly::IOBuf > encode(std::vector< compress::Header > &headers) noexcept
virtual void recordEncode(Type type, HTTPHeaderSize &size)=0
folly::Expected< size_t, GzipDecodeError > parseNameValues(const folly::IOBuf &uncompressed, uint32_t uncompressedLength) noexcept
#define UNLIKELY(x)
Definition: Likely.h:48
folly::Expected< HeaderDecodeResult, GzipDecodeError > decode(folly::io::Cursor &cursor, uint32_t length) noexcept
PUSHMI_INLINE_VAR constexpr detail::get_fn< T > get
Definition: submit.h:391
char c
static constexpr uint64_t data[1]
Definition: Fingerprint.cpp:43
uint32_t(* parseSizeFun)(folly::io::Cursor *)
GzipHeaderCodec(int compressionLevel, const SPDYVersionSettings &versionSettings)
void append(std::size_t amount)
Definition: IOBuf.h:689
void reset(Iter start, size_type size)
Definition: Range.h:421
constexpr detail::First first
Definition: Base-inl.h:2553
def next(obj)
Definition: ast.py:58