blob: 047ae7292e6f6e538ca80c19e437012d6378d70b [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 team5be974e2020-12-29 18:35:24 -050010#include "http2/test_tools/http2_random.h"
11#include "common/platform/api/quiche_test.h"
12#include "spdy/core/hpack/hpack_constants.h"
13#include "spdy/core/hpack/hpack_decoder_adapter.h"
14#include "spdy/core/hpack/hpack_encoder.h"
15#include "spdy/core/spdy_test_utils.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
QUICHE teamf3c80c92020-02-12 09:47:55 -080025class HpackRoundTripTest : public QuicheTestWithParam<InputSizeParam> {
QUICHE team82dee2f2019-01-18 12:35:12 -050026 protected:
QUICHE team82dee2f2019-01-18 12:35:12 -050027 void SetUp() override {
28 // Use a small table size to tickle eviction handling.
29 encoder_.ApplyHeaderTableSizeSetting(256);
30 decoder_.ApplyHeaderTableSizeSetting(256);
31 }
32
33 bool RoundTrip(const SpdyHeaderBlock& header_set) {
bnc44712912019-08-15 18:58:14 -070034 std::string encoded;
QUICHE team82dee2f2019-01-18 12:35:12 -050035 encoder_.EncodeHeaderSet(header_set, &encoded);
36
37 bool success = true;
38 if (GetParam() == ALL_INPUT) {
39 // Pass all the input to the decoder at once.
40 success = decoder_.HandleControlFrameHeadersData(encoded.data(),
41 encoded.size());
42 } else if (GetParam() == ONE_BYTE) {
43 // Pass the input to the decoder one byte at a time.
44 const char* data = encoded.data();
45 for (size_t ndx = 0; ndx < encoded.size() && success; ++ndx) {
46 success = decoder_.HandleControlFrameHeadersData(data + ndx, 1);
47 }
48 } else if (GetParam() == ZERO_THEN_ONE_BYTE) {
49 // Pass the input to the decoder one byte at a time, but before each
50 // byte pass an empty buffer.
51 const char* data = encoded.data();
52 for (size_t ndx = 0; ndx < encoded.size() && success; ++ndx) {
53 success = (decoder_.HandleControlFrameHeadersData(data + ndx, 0) &&
54 decoder_.HandleControlFrameHeadersData(data + ndx, 1));
55 }
56 } else {
57 ADD_FAILURE() << "Unknown param: " << GetParam();
58 }
59
60 if (success) {
61 success = decoder_.HandleControlFrameHeadersComplete(nullptr);
62 }
63
64 EXPECT_EQ(header_set, decoder_.decoded_block());
65 return success;
66 }
67
68 size_t SampleExponential(size_t mean, size_t sanity_bound) {
69 return std::min<size_t>(-std::log(random_.RandDouble()) * mean,
70 sanity_bound);
71 }
72
73 http2::test::Http2Random random_;
74 HpackEncoder encoder_;
75 HpackDecoderAdapter decoder_;
76};
77
QUICHE teamded03512019-03-07 14:45:11 -080078INSTANTIATE_TEST_SUITE_P(Tests,
79 HpackRoundTripTest,
80 ::testing::Values(ALL_INPUT,
81 ONE_BYTE,
QUICHE team3cab5a92019-01-30 21:10:16 -050082 ZERO_THEN_ONE_BYTE));
QUICHE team82dee2f2019-01-18 12:35:12 -050083
84TEST_P(HpackRoundTripTest, ResponseFixtures) {
85 {
86 SpdyHeaderBlock headers;
87 headers[":status"] = "302";
88 headers["cache-control"] = "private";
89 headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
90 headers["location"] = "https://www.example.com";
91 EXPECT_TRUE(RoundTrip(headers));
92 }
93 {
94 SpdyHeaderBlock headers;
95 headers[":status"] = "200";
96 headers["cache-control"] = "private";
97 headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
98 headers["location"] = "https://www.example.com";
99 EXPECT_TRUE(RoundTrip(headers));
100 }
101 {
102 SpdyHeaderBlock headers;
103 headers[":status"] = "200";
104 headers["cache-control"] = "private";
105 headers["content-encoding"] = "gzip";
106 headers["date"] = "Mon, 21 Oct 2013 20:13:22 GMT";
107 headers["location"] = "https://www.example.com";
108 headers["set-cookie"] =
109 "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
110 " max-age=3600; version=1";
bnc44712912019-08-15 18:58:14 -0700111 headers["multivalue"] = std::string("foo\0bar", 7);
QUICHE team82dee2f2019-01-18 12:35:12 -0500112 EXPECT_TRUE(RoundTrip(headers));
113 }
114}
115
116TEST_P(HpackRoundTripTest, RequestFixtures) {
117 {
118 SpdyHeaderBlock headers;
119 headers[":authority"] = "www.example.com";
120 headers[":method"] = "GET";
121 headers[":path"] = "/";
122 headers[":scheme"] = "http";
123 headers["cookie"] = "baz=bing; foo=bar";
124 EXPECT_TRUE(RoundTrip(headers));
125 }
126 {
127 SpdyHeaderBlock headers;
128 headers[":authority"] = "www.example.com";
129 headers[":method"] = "GET";
130 headers[":path"] = "/";
131 headers[":scheme"] = "http";
132 headers["cache-control"] = "no-cache";
133 headers["cookie"] = "foo=bar; spam=eggs";
134 EXPECT_TRUE(RoundTrip(headers));
135 }
136 {
137 SpdyHeaderBlock headers;
138 headers[":authority"] = "www.example.com";
139 headers[":method"] = "GET";
140 headers[":path"] = "/index.html";
141 headers[":scheme"] = "https";
142 headers["custom-key"] = "custom-value";
143 headers["cookie"] = "baz=bing; fizzle=fazzle; garbage";
bnc44712912019-08-15 18:58:14 -0700144 headers["multivalue"] = std::string("foo\0bar", 7);
QUICHE team82dee2f2019-01-18 12:35:12 -0500145 EXPECT_TRUE(RoundTrip(headers));
146 }
147}
148
149TEST_P(HpackRoundTripTest, RandomizedExamples) {
150 // Grow vectors of names & values, which are seeded with fixtures and then
151 // expanded with dynamically generated data. Samples are taken using the
152 // exponential distribution.
bnc44712912019-08-15 18:58:14 -0700153 std::vector<std::string> pseudo_header_names, random_header_names;
QUICHE team82dee2f2019-01-18 12:35:12 -0500154 pseudo_header_names.push_back(":authority");
155 pseudo_header_names.push_back(":path");
156 pseudo_header_names.push_back(":status");
157
158 // TODO(jgraettinger): Enable "cookie" as a name fixture. Crumbs may be
159 // reconstructed in any order, which breaks the simple validation used here.
160
bnc44712912019-08-15 18:58:14 -0700161 std::vector<std::string> values;
QUICHE team82dee2f2019-01-18 12:35:12 -0500162 values.push_back("/");
163 values.push_back("/index.html");
164 values.push_back("200");
165 values.push_back("404");
166 values.push_back("");
167 values.push_back("baz=bing; foo=bar; garbage");
168 values.push_back("baz=bing; fizzle=fazzle; garbage");
169
170 for (size_t i = 0; i != 2000; ++i) {
171 SpdyHeaderBlock headers;
172
173 // Choose a random number of headers to add, and of these a random subset
174 // will be HTTP/2 pseudo headers.
175 size_t header_count = 1 + SampleExponential(7, 50);
176 size_t pseudo_header_count =
177 std::min(header_count, 1 + SampleExponential(7, 50));
178 EXPECT_LE(pseudo_header_count, header_count);
179 for (size_t j = 0; j != header_count; ++j) {
bnc44712912019-08-15 18:58:14 -0700180 std::string name, value;
QUICHE team82dee2f2019-01-18 12:35:12 -0500181 // Pseudo headers must be added before regular headers.
182 if (j < pseudo_header_count) {
183 // Choose one of the defined pseudo headers at random.
184 size_t name_index = random_.Uniform(pseudo_header_names.size());
185 name = pseudo_header_names[name_index];
186 } else {
187 // Randomly reuse an existing header name, or generate a new one.
188 size_t name_index = SampleExponential(20, 200);
189 if (name_index >= random_header_names.size()) {
190 name = random_.RandString(1 + SampleExponential(5, 30));
191 // A regular header cannot begin with the pseudo header prefix ":".
192 if (name[0] == ':') {
193 name[0] = 'x';
194 }
195 random_header_names.push_back(name);
196 } else {
197 name = random_header_names[name_index];
198 }
199 }
200
201 // Randomly reuse an existing value, or generate a new one.
202 size_t value_index = SampleExponential(20, 200);
203 if (value_index >= values.size()) {
bnc44712912019-08-15 18:58:14 -0700204 std::string newvalue =
205 random_.RandString(1 + SampleExponential(15, 75));
QUICHE team82dee2f2019-01-18 12:35:12 -0500206 // Currently order is not preserved in the encoder. In particular,
207 // when a value is decomposed at \0 delimiters, its parts might get
208 // encoded out of order if some but not all of them already exist in
209 // the header table. For now, avoid \0 bytes in values.
210 std::replace(newvalue.begin(), newvalue.end(), '\x00', '\x01');
211 values.push_back(newvalue);
212 value = values.back();
213 } else {
214 value = values[value_index];
215 }
216 headers[name] = value;
217 }
218 EXPECT_TRUE(RoundTrip(headers));
219 }
220}
221
222} // namespace
223
224} // namespace test
225} // namespace spdy