blob: bae0fd2e7ef89dc36b6cae2945bfb701ffc0e0b8 [file] [log] [blame]
QUICHE team82dee2f2019-01-18 12:35:12 -05001// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <cmath>
6#include <cstdint>
7#include <ctime>
8#include <vector>
9
QUICHE team82dee2f2019-01-18 12:35:12 -050010#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
11#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
12#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h"
13#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
14#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
danzh8f3a5762019-06-25 13:43:51 -070015#include "net/third_party/quiche/src/spdy/platform/api/spdy_test.h"
QUICHE team82dee2f2019-01-18 12:35:12 -050016
17namespace spdy {
18namespace test {
19
20namespace {
21
22// Supports testing with the input split at every byte boundary.
23enum InputSizeParam { ALL_INPUT, ONE_BYTE, ZERO_THEN_ONE_BYTE };
24
25class HpackRoundTripTest : public ::testing::TestWithParam<InputSizeParam> {
26 protected:
27 HpackRoundTripTest() : encoder_(ObtainHpackHuffmanTable()), decoder_() {}
28
29 void SetUp() override {
30 // Use a small table size to tickle eviction handling.
31 encoder_.ApplyHeaderTableSizeSetting(256);
32 decoder_.ApplyHeaderTableSizeSetting(256);
33 }
34
35 bool RoundTrip(const SpdyHeaderBlock& header_set) {
bnc44712912019-08-15 18:58:14 -070036 std::string encoded;
QUICHE team82dee2f2019-01-18 12:35:12 -050037 encoder_.EncodeHeaderSet(header_set, &encoded);
38
39 bool success = true;
40 if (GetParam() == ALL_INPUT) {
41 // Pass all the input to the decoder at once.
42 success = decoder_.HandleControlFrameHeadersData(encoded.data(),
43 encoded.size());
44 } else if (GetParam() == ONE_BYTE) {
45 // Pass the input to the decoder one byte at a time.
46 const char* data = encoded.data();
47 for (size_t ndx = 0; ndx < encoded.size() && success; ++ndx) {
48 success = decoder_.HandleControlFrameHeadersData(data + ndx, 1);
49 }
50 } else if (GetParam() == ZERO_THEN_ONE_BYTE) {
51 // Pass the input to the decoder one byte at a time, but before each
52 // byte pass an empty buffer.
53 const char* data = encoded.data();
54 for (size_t ndx = 0; ndx < encoded.size() && success; ++ndx) {
55 success = (decoder_.HandleControlFrameHeadersData(data + ndx, 0) &&
56 decoder_.HandleControlFrameHeadersData(data + ndx, 1));
57 }
58 } else {
59 ADD_FAILURE() << "Unknown param: " << GetParam();
60 }
61
62 if (success) {
63 success = decoder_.HandleControlFrameHeadersComplete(nullptr);
64 }
65
66 EXPECT_EQ(header_set, decoder_.decoded_block());
67 return success;
68 }
69
70 size_t SampleExponential(size_t mean, size_t sanity_bound) {
71 return std::min<size_t>(-std::log(random_.RandDouble()) * mean,
72 sanity_bound);
73 }
74
75 http2::test::Http2Random random_;
76 HpackEncoder encoder_;
77 HpackDecoderAdapter decoder_;
78};
79
QUICHE teamded03512019-03-07 14:45:11 -080080INSTANTIATE_TEST_SUITE_P(Tests,
81 HpackRoundTripTest,
82 ::testing::Values(ALL_INPUT,
83 ONE_BYTE,
QUICHE team3cab5a92019-01-30 21:10:16 -050084 ZERO_THEN_ONE_BYTE));
QUICHE team82dee2f2019-01-18 12:35:12 -050085
86TEST_P(HpackRoundTripTest, ResponseFixtures) {
87 {
88 SpdyHeaderBlock headers;
89 headers[":status"] = "302";
90 headers["cache-control"] = "private";
91 headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
92 headers["location"] = "https://www.example.com";
93 EXPECT_TRUE(RoundTrip(headers));
94 }
95 {
96 SpdyHeaderBlock headers;
97 headers[":status"] = "200";
98 headers["cache-control"] = "private";
99 headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
100 headers["location"] = "https://www.example.com";
101 EXPECT_TRUE(RoundTrip(headers));
102 }
103 {
104 SpdyHeaderBlock headers;
105 headers[":status"] = "200";
106 headers["cache-control"] = "private";
107 headers["content-encoding"] = "gzip";
108 headers["date"] = "Mon, 21 Oct 2013 20:13:22 GMT";
109 headers["location"] = "https://www.example.com";
110 headers["set-cookie"] =
111 "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
112 " max-age=3600; version=1";
bnc44712912019-08-15 18:58:14 -0700113 headers["multivalue"] = std::string("foo\0bar", 7);
QUICHE team82dee2f2019-01-18 12:35:12 -0500114 EXPECT_TRUE(RoundTrip(headers));
115 }
116}
117
118TEST_P(HpackRoundTripTest, RequestFixtures) {
119 {
120 SpdyHeaderBlock headers;
121 headers[":authority"] = "www.example.com";
122 headers[":method"] = "GET";
123 headers[":path"] = "/";
124 headers[":scheme"] = "http";
125 headers["cookie"] = "baz=bing; foo=bar";
126 EXPECT_TRUE(RoundTrip(headers));
127 }
128 {
129 SpdyHeaderBlock headers;
130 headers[":authority"] = "www.example.com";
131 headers[":method"] = "GET";
132 headers[":path"] = "/";
133 headers[":scheme"] = "http";
134 headers["cache-control"] = "no-cache";
135 headers["cookie"] = "foo=bar; spam=eggs";
136 EXPECT_TRUE(RoundTrip(headers));
137 }
138 {
139 SpdyHeaderBlock headers;
140 headers[":authority"] = "www.example.com";
141 headers[":method"] = "GET";
142 headers[":path"] = "/index.html";
143 headers[":scheme"] = "https";
144 headers["custom-key"] = "custom-value";
145 headers["cookie"] = "baz=bing; fizzle=fazzle; garbage";
bnc44712912019-08-15 18:58:14 -0700146 headers["multivalue"] = std::string("foo\0bar", 7);
QUICHE team82dee2f2019-01-18 12:35:12 -0500147 EXPECT_TRUE(RoundTrip(headers));
148 }
149}
150
151TEST_P(HpackRoundTripTest, RandomizedExamples) {
152 // Grow vectors of names & values, which are seeded with fixtures and then
153 // expanded with dynamically generated data. Samples are taken using the
154 // exponential distribution.
bnc44712912019-08-15 18:58:14 -0700155 std::vector<std::string> pseudo_header_names, random_header_names;
QUICHE team82dee2f2019-01-18 12:35:12 -0500156 pseudo_header_names.push_back(":authority");
157 pseudo_header_names.push_back(":path");
158 pseudo_header_names.push_back(":status");
159
160 // TODO(jgraettinger): Enable "cookie" as a name fixture. Crumbs may be
161 // reconstructed in any order, which breaks the simple validation used here.
162
bnc44712912019-08-15 18:58:14 -0700163 std::vector<std::string> values;
QUICHE team82dee2f2019-01-18 12:35:12 -0500164 values.push_back("/");
165 values.push_back("/index.html");
166 values.push_back("200");
167 values.push_back("404");
168 values.push_back("");
169 values.push_back("baz=bing; foo=bar; garbage");
170 values.push_back("baz=bing; fizzle=fazzle; garbage");
171
172 for (size_t i = 0; i != 2000; ++i) {
173 SpdyHeaderBlock headers;
174
175 // Choose a random number of headers to add, and of these a random subset
176 // will be HTTP/2 pseudo headers.
177 size_t header_count = 1 + SampleExponential(7, 50);
178 size_t pseudo_header_count =
179 std::min(header_count, 1 + SampleExponential(7, 50));
180 EXPECT_LE(pseudo_header_count, header_count);
181 for (size_t j = 0; j != header_count; ++j) {
bnc44712912019-08-15 18:58:14 -0700182 std::string name, value;
QUICHE team82dee2f2019-01-18 12:35:12 -0500183 // Pseudo headers must be added before regular headers.
184 if (j < pseudo_header_count) {
185 // Choose one of the defined pseudo headers at random.
186 size_t name_index = random_.Uniform(pseudo_header_names.size());
187 name = pseudo_header_names[name_index];
188 } else {
189 // Randomly reuse an existing header name, or generate a new one.
190 size_t name_index = SampleExponential(20, 200);
191 if (name_index >= random_header_names.size()) {
192 name = random_.RandString(1 + SampleExponential(5, 30));
193 // A regular header cannot begin with the pseudo header prefix ":".
194 if (name[0] == ':') {
195 name[0] = 'x';
196 }
197 random_header_names.push_back(name);
198 } else {
199 name = random_header_names[name_index];
200 }
201 }
202
203 // Randomly reuse an existing value, or generate a new one.
204 size_t value_index = SampleExponential(20, 200);
205 if (value_index >= values.size()) {
bnc44712912019-08-15 18:58:14 -0700206 std::string newvalue =
207 random_.RandString(1 + SampleExponential(15, 75));
QUICHE team82dee2f2019-01-18 12:35:12 -0500208 // Currently order is not preserved in the encoder. In particular,
209 // when a value is decomposed at \0 delimiters, its parts might get
210 // encoded out of order if some but not all of them already exist in
211 // the header table. For now, avoid \0 bytes in values.
212 std::replace(newvalue.begin(), newvalue.end(), '\x00', '\x01');
213 values.push_back(newvalue);
214 value = values.back();
215 } else {
216 value = values[value_index];
217 }
218 headers[name] = value;
219 }
220 EXPECT_TRUE(RoundTrip(headers));
221 }
222}
223
224} // namespace
225
226} // namespace test
227} // namespace spdy