xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_round_trip_test.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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