proxygen
HPACKContextTests.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/Conv.h>
11 #include <glog/logging.h>
13 #include <memory>
21 
22 using namespace folly;
23 using namespace proxygen;
24 using namespace std;
25 using namespace testing;
26 
27 class HPACKContextTests : public testing::TestWithParam<bool> {
28 };
29 
30 class TestContext : public HPACKContext {
31 
32  public:
33  explicit TestContext(uint32_t tableSize) : HPACKContext(tableSize) {}
34 
35  void add(const HPACKHeader& header) {
36  table_.add(header.copy());
37  }
38 
39 };
40 
43  HPACKHeader method(":method", "POST");
44 
45  // this will get it from the static table
46  CHECK_EQ(context.getIndex(method), 3);
47 }
48 
51  // add 10 headers to the table
52  for (int i = 1; i <= 10; i++) {
53  HPACKHeader header("name" + folly::to<string>(i),
54  "value" + folly::to<string>(i));
55  context.add(std::move(header));
56  }
57  EXPECT_EQ(context.getTable().size(), 10);
58 
59 
60  EXPECT_EQ(context.isStatic(1), true);
61  EXPECT_EQ(context.isStatic(10), true);
62  EXPECT_EQ(context.isStatic(40), true);
63  EXPECT_EQ(context.isStatic(60), true);
64  EXPECT_EQ(context.isStatic(69), false);
65 }
66 
67 TEST_F(HPACKContextTests, StaticTable) {
68  auto& table = StaticHeaderTable::get();
69  const HPACKHeader& first = table.getHeader(1);
70  const HPACKHeader& methodPost = table.getHeader(3);
71  const HPACKHeader& last = table.getHeader(table.size());
72 
73  // there are 61 entries in the spec
74  CHECK_EQ(table.size(), 61);
75  CHECK_EQ(methodPost, HPACKHeader(":method", "POST"));
76  CHECK_EQ(first.name.get(), ":authority");
77  CHECK_EQ(last.name.get(), "www-authenticate");
78 }
79 
80 TEST_F(HPACKContextTests, StaticTableHeaderNamesAreCommon) {
81  auto& table = StaticHeaderTable::get();
82  for (std::pair<HPACKHeaderName, std::list<uint32_t>> entry : table.names()) {
83  EXPECT_TRUE(entry.first.isCommonHeader());
84  }
85 }
86 
88  static_table_is_header_code_in_table_with_non_empty_value) {
89  auto& table = StaticHeaderTable::get();
90  for (uint32_t i = 1; i <= table.size(); ++i) {
91  const HPACKHeader& staticTableHeader = table.getHeader(i);
93  staticTableHeader.value.empty() !=
94  StaticHeaderTable::isHeaderCodeInTableWithNonEmptyValue(
95  staticTableHeader.name.getHeaderCode()));
96  }
97 }
98 
99 TEST_F(HPACKContextTests, StaticIndex) {
101  HPACKHeader authority(":authority", "");
102  EXPECT_EQ(context.getHeader(1), authority);
103 
104  HPACKHeader post(":method", "POST");
105  EXPECT_EQ(context.getHeader(3), post);
106 
107  HPACKHeader contentLength("content-length", "");
108  EXPECT_EQ(context.getHeader(28), contentLength);
109 }
110 
111 TEST_F(HPACKContextTests, EncoderMultipleValues) {
112  HPACKEncoder encoder(true);
113  vector<HPACKHeader> req;
114  req.push_back(HPACKHeader("accept-encoding", "gzip"));
115  req.push_back(HPACKHeader("accept-encoding", "sdch,gzip"));
116  unique_ptr<IOBuf> encoded = encoder.encode(req);
117  EXPECT_TRUE(encoded->length() > 0);
118  EXPECT_EQ(encoder.getTable().size(), 2);
119  // sending the same request again should lead to a smaller but non
120  // empty buffer
121  unique_ptr<IOBuf> encoded2 = encoder.encode(req);
122  EXPECT_LT(encoded2->computeChainDataLength(),
123  encoded->computeChainDataLength());
124  EXPECT_GT(encoded2->computeChainDataLength(), 0);
125 }
126 
127 TEST_F(HPACKContextTests, DecoderLargeHeader) {
128  // with this size basically the table will not be able to store any entry
129  uint32_t size = 32;
130  HPACKHeader header;
131  HPACKEncoder encoder(true, size);
132  HPACKDecoder decoder(size);
133  vector<HPACKHeader> headers;
134  headers.push_back(HPACKHeader(":path", "verylargeheader"));
135  // add a static entry
136  headers.push_back(HPACKHeader(":method", "GET"));
137  auto buf = encoder.encode(headers);
138  auto decoded = proxygen::hpack::decode(decoder, buf.get());
139  EXPECT_EQ(encoder.getTable().size(), 0);
140  EXPECT_EQ(decoder.getTable().size(), 0);
141 }
142 
146 TEST_F(HPACKContextTests, DecoderInvalidPeek) {
147  HPACKEncoder encoder(true);
148  HPACKDecoder decoder;
149  vector<HPACKHeader> headers;
150  headers.push_back(HPACKHeader("x-fb-debug", "test"));
151 
152  unique_ptr<IOBuf> encoded = encoder.encode(headers);
153  unique_ptr<IOBuf> first = IOBuf::create(128);
154  // set a trap for indexed header and don't call append
155  first->writableData()[0] = HPACK::INDEX_REF.code;
156 
157  first->appendChain(std::move(encoded));
158  auto decoded = proxygen::hpack::decode(decoder, first.get());
159 
160  EXPECT_FALSE(decoder.hasError());
161  EXPECT_EQ(*decoded, headers);
162 }
163 
167 TEST_F(HPACKContextTests, DecoderInvalidLiteralPeek) {
168  HPACKEncoder encoder(true);
169  HPACKDecoder decoder;
170  vector<HPACKHeader> headers;
171  headers.push_back(HPACKHeader("x-fb-random", "bla"));
172  unique_ptr<IOBuf> encoded = encoder.encode(headers);
173 
174  unique_ptr<IOBuf> first = IOBuf::create(128);
175  first->writableData()[0] = 0x3F;
176 
177  first->appendChain(std::move(encoded));
178  auto decoded = proxygen::hpack::decode(decoder, first.get());
179 
180  EXPECT_FALSE(decoder.hasError());
181  EXPECT_EQ(*decoded, headers);
182 }
183 
187 void checkError(const IOBuf* buf, const HPACK::DecodeError err) {
188  HPACKDecoder decoder;
189  auto decoded = proxygen::hpack::decode(decoder, buf);
190  EXPECT_TRUE(decoder.hasError());
191  EXPECT_EQ(decoder.getError(), err);
192 }
193 
194 TEST_F(HPACKContextTests, DecodeErrors) {
195  unique_ptr<IOBuf> buf = IOBuf::create(128);
196 
197  // 1. simulate an error decoding the index for an indexed header name
198  // we try to encode index 65
199  buf->writableData()[0] = 0x3F;
200  buf->append(1); // intentionally omit the second byte
201  checkError(buf.get(), HPACK::DecodeError::BUFFER_UNDERFLOW);
202 
203  // 2. invalid index for indexed header name
204  buf->writableData()[0] = 0x7F;
205  buf->writableData()[1] = 0xFF;
206  buf->writableData()[2] = 0x7F;
207  buf->append(2);
208  checkError(buf.get(), HPACK::DecodeError::INVALID_INDEX);
209 
210  // 2a. invalid integer for indexed header name
211  buf->writableData()[0] = 0x7F;
212  buf->writableData()[1] = 0xFF;
213  buf->writableData()[2] = 0xFF;
214  checkError(buf.get(), HPACK::DecodeError::BUFFER_UNDERFLOW);
215 
216  // 3. buffer overflow when decoding literal header name
217  buf->writableData()[0] = 0x00; // this will activate the non-indexed branch
218  checkError(buf.get(), HPACK::DecodeError::BUFFER_UNDERFLOW);
219 
220  // 4. buffer overflow when decoding a header value
221  // size for header name size and the actual header name
222  buf->writableData()[1] = 0x01;
223  buf->writableData()[2] = 'h';
224  checkError(buf.get(), HPACK::DecodeError::BUFFER_UNDERFLOW);
225 
226  // 5. buffer overflow decoding the index of an indexed header
227  buf->writableData()[0] = 0xFF; // first bit is 1 to mark indexed header
228  buf->writableData()[1] = 0x80; // first bit is 1 to continue the
229  // variable-length encoding
230  buf->writableData()[2] = 0x80;
231  checkError(buf.get(), HPACK::DecodeError::BUFFER_UNDERFLOW);
232 
233  // 6. Increase the table size
234  buf->writableData()[0] = 0x3F;
235  buf->writableData()[1] = 0xFF;
236  buf->writableData()[2] = 0x7F;
237  checkError(buf.get(), HPACK::DecodeError::INVALID_TABLE_SIZE);
238 
239  // 7. integer overflow decoding the index of an indexed header
240  buf->writableData()[0] = 0xFF; // first bit is 1 to mark indexed header
241  buf->writableData()[1] = 0xFF;
242  buf->writableData()[2] = 0xFF;
243  buf->writableData()[3] = 0xFF;
244  buf->writableData()[4] = 0xFF;
245  buf->writableData()[5] = 0xFF;
246  buf->writableData()[6] = 0xFF;
247  buf->writableData()[7] = 0xFF;
248  buf->writableData()[8] = 0xFF;
249  buf->writableData()[9] = 0xFF;
250  buf->writableData()[10] = 0x7F;
251  buf->append(8);
252  checkError(buf.get(), HPACK::DecodeError::INTEGER_OVERFLOW);
253 }
254 
255 TEST_F(HPACKContextTests, ExcludeHeadersLargerThanTable) {
256  HPACKEncoder encoder{true, 128};
257  std::string longer = std::string(150, '.');
258  HPACKHeader header1(longer, "header");
259  HPACKHeader header2("Short", "header");
260 
261  CHECK_GT(header1.bytes(), 128);
262  CHECK_LT(header2.bytes(), 128);
263 
264  vector<HPACKHeader> headers;
265  headers.push_back(std::move(header2));
266  headers.push_back(std::move(header1));
267 
268  encoder.encode(headers);
269 
270  CHECK_EQ(encoder.getIndex(headers[1]), 0);
271  CHECK_EQ(encoder.getIndex(headers[0]), 62);
272 }
273 
274 TEST_P(HPACKContextTests, ContextUpdate) {
275  HPACKEncoder encoder(true);
276  HPACKDecoder decoder;
277  vector<HPACKHeader> headers;
278  bool setDecoderSize = GetParam();
279  encoder.setHeaderTableSize(8192);
280  if (setDecoderSize) {
281  decoder.setHeaderTableMaxSize(8192);
282  }
283  headers.push_back(HPACKHeader("x-fb-random", "bla"));
284  unique_ptr<IOBuf> encoded = encoder.encode(headers);
285 
286  unique_ptr<IOBuf> first = IOBuf::create(128);
287 
288  first->appendChain(std::move(encoded));
289  auto decoded = proxygen::hpack::decode(decoder, first.get());
290 
291 
292  EXPECT_EQ(decoder.hasError(), !setDecoderSize);
293  if (setDecoderSize) {
294  EXPECT_EQ(*decoded, headers);
295  } else {
296  EXPECT_EQ(decoder.getError(), HPACK::DecodeError::INVALID_TABLE_SIZE);
297  }
298 }
299 
302  ::testing::Values(true, false));
const uint32_t kTableSize
HPACK::DecodeError getError() const
bool empty() const
Definition: FBString.h:1372
const Instruction INDEX_REF
static std::unique_ptr< IOBuf > create(std::size_t capacity)
Definition: IOBuf.cpp:229
HTTPHeaderCode getHeaderCode() const
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
context
Definition: CMakeCache.txt:563
void appendChain(std::unique_ptr< IOBuf > &&iobuf)
Definition: IOBuf.h:827
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
STL namespace.
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
uint32_t bytes() const
Definition: HPACKHeader.h:49
constexpr auto size(C const &c) -> decltype(c.size())
Definition: Access.h:45
unique_ptr< HPACKDecoder::headers_t > decode(HPACKDecoder &decoder, const IOBuf *buffer)
Definition: TestUtil.cpp:95
const HPACKHeader & getHeader(uint32_t index)
uint32_t getIndex(const HPACKHeader &header) const
uint8_t * writableData()
Definition: IOBuf.h:509
std::size_t length() const
Definition: IOBuf.h:533
void setHeaderTableSize(uint32_t size)
Definition: HPACKEncoder.h:35
TEST_P(HPACKContextTests, ContextUpdate)
void checkError(const IOBuf *buf, const HPACK::DecodeError err)
HPACKHeaderName name
Definition: HPACKHeader.h:82
INSTANTIATE_TEST_CASE_P(Context, HPACKContextTests,::testing::Values(true, false))
const HeaderTable & getTable() const
Definition: HPACKContext.h:48
void add(const HPACKHeader &header)
TEST_F(AsyncSSLSocketWriteTest, write_coalescing1)
std::unique_ptr< folly::IOBuf > encode(const std::vector< HPACKHeader > &headers, uint32_t headroom=0)
std::size_t computeChainDataLength() const
Definition: IOBuf.cpp:501
TestContext(uint32_t tableSize)
folly::fbstring value
Definition: HPACKHeader.h:83
#define EXPECT_TRUE(condition)
Definition: gtest.h:1859
void setHeaderTableMaxSize(uint32_t maxSize)
bool isStatic(uint32_t index) const
const char * string
Definition: Conv.cpp:212
HPACKHeader copy() const
Definition: HPACKHeader.h:71
uint32_t size() const
Definition: HeaderTable.h:105
#define EXPECT_FALSE(condition)
Definition: gtest.h:1862
#define EXPECT_LT(val1, val2)
Definition: gtest.h:1930
PUSHMI_INLINE_VAR constexpr detail::get_fn< T > get
Definition: submit.h:391
void append(std::size_t amount)
Definition: IOBuf.h:689
constexpr detail::First first
Definition: Base-inl.h:2553
#define EXPECT_GT(val1, val2)
Definition: gtest.h:1934
const std::string & get() const