blob: 7133c38c114ef76d7e26b6ad981f04d287cc87b4 [file] [log] [blame]
// Copyright 2016 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 "net/spdy/hpack/hpack_huffman_decoder.h"
#include <bitset>
#include <limits>
#include "base/logging.h"
#include "base/macros.h"
#include "base/rand_util.h"
#include "net/spdy/hpack/hpack_constants.h"
#include "net/spdy/hpack/hpack_huffman_table.h"
#include "net/spdy/hpack/hpack_input_stream.h"
#include "net/spdy/hpack/hpack_output_stream.h"
#include "net/spdy/platform/api/spdy_string_piece.h"
#include "net/spdy/spdy_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
namespace test {
namespace {
uint32_t RandUint32() {
return static_cast<uint32_t>(base::RandUint64() & 0xffffffff);
}
} // anonymous namespace
// Bits(HuffmanWord) constructs a bitset<32>, which produces nicely formatted
// binary numbers when LOG'd.
typedef std::bitset<32> Bits;
typedef HpackHuffmanDecoder::HuffmanWord HuffmanWord;
typedef HpackHuffmanDecoder::HuffmanCodeLength HuffmanCodeLength;
class HpackHuffmanDecoderPeer {
public:
static HuffmanCodeLength CodeLengthOfPrefix(HuffmanWord value) {
return HpackHuffmanDecoder::CodeLengthOfPrefix(value);
}
static HuffmanWord DecodeToCanonical(HuffmanCodeLength code_length,
HuffmanWord bits) {
return HpackHuffmanDecoder::DecodeToCanonical(code_length, bits);
}
static char CanonicalToSource(HuffmanWord canonical) {
return HpackHuffmanDecoder::CanonicalToSource(canonical);
}
};
// Tests of the ability to decode the HPACK Huffman Code, defined in:
// https://httpwg.github.io/specs/rfc7541.html#huffman.code
class HpackHuffmanDecoderTest : public ::testing::Test {
protected:
HpackHuffmanDecoderTest() : table_(ObtainHpackHuffmanTable()) {}
// Since kHpackHuffmanCode doesn't include the canonical symbol value,
// this helper helps us to decode directly to the source symbol, allowing
// for EOS.
uint16_t DecodeToSource(HuffmanCodeLength code_length, HuffmanWord bits) {
HuffmanWord canonical =
HpackHuffmanDecoderPeer::DecodeToCanonical(code_length, bits);
EXPECT_LE(canonical, 256u);
if (canonical == 256u) {
return canonical;
}
return static_cast<unsigned char>(
HpackHuffmanDecoderPeer::CanonicalToSource(canonical));
}
void EncodeString(SpdyStringPiece input, std::string* encoded) {
HpackOutputStream output_stream;
table_.EncodeString(input, &output_stream);
encoded->clear();
output_stream.TakeString(encoded);
// Verify EncodedSize() agrees with EncodeString().
EXPECT_EQ(encoded->size(), table_.EncodedSize(input));
}
std::string EncodeString(SpdyStringPiece input) {
std::string result;
EncodeString(input, &result);
return result;
}
const HpackHuffmanTable& table_;
};
TEST_F(HpackHuffmanDecoderTest, CodeLengthOfPrefix) {
for (const HpackHuffmanSymbol& entry : HpackHuffmanCode()) {
// First confirm our assumption that the low order bits of entry.code
// (those not part of the high order entry.length bits) are zero.
uint32_t non_code_bits = 0xffffffff >> entry.length;
EXPECT_EQ(0u, entry.code & non_code_bits);
// entry.code has a code length of entry.length.
EXPECT_EQ(entry.length,
HpackHuffmanDecoderPeer::CodeLengthOfPrefix(entry.code))
<< "Full code: " << Bits(entry.code) << "\n"
<< " ID: " << entry.id;
// Let's try again with all the low order bits set to 1.
uint32_t bits = entry.code | (0xffffffff >> entry.length);
EXPECT_EQ(entry.length, HpackHuffmanDecoderPeer::CodeLengthOfPrefix(bits))
<< "Full code: " << Bits(entry.code) << "\n"
<< " bits: " << Bits(bits) << "\n"
<< " ID: " << entry.id;
// Let's try again with random low order bits.
uint32_t rand = RandUint32() & (0xffffffff >> entry.length);
bits = entry.code | rand;
EXPECT_EQ(entry.length, HpackHuffmanDecoderPeer::CodeLengthOfPrefix(bits))
<< "Full code: " << Bits(entry.code) << "\n"
<< " rand: " << Bits(rand) << "\n"
<< " bits: " << Bits(bits) << "\n"
<< " ID: " << entry.id;
// If fewer bits are available and low order bits are zero after left
// shifting (should be true), CodeLengthOfPrefix should never return
// a value that is <= the number of available bits.
for (uint8_t available = entry.length - 1; available > 0; --available) {
uint32_t mask = 0xffffffff;
uint32_t avail_mask = mask << (32 - available);
bits = entry.code & avail_mask;
EXPECT_LT(available, HpackHuffmanDecoderPeer::CodeLengthOfPrefix(bits))
<< "Full code: " << Bits(entry.code) << "\n"
<< "availMask: " << Bits(avail_mask) << "\n"
<< " bits: " << Bits(bits) << "\n"
<< " ID: " << entry.id;
}
}
}
TEST_F(HpackHuffmanDecoderTest, DecodeToSource) {
for (const HpackHuffmanSymbol& entry : HpackHuffmanCode()) {
// Check that entry.code, which has all the low order bits set to 0,
// decodes to entry.id.
EXPECT_EQ(entry.id, DecodeToSource(entry.length, entry.code))
<< " Length: " << entry.length << "\n"
<< "Full code: " << Bits(entry.code);
// Let's try again with all the low order bits set to 1.
uint32_t bits = entry.code | (0xffffffff >> entry.length);
EXPECT_EQ(entry.id, DecodeToSource(entry.length, bits))
<< " Length: " << entry.length << "\n"
<< "Full code: " << Bits(entry.code) << "\n"
<< " bits: " << Bits(bits);
// Let's try again with random low order bits.
uint32_t rand = RandUint32() & (0xffffffff >> entry.length);
bits = entry.code | rand;
EXPECT_EQ(entry.id, DecodeToSource(entry.length, bits))
<< " Length: " << entry.length << "\n"
<< "Full code: " << Bits(entry.code) << "\n"
<< " rand: " << Bits(rand) << "\n"
<< " bits: " << Bits(bits);
}
}
TEST_F(HpackHuffmanDecoderTest, SpecRequestExamples) {
std::string buffer;
std::string test_table[] = {
a2b_hex("f1e3c2e5f23a6ba0ab90f4ff"),
"www.example.com",
a2b_hex("a8eb10649cbf"),
"no-cache",
a2b_hex("25a849e95ba97d7f"),
"custom-key",
a2b_hex("25a849e95bb8e8b4bf"),
"custom-value",
};
// Round-trip each test example.
for (size_t i = 0; i != arraysize(test_table); i += 2) {
const std::string& encodedFixture(test_table[i]);
const std::string& decodedFixture(test_table[i + 1]);
HpackInputStream input_stream(encodedFixture);
EXPECT_TRUE(HpackHuffmanDecoder::DecodeString(&input_stream, &buffer));
EXPECT_EQ(decodedFixture, buffer);
buffer = EncodeString(decodedFixture);
EXPECT_EQ(encodedFixture, buffer);
}
}
TEST_F(HpackHuffmanDecoderTest, SpecResponseExamples) {
std::string buffer;
// clang-format off
std::string test_table[] = {
a2b_hex("6402"),
"302",
a2b_hex("aec3771a4b"),
"private",
a2b_hex("d07abe941054d444a8200595040b8166"
"e082a62d1bff"),
"Mon, 21 Oct 2013 20:13:21 GMT",
a2b_hex("9d29ad171863c78f0b97c8e9ae82ae43"
"d3"),
"https://www.example.com",
a2b_hex("94e7821dd7f2e6c7b335dfdfcd5b3960"
"d5af27087f3672c1ab270fb5291f9587"
"316065c003ed4ee5b1063d5007"),
"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
};
// clang-format on
// Round-trip each test example.
for (size_t i = 0; i != arraysize(test_table); i += 2) {
const std::string& encodedFixture(test_table[i]);
const std::string& decodedFixture(test_table[i + 1]);
HpackInputStream input_stream(encodedFixture);
EXPECT_TRUE(HpackHuffmanDecoder::DecodeString(&input_stream, &buffer));
EXPECT_EQ(decodedFixture, buffer);
buffer = EncodeString(decodedFixture);
EXPECT_EQ(encodedFixture, buffer);
}
}
TEST_F(HpackHuffmanDecoderTest, RoundTripIndividualSymbols) {
for (size_t i = 0; i != 256; i++) {
char c = static_cast<char>(i);
char storage[3] = {c, c, c};
SpdyStringPiece input(storage, arraysize(storage));
std::string buffer_in = EncodeString(input);
std::string buffer_out;
HpackInputStream input_stream(buffer_in);
EXPECT_TRUE(HpackHuffmanDecoder::DecodeString(&input_stream, &buffer_out));
EXPECT_EQ(input, buffer_out);
}
}
// Creates 256 input strings, each with a unique byte value i used to sandwich
// all the other higher byte values.
TEST_F(HpackHuffmanDecoderTest, RoundTripSymbolSequences) {
std::string input;
std::string encoded;
std::string decoded;
for (size_t i = 0; i != 256; i++) {
input.clear();
auto ic = static_cast<char>(i);
input.push_back(ic);
for (size_t j = i; j != 256; j++) {
input.push_back(static_cast<char>(j));
input.push_back(ic);
}
EncodeString(input, &encoded);
HpackInputStream input_stream(encoded);
EXPECT_TRUE(HpackHuffmanDecoder::DecodeString(&input_stream, &decoded));
EXPECT_EQ(input, decoded);
}
}
} // namespace test
} // namespace net