OLD | NEW |
| (Empty) |
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 <cmath> | |
6 #include <ctime> | |
7 #include <vector> | |
8 | |
9 #include "base/rand_util.h" | |
10 #include "net/spdy/hpack/hpack_constants.h" | |
11 #include "net/spdy/hpack/hpack_decoder.h" | |
12 #include "net/spdy/hpack/hpack_encoder.h" | |
13 #include "net/spdy/platform/api/spdy_string.h" | |
14 #include "net/spdy/spdy_test_utils.h" | |
15 #include "testing/gtest/include/gtest/gtest.h" | |
16 | |
17 namespace net { | |
18 namespace test { | |
19 | |
20 namespace { | |
21 | |
22 // Supports testing with the input split at every byte boundary. | |
23 enum InputSizeParam { ALL_INPUT, ONE_BYTE, ZERO_THEN_ONE_BYTE }; | |
24 | |
25 class 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) { | |
36 SpdyString encoded; | |
37 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(base::RandDouble()) * mean, sanity_bound); | |
72 } | |
73 | |
74 HpackEncoder encoder_; | |
75 HpackDecoder decoder_; | |
76 }; | |
77 | |
78 INSTANTIATE_TEST_CASE_P(Tests, | |
79 HpackRoundTripTest, | |
80 ::testing::Values(ALL_INPUT, | |
81 ONE_BYTE, | |
82 ZERO_THEN_ONE_BYTE)); | |
83 | |
84 TEST_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"; | |
111 headers["multivalue"] = SpdyString("foo\0bar", 7); | |
112 EXPECT_TRUE(RoundTrip(headers)); | |
113 } | |
114 } | |
115 | |
116 TEST_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"; | |
144 headers["multivalue"] = SpdyString("foo\0bar", 7); | |
145 EXPECT_TRUE(RoundTrip(headers)); | |
146 } | |
147 } | |
148 | |
149 TEST_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. | |
153 std::vector<SpdyString> pseudo_header_names, random_header_names; | |
154 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 | |
161 std::vector<SpdyString> values; | |
162 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 int seed = std::time(NULL); | |
171 LOG(INFO) << "Seeding with srand(" << seed << ")"; | |
172 srand(seed); | |
173 | |
174 for (size_t i = 0; i != 2000; ++i) { | |
175 SpdyHeaderBlock headers; | |
176 | |
177 // Choose a random number of headers to add, and of these a random subset | |
178 // will be HTTP/2 pseudo headers. | |
179 size_t header_count = 1 + SampleExponential(7, 50); | |
180 size_t pseudo_header_count = | |
181 std::min(header_count, 1 + SampleExponential(7, 50)); | |
182 EXPECT_LE(pseudo_header_count, header_count); | |
183 for (size_t j = 0; j != header_count; ++j) { | |
184 SpdyString name, value; | |
185 // Pseudo headers must be added before regular headers. | |
186 if (j < pseudo_header_count) { | |
187 // Choose one of the defined pseudo headers at random. | |
188 size_t name_index = base::RandGenerator(pseudo_header_names.size()); | |
189 name = pseudo_header_names[name_index]; | |
190 } else { | |
191 // Randomly reuse an existing header name, or generate a new one. | |
192 size_t name_index = SampleExponential(20, 200); | |
193 if (name_index >= random_header_names.size()) { | |
194 name = base::RandBytesAsString(1 + SampleExponential(5, 30)); | |
195 // A regular header cannot begin with the pseudo header prefix ":". | |
196 if (name[0] == ':') { | |
197 name[0] = 'x'; | |
198 } | |
199 random_header_names.push_back(name); | |
200 } else { | |
201 name = random_header_names[name_index]; | |
202 } | |
203 } | |
204 | |
205 // Randomly reuse an existing value, or generate a new one. | |
206 size_t value_index = SampleExponential(20, 200); | |
207 if (value_index >= values.size()) { | |
208 SpdyString newvalue = | |
209 base::RandBytesAsString(1 + SampleExponential(15, 75)); | |
210 // Currently order is not preserved in the encoder. In particular, | |
211 // when a value is decomposed at \0 delimiters, its parts might get | |
212 // encoded out of order if some but not all of them already exist in | |
213 // the header table. For now, avoid \0 bytes in values. | |
214 std::replace(newvalue.begin(), newvalue.end(), '\x00', '\x01'); | |
215 values.push_back(newvalue); | |
216 value = values.back(); | |
217 } else { | |
218 value = values[value_index]; | |
219 } | |
220 headers[name] = value; | |
221 } | |
222 EXPECT_TRUE(RoundTrip(headers)); | |
223 } | |
224 } | |
225 | |
226 } // namespace | |
227 | |
228 } // namespace test | |
229 } // namespace net | |
OLD | NEW |