1 // 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 <algorithm>
6 #include <cmath>
7 #include <ctime>
8 #include <string>
9 #include <vector>
10
11 #include "quiche/http2/test_tools/http2_random.h"
12 #include "quiche/common/platform/api/quiche_test.h"
13 #include "quiche/spdy/core/hpack/hpack_decoder_adapter.h"
14 #include "quiche/spdy/core/hpack/hpack_encoder.h"
15 #include "quiche/spdy/core/http2_header_block.h"
16 #include "quiche/spdy/core/recording_headers_handler.h"
17
18 namespace spdy {
19 namespace test {
20
21 namespace {
22
23 // Supports testing with the input split at every byte boundary.
24 enum InputSizeParam { ALL_INPUT, ONE_BYTE, ZERO_THEN_ONE_BYTE };
25
26 class HpackRoundTripTest
27 : public quiche::test::QuicheTestWithParam<InputSizeParam> {
28 protected:
SetUp()29 void SetUp() override {
30 // Use a small table size to tickle eviction handling.
31 encoder_.ApplyHeaderTableSizeSetting(256);
32 decoder_.ApplyHeaderTableSizeSetting(256);
33 }
34
RoundTrip(const Http2HeaderBlock & header_set)35 bool RoundTrip(const Http2HeaderBlock& header_set) {
36 std::string encoded = encoder_.EncodeHeaderBlock(header_set);
37
38 bool success = true;
39 decoder_.HandleControlFrameHeadersStart(&handler_);
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();
64 }
65
66 EXPECT_EQ(header_set, handler_.decoded_block());
67 return success;
68 }
69
SampleExponential(size_t mean,size_t sanity_bound)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 RecordingHeadersHandler handler_;
79 };
80
81 INSTANTIATE_TEST_SUITE_P(Tests, HpackRoundTripTest,
82 ::testing::Values(ALL_INPUT, ONE_BYTE,
83 ZERO_THEN_ONE_BYTE));
84
TEST_P(HpackRoundTripTest,ResponseFixtures)85 TEST_P(HpackRoundTripTest, ResponseFixtures) {
86 {
87 Http2HeaderBlock headers;
88 headers[":status"] = "302";
89 headers["cache-control"] = "private";
90 headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
91 headers["location"] = "https://www.example.com";
92 EXPECT_TRUE(RoundTrip(headers));
93 }
94 {
95 Http2HeaderBlock headers;
96 headers[":status"] = "200";
97 headers["cache-control"] = "private";
98 headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
99 headers["location"] = "https://www.example.com";
100 EXPECT_TRUE(RoundTrip(headers));
101 }
102 {
103 Http2HeaderBlock headers;
104 headers[":status"] = "200";
105 headers["cache-control"] = "private";
106 headers["content-encoding"] = "gzip";
107 headers["date"] = "Mon, 21 Oct 2013 20:13:22 GMT";
108 headers["location"] = "https://www.example.com";
109 headers["set-cookie"] =
110 "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
111 " max-age=3600; version=1";
112 headers["multivalue"] = std::string("foo\0bar", 7);
113 EXPECT_TRUE(RoundTrip(headers));
114 }
115 }
116
TEST_P(HpackRoundTripTest,RequestFixtures)117 TEST_P(HpackRoundTripTest, RequestFixtures) {
118 {
119 Http2HeaderBlock headers;
120 headers[":authority"] = "www.example.com";
121 headers[":method"] = "GET";
122 headers[":path"] = "/";
123 headers[":scheme"] = "http";
124 headers["cookie"] = "baz=bing; foo=bar";
125 EXPECT_TRUE(RoundTrip(headers));
126 }
127 {
128 Http2HeaderBlock headers;
129 headers[":authority"] = "www.example.com";
130 headers[":method"] = "GET";
131 headers[":path"] = "/";
132 headers[":scheme"] = "http";
133 headers["cache-control"] = "no-cache";
134 headers["cookie"] = "foo=bar; spam=eggs";
135 EXPECT_TRUE(RoundTrip(headers));
136 }
137 {
138 Http2HeaderBlock headers;
139 headers[":authority"] = "www.example.com";
140 headers[":method"] = "GET";
141 headers[":path"] = "/index.html";
142 headers[":scheme"] = "https";
143 headers["custom-key"] = "custom-value";
144 headers["cookie"] = "baz=bing; fizzle=fazzle; garbage";
145 headers["multivalue"] = std::string("foo\0bar", 7);
146 EXPECT_TRUE(RoundTrip(headers));
147 }
148 }
149
TEST_P(HpackRoundTripTest,RandomizedExamples)150 TEST_P(HpackRoundTripTest, RandomizedExamples) {
151 // Grow vectors of names & values, which are seeded with fixtures and then
152 // expanded with dynamically generated data. Samples are taken using the
153 // exponential distribution.
154 std::vector<std::string> pseudo_header_names, random_header_names;
155 pseudo_header_names.push_back(":authority");
156 pseudo_header_names.push_back(":path");
157 pseudo_header_names.push_back(":status");
158
159 // TODO(jgraettinger): Enable "cookie" as a name fixture. Crumbs may be
160 // reconstructed in any order, which breaks the simple validation used here.
161
162 std::vector<std::string> values;
163 values.push_back("/");
164 values.push_back("/index.html");
165 values.push_back("200");
166 values.push_back("404");
167 values.push_back("");
168 values.push_back("baz=bing; foo=bar; garbage");
169 values.push_back("baz=bing; fizzle=fazzle; garbage");
170
171 for (size_t i = 0; i != 2000; ++i) {
172 Http2HeaderBlock headers;
173
174 // Choose a random number of headers to add, and of these a random subset
175 // will be HTTP/2 pseudo headers.
176 size_t header_count = 1 + SampleExponential(7, 50);
177 size_t pseudo_header_count =
178 std::min(header_count, 1 + SampleExponential(7, 50));
179 EXPECT_LE(pseudo_header_count, header_count);
180 for (size_t j = 0; j != header_count; ++j) {
181 std::string name, value;
182 // Pseudo headers must be added before regular headers.
183 if (j < pseudo_header_count) {
184 // Choose one of the defined pseudo headers at random.
185 size_t name_index = random_.Uniform(pseudo_header_names.size());
186 name = pseudo_header_names[name_index];
187 } else {
188 // Randomly reuse an existing header name, or generate a new one.
189 size_t name_index = SampleExponential(20, 200);
190 if (name_index >= random_header_names.size()) {
191 name = random_.RandString(1 + SampleExponential(5, 30));
192 // A regular header cannot begin with the pseudo header prefix ":".
193 if (name[0] == ':') {
194 name[0] = 'x';
195 }
196 random_header_names.push_back(name);
197 } else {
198 name = random_header_names[name_index];
199 }
200 }
201
202 // Randomly reuse an existing value, or generate a new one.
203 size_t value_index = SampleExponential(20, 200);
204 if (value_index >= values.size()) {
205 std::string newvalue =
206 random_.RandString(1 + SampleExponential(15, 75));
207 // Currently order is not preserved in the encoder. In particular,
208 // when a value is decomposed at \0 delimiters, its parts might get
209 // encoded out of order if some but not all of them already exist in
210 // the header table. For now, avoid \0 bytes in values.
211 std::replace(newvalue.begin(), newvalue.end(), '\x00', '\x01');
212 values.push_back(newvalue);
213 value = values.back();
214 } else {
215 value = values[value_index];
216 }
217 headers[name] = value;
218 }
219 EXPECT_TRUE(RoundTrip(headers));
220 }
221 }
222
223 } // namespace
224
225 } // namespace test
226 } // namespace spdy
227