// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cmath>
#include <ctime>
#include <map>
#include <string>
#include <vector>
#include "base/rand_util.h"
#include "net/spdy/hpack_constants.h"
#include "net/spdy/hpack_decoder.h"
#include "net/spdy/hpack_encoder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
using std::map;
using std::string;
using std::vector;
namespace {
class HpackRoundTripTest : public ::testing::Test {
protected:
HpackRoundTripTest()
: encoder_(ObtainHpackHuffmanTable()),
decoder_(ObtainHpackHuffmanTable()) {}
virtual void SetUp() {
// Use a small table size to tickle eviction handling.
encoder_.ApplyHeaderTableSizeSetting(256);
decoder_.ApplyHeaderTableSizeSetting(256);
}
bool RoundTrip(const map<string, string>& header_set) {
string encoded;
encoder_.EncodeHeaderSet(header_set, &encoded);
bool success = decoder_.HandleControlFrameHeadersData(
1, encoded.data(), encoded.size());
success &= decoder_.HandleControlFrameHeadersComplete(1);
EXPECT_EQ(header_set, decoder_.decoded_block());
return success;
}
size_t SampleExponential(size_t mean, size_t sanity_bound) {
return std::min<size_t>(-std::log(base::RandDouble()) * mean,
sanity_bound);
}
HpackEncoder encoder_;
HpackDecoder decoder_;
};
TEST_F(HpackRoundTripTest, ResponseFixtures) {
{
map<string, string> headers;
headers[":status"] = "302";
headers["cache-control"] = "private";
headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
headers["location"] = "https://www.example.com";
EXPECT_TRUE(RoundTrip(headers));
}
{
map<string, string> headers;
headers[":status"] = "200";
headers["cache-control"] = "private";
headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
headers["location"] = "https://www.example.com";
EXPECT_TRUE(RoundTrip(headers));
}
{
map<string, string> headers;
headers[":status"] = "200";
headers["cache-control"] = "private";
headers["content-encoding"] = "gzip";
headers["date"] = "Mon, 21 Oct 2013 20:13:22 GMT";
headers["location"] = "https://www.example.com";
headers["set-cookie"] = "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
" max-age=3600; version=1";
EXPECT_TRUE(RoundTrip(headers));
}
}
TEST_F(HpackRoundTripTest, RequestFixtures) {
{
map<string, string> headers;
headers[":authority"] = "www.example.com";
headers[":method"] = "GET";
headers[":path"] = "/";
headers[":scheme"] = "http";
headers["cookie"] = "baz=bing; foo=bar";
EXPECT_TRUE(RoundTrip(headers));
}
{
map<string, string> headers;
headers[":authority"] = "www.example.com";
headers[":method"] = "GET";
headers[":path"] = "/";
headers[":scheme"] = "http";
headers["cache-control"] = "no-cache";
headers["cookie"] = "fizzle=fazzle; foo=bar";
EXPECT_TRUE(RoundTrip(headers));
}
{
map<string, string> headers;
headers[":authority"] = "www.example.com";
headers[":method"] = "GET";
headers[":path"] = "/index.html";
headers[":scheme"] = "https";
headers["custom-key"] = "custom-value";
headers["cookie"] = "baz=bing; fizzle=fazzle; garbage";
EXPECT_TRUE(RoundTrip(headers));
}
}
TEST_F(HpackRoundTripTest, RandomizedExamples) {
// Grow vectors of names & values, which are seeded with fixtures and then
// expanded with dynamically generated data. Samples are taken using the
// exponential distribution.
vector<string> names;
names.push_back(":authority");
names.push_back(":path");
names.push_back(":status");
// TODO(jgraettinger): Enable "cookie" as a name fixture. Crumbs may be
// reconstructed in any order, which breaks the simple validation used here.
vector<string> values;
values.push_back("/");
values.push_back("/index.html");
values.push_back("200");
values.push_back("404");
values.push_back("");
values.push_back("baz=bing; foo=bar; garbage");
values.push_back("baz=bing; fizzle=fazzle; garbage");
int seed = std::time(NULL);
LOG(INFO) << "Seeding with srand(" << seed << ")";
srand(seed);
for (size_t i = 0; i != 2000; ++i) {
map<string, string> headers;
size_t header_count = 1 + SampleExponential(7, 50);
for (size_t j = 0; j != header_count; ++j) {
size_t name_index = SampleExponential(20, 200);
size_t value_index = SampleExponential(20, 200);
string name, value;
if (name_index >= names.size()) {
names.push_back(base::RandBytesAsString(1 + SampleExponential(5, 30)));
name = names.back();
} else {
name = names[name_index];
}
if (value_index >= values.size()) {
values.push_back(base::RandBytesAsString(
1 + SampleExponential(15, 75)));
value = values.back();
} else {
value = values[value_index];
}
headers[name] = value;
}
EXPECT_TRUE(RoundTrip(headers));
}
}
} // namespace
} // namespace net