| 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 |