proxygen
HPACKCodecTests.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  */
10 #include <folly/Range.h>
11 #include <folly/io/Cursor.h>
12 #include <folly/io/IOBuf.h>
13 #include <glog/logging.h>
21 #include <vector>
22 
23 using namespace folly::io;
24 using namespace folly;
25 using namespace proxygen::compress;
26 using namespace proxygen::hpack;
27 using namespace proxygen;
28 using namespace std;
29 using namespace testing;
30 
32  for (auto ch : str) {
33  if (isalpha(ch) && !islower(ch)) {
34  return false;
35  }
36  }
37  return true;
38 }
39 
40 namespace {
41 
42 struct DecodeResult {
44  uint32_t bytesConsumed;
45 };
46 
48 decode(HPACKCodec& codec, Cursor& cursor, uint32_t length) noexcept {
50  codec.decodeStreaming(cursor, length, &cb);
51  if (cb.hasError()) {
52  LOG(ERROR) << "decoder state: " << codec;
53  return folly::makeUnexpected(cb.error);
54  }
55  return DecodeResult{std::move(cb.getResult()->headers),
56  cb.getResult()->bytesConsumed};
57 }
58 
60 encodeDecode(HPACKCodec& encoder, HPACKCodec& decoder,
61  vector<Header>&& headers) {
62  unique_ptr<IOBuf> encoded = encoder.encode(headers);
63  Cursor c(encoded.get());
64  return decode(decoder, c, c.totalLength());
65 }
66 
67 uint64_t bufLen(const std::unique_ptr<IOBuf>& buf) {
68  if (buf) {
69  return buf->computeChainDataLength();
70  }
71  return 0;
72 }
73 
74 }
75 
77  protected:
78 
79  HPACKCodec client{TransportDirection::UPSTREAM};
80  HPACKCodec server{TransportDirection::DOWNSTREAM};
81 };
82 
84  for (int i = 0; i < 3; i++) {
85  auto result = encodeDecode(client, server, basicHeaders());
86  EXPECT_TRUE(!result.hasError());
87  EXPECT_EQ(result->headers.size(), 12);
88  }
89 }
90 
91 TEST_F(HPACKCodecTests, Response) {
92  vector<vector<string>> headers = {
93  {"content-length", "80"},
94  {"content-encoding", "gzip"},
95  {"x-fb-debug", "sdfgrwer"}
96  };
97  vector<Header> req = headersFromArray(headers);
98 
99  for (int i = 0; i < 3; i++) {
100  auto result = encodeDecode(server, client, basicHeaders());
101  EXPECT_TRUE(!result.hasError());
102  EXPECT_EQ(result->headers.size(), 12);
103  }
104 }
105 
107  vector<Header> req = basicHeaders();
108 
109  uint32_t headroom = 20;
110  client.setEncodeHeadroom(headroom);
111  unique_ptr<IOBuf> encodedReq = client.encode(req);
112  EXPECT_EQ(encodedReq->headroom(), headroom);
113  Cursor cursor(encodedReq.get());
114  auto result = decode(server, cursor, cursor.totalLength());
115  EXPECT_TRUE(!result.hasError());
116  EXPECT_EQ(result->headers.size(), 12);
117 }
118 
122 TEST_F(HPACKCodecTests, LowercasingHeaderNames) {
123  vector<vector<string>> headers = {
124  {"Content-Length", "80"},
125  {"Content-Encoding", "gzip"},
126  {"X-FB-Debug", "bleah"}
127  };
128  auto result = encodeDecode(server, client, headersFromArray(headers));
129  EXPECT_TRUE(!result.hasError());
130  auto& decoded = result->headers;
131  CHECK_EQ(decoded.size(), 6);
132  for (int i = 0; i < 6; i += 2) {
133  EXPECT_TRUE(isLowercase(decoded[i].str));
134  }
135 }
136 
141 TEST_F(HPACKCodecTests, MultivalueHeaders) {
142  vector<vector<string>> headers = {
143  {"Content-Length", "80"},
144  {"Content-Encoding", "gzip"},
145  {"X-FB-Dup", "bleah"},
146  {"X-FB-Dup", "hahaha"}
147  };
148  auto result = encodeDecode(server, client, headersFromArray(headers));
149  EXPECT_TRUE(!result.hasError());
150  auto& decoded = result->headers;
151  CHECK_EQ(decoded.size(), 8);
152  uint32_t count = 0;
153  for (int i = 0; i < 8; i += 2) {
154  if (decoded[i].str == "x-fb-dup") {
155  count++;
156  }
157  }
158  EXPECT_EQ(count, 2);
159 }
160 
165  vector<vector<string>> headers = {
166  {"Content-Length", "80"}
167  };
168  vector<Header> req = headersFromArray(headers);
169 
170  unique_ptr<IOBuf> encodedReq = server.encode(req);
171  encodedReq->writableData()[0] = 0xFF;
172  Cursor cursor(encodedReq.get());
173 
174  TestHeaderCodecStats stats(HeaderCodec::Type::HPACK);
175  client.setStats(&stats);
176  auto result = decode(client, cursor, cursor.totalLength());
177  // this means there was an error
178  EXPECT_TRUE(result.hasError());
179  EXPECT_EQ(result.error(), HPACK::DecodeError::INVALID_INDEX);
180  EXPECT_EQ(stats.errors, 1);
181  client.setStats(nullptr);
182 }
183 
187 TEST_F(HPACKCodecTests, HeaderCodecStats) {
188  vector<vector<string>> headers = {
189  {"Content-Length", "80"},
190  {"Content-Encoding", "gzip"},
191  {"X-FB-Debug", "eirtijvdgtccffkutnbttcgbfieghgev"}
192  };
193  vector<Header> resp = headersFromArray(headers);
194 
195  TestHeaderCodecStats stats(HeaderCodec::Type::HPACK);
196  // encode
197  server.setStats(&stats);
198  unique_ptr<IOBuf> encodedResp = server.encode(resp);
199  EXPECT_EQ(stats.encodes, 1);
200  EXPECT_EQ(stats.decodes, 0);
201  EXPECT_EQ(stats.errors, 0);
202  EXPECT_TRUE(stats.encodedBytesCompr > 0);
203  EXPECT_TRUE(stats.encodedBytesUncompr > 0);
204  EXPECT_EQ(stats.decodedBytesCompr, 0);
205  EXPECT_EQ(stats.decodedBytesUncompr, 0);
206  server.setStats(nullptr);
207 
208  // decode
209  Cursor cursor(encodedResp.get());
210  stats.reset();
211  client.setStats(&stats);
212  auto result = decode(client, cursor, cursor.totalLength());
213  EXPECT_TRUE(!result.hasError());
214  auto& decoded = result->headers;
215  CHECK_EQ(decoded.size(), 3 * 2);
216  EXPECT_EQ(stats.decodes, 1);
217  EXPECT_EQ(stats.encodes, 0);
218  EXPECT_GT(stats.decodedBytesCompr, 0);
219  EXPECT_GT(stats.decodedBytesUncompr, 0);
220  EXPECT_EQ(stats.encodedBytesCompr, 0);
221  EXPECT_EQ(stats.encodedBytesUncompr, 0);
222  client.setStats(nullptr);
223 }
224 
228 TEST_F(HPACKCodecTests, UncompressedSizeLimit) {
229  vector<vector<string>> headers;
230  // generate lots of small headers
231  string contentLength = "Content-Length";
232  for (int i = 0; i < 10000; i++) {
233  string value = folly::to<string>(i);
234  vector<string> header = {contentLength, value};
235  headers.push_back(header);
236  }
237  auto result = encodeDecode(server, client, headersFromArray(headers));
238  EXPECT_TRUE(result.hasError());
239  EXPECT_EQ(result.error(), HPACK::DecodeError::HEADERS_TOO_LARGE);
240 }
241 
242 
246 TEST_F(HPACKCodecTests, SizeLimitStats) {
247  vector<vector<string>> headers;
248  // generate lots of small headers
249  string contentLength = "Content-Length";
250  for (int i = 0; i < 10000; i++) {
251  string value = folly::to<string>(i);
252  vector<string> header = {contentLength, value};
253  headers.push_back(header);
254  }
255  auto encHeaders = headersFromArray(headers);
256  unique_ptr<IOBuf> encoded = client.encode(encHeaders);
257  Cursor cursor(encoded.get());
259  TestHeaderCodecStats stats(HeaderCodec::Type::HPACK);
260  server.setStats(&stats);
261  server.decodeStreaming(cursor, cursor.totalLength(), &cb);
262  auto result = cb.getResult();
263  EXPECT_TRUE(result.hasError());
264  EXPECT_EQ(result.error(), HPACK::DecodeError::HEADERS_TOO_LARGE);
265  EXPECT_EQ(stats.tooLarge, 1);
266 }
267 
268 TEST_F(HPACKCodecTests, DefaultHeaderIndexingStrategy) {
269  vector<Header> headers = basicHeaders();
270  size_t headersIndexableSize = 4;
271 
272  // Control equality check; all basic headers were indexed
273  client.encode(headers);
274  EXPECT_EQ(client.getHPACKTableInfo().egressHeadersStored_,
275  headersIndexableSize);
276 
277  // Verify HPACKCodec by default utilizes the default header indexing strategy
278  // by ensuring that it does not index any of the added headers below
279  // The below is quite verbose but that is because Header constructors use
280  // references and so we need the actual strings to not go out of scope
281  vector<vector<string>> noIndexHeadersStrings = {
282  {"content-length", "80"},
283  {":path", "/some/random/file.jpg"},
284  {":path", "checks_for_="},
285  {"if-modified-since", "some_value"},
286  {"last-modified", "some_value"}
287  };
288  vector<Header> noIndexHeaders = headersFromArray(noIndexHeadersStrings);
289  headers.insert(headers.end(), noIndexHeaders.begin(), noIndexHeaders.end());
290  HPACKCodec testCodec{TransportDirection::UPSTREAM};
291  testCodec.encode(headers);
292  EXPECT_EQ(
293  testCodec.getHPACKTableInfo().egressHeadersStored_, headersIndexableSize);
294 }
295 
296 
297 class HPACKQueueTests : public testing::TestWithParam<int> {
298  public:
300  : queue(std::make_unique<HPACKQueue>(server)) {}
301 
302  protected:
303 
304  HPACKCodec client{TransportDirection::UPSTREAM};
305  HPACKCodec server{TransportDirection::DOWNSTREAM};
306  std::unique_ptr<HPACKQueue> queue;
307 };
308 
309 TEST_F(HPACKQueueTests, QueueInline) {
310  vector<Header> req = basicHeaders();
312 
313  for (int i = 0; i < 3; i++) {
314  unique_ptr<IOBuf> encodedReq = client.encode(req);
315  auto len = bufLen(encodedReq);
316  cb.reset();
317  queue->enqueueHeaderBlock(i, std::move(encodedReq), len, &cb, false);
318  auto result = cb.getResult();
319  EXPECT_TRUE(!result.hasError());
320  EXPECT_EQ(result->headers.size(), 12);
321  }
322 }
323 
324 TEST_F(HPACKQueueTests, QueueReorder) {
325  vector<Header> req = basicHeaders();
326  vector<std::pair<unique_ptr<IOBuf>, TestStreamingCallback>> data;
327 
328  for (int i = 0; i < 4; i++) {
329  data.emplace_back(client.encode(req), TestStreamingCallback());
330  }
331 
332  std::vector<int> insertOrder{1, 3, 2, 0};
333  for (auto i: insertOrder) {
334  auto& encodedReq = data[i].first;
335  auto len = bufLen(encodedReq);
336  queue->enqueueHeaderBlock(i, std::move(encodedReq), len, &data[i].second,
337  false);
338  }
339  for (auto& d: data) {
340  auto result = d.second.getResult();
341  EXPECT_TRUE(!result.hasError());
342  EXPECT_EQ(result->headers.size(), 12);
343  }
344  EXPECT_EQ(queue->getHolBlockCount(), 3);
345 }
346 
347 TEST_F(HPACKQueueTests, QueueReorderOoo) {
348  vector<Header> req = basicHeaders();
349  vector<std::pair<unique_ptr<IOBuf>, TestStreamingCallback>> data;
350 
351  for (int i = 0; i < 4; i++) {
352  data.emplace_back(client.encode(req), TestStreamingCallback());
353  }
354 
355  std::vector<int> insertOrder{0, 3, 2, 1};
356  for (auto i: insertOrder) {
357  auto& encodedReq = data[i].first;
358  auto len = bufLen(encodedReq);
359  // Allow idx 3 to be decoded out of order
360  queue->enqueueHeaderBlock(i, std::move(encodedReq), len, &data[i].second,
361  i == 3);
362  }
363  for (auto& d: data) {
364  auto result = d.second.getResult();
365  EXPECT_TRUE(!result.hasError());
366  EXPECT_EQ(result->headers.size(), 12);
367  }
368  EXPECT_EQ(queue->getHolBlockCount(), 1);
369 }
370 
371 TEST_F(HPACKQueueTests, QueueError) {
372  vector<Header> req = basicHeaders();
374 
375  bool expectOk = true;
376  // ok, dup, ok, lower
377  for (auto i: std::vector<int>({0, 0, 1, 0, 3, 3, 2})) {
378  unique_ptr<IOBuf> encodedReq = client.encode(req);
379  auto len = bufLen(encodedReq);
380  cb.reset();
381  queue->enqueueHeaderBlock(i, std::move(encodedReq), len, &cb, true);
382  auto result = cb.getResult();
383  if (expectOk) {
384  EXPECT_TRUE(!result.hasError());
385  EXPECT_EQ(result->headers.size(), 12);
386  } else {
387  EXPECT_TRUE(result.hasError());
388  EXPECT_EQ(result.error(), HPACK::DecodeError::BAD_SEQUENCE_NUMBER);
389  }
390  expectOk = !expectOk;
391  }
392 }
393 
394 TEST_P(HPACKQueueTests, QueueDeleted) {
395  vector<Header> req = basicHeaders();
396  vector<std::pair<unique_ptr<IOBuf>, TestStreamingCallback>> data;
397 
398  for (int i = 0; i < 4; i++) {
399  data.emplace_back(client.encode(req), TestStreamingCallback());
400  if (i == GetParam()) {
401  data.back().second.headersCompleteCb = [&] { queue.reset(); };
402  }
403  }
404 
405  std::vector<int> insertOrder{0, 3, 2, 1};
406  for (auto i: insertOrder) {
407  auto& encodedReq = data[i].first;
408  auto len = bufLen(encodedReq);
409 
410  // Allow idx 3 to be decoded out of order
411  queue->enqueueHeaderBlock(i, std::move(encodedReq), len, &data[i].second,
412  i == 3);
413  if (!queue) {
414  break;
415  }
416  }
417 }
418 
421  ::testing::Values(0, 1, 2, 3));
std::unique_ptr< HPACKQueue > queue
void decodeStreaming(folly::io::Cursor &cursor, uint32_t length, HPACK::StreamingCallback *streamingCb) noexcept
Definition: HPACKCodec.cpp:67
bool isLowercase(StringPiece str)
vector< compress::Header > basicHeaders()
Definition: TestUtil.cpp:116
INSTANTIATE_TEST_CASE_P(Queue, HPACKQueueTests,::testing::Values(0, 1, 2, 3))
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
TokenBindingMessage decode(folly::io::Cursor &cursor)
Definition: Types.cpp:132
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
vector< compress::Header > headersFromArray(vector< vector< string >> &a)
Definition: TestUtil.cpp:108
CodecFactory codec
STL namespace.
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
requires E e noexcept(noexcept(s.error(std::move(e))))
auto ch
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
std::size_t headroom() const
Definition: IOBuf.h:542
constexpr auto data(C &c) -> decltype(c.data())
Definition: Access.h:71
std::enable_if<!std::is_array< T >::value, std::unique_ptr< T > >::type make_unique(Args &&...args)
Definition: Memory.h:259
TEST_F(AsyncSSLSocketWriteTest, write_coalescing1)
int * count
std::size_t computeChainDataLength() const
Definition: IOBuf.cpp:501
#define EXPECT_TRUE(condition)
Definition: gtest.h:1859
uint64_t value(const typename LockFreeRingBuffer< T, Atom >::Cursor &rbcursor)
TEST_P(HPACKQueueTests, QueueDeleted)
std::unique_ptr< folly::IOBuf > encode(std::vector< compress::Header > &headers) noexcept
Definition: HPACKCodec.cpp:48
void encodeDecode(vector< HPACKHeader > &headers, HPACKEncoder &encoder, HPACKDecoder &decoder)
char c
folly::Expected< HeaderDecodeResult, HPACK::DecodeError > getResult()
#define EXPECT_GT(val1, val2)
Definition: gtest.h:1934