| Index: net/spdy/hpack/hpack_huffman_decoder_test.cc
|
| diff --git a/net/spdy/hpack/hpack_huffman_decoder_test.cc b/net/spdy/hpack/hpack_huffman_decoder_test.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..5b7b16cc072776fd538da9ff1a5c5984bc42e9bc
|
| --- /dev/null
|
| +++ b/net/spdy/hpack/hpack_huffman_decoder_test.cc
|
| @@ -0,0 +1,292 @@
|
| +// 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 "base/strings/string_piece.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/spdy_test_utils.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +using base::StringPiece;
|
| +
|
| +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(StringPiece 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(StringPiece 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(std::numeric_limits<uint32_t>::max(),
|
| + encodedFixture);
|
| + EXPECT_TRUE(HpackHuffmanDecoder::DecodeString(
|
| + &input_stream, decodedFixture.size(), &buffer));
|
| + EXPECT_EQ(decodedFixture, buffer);
|
| + buffer = EncodeString(decodedFixture);
|
| + EXPECT_EQ(encodedFixture, buffer);
|
| + }
|
| +}
|
| +
|
| +TEST_F(HpackHuffmanDecoderTest, TooLong) {
|
| + 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, but with too small an output buffer.
|
| + 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]);
|
| + uint32_t limit = base::RandInt(0, decodedFixture.size() - 1);
|
| + HpackInputStream strm(std::numeric_limits<uint32_t>::max(), encodedFixture);
|
| + EXPECT_FALSE(HpackHuffmanDecoder::DecodeString(&strm, limit, &buffer));
|
| +
|
| + // This is NOT a required test as it really tests an implementation detail,
|
| + // i.e. the fact that it writes the first |limit| values into |buffer|,
|
| + // then returns false leaving those chars in the buffer.
|
| + EXPECT_EQ(decodedFixture.substr(0, limit), 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(std::numeric_limits<uint32_t>::max(),
|
| + encodedFixture);
|
| + EXPECT_TRUE(HpackHuffmanDecoder::DecodeString(
|
| + &input_stream, decodedFixture.size(), &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};
|
| + StringPiece input(storage, arraysize(storage));
|
| + std::string buffer_in = EncodeString(input);
|
| + std::string buffer_out;
|
| + HpackInputStream input_stream(std::numeric_limits<uint32_t>::max(),
|
| + buffer_in);
|
| + EXPECT_TRUE(HpackHuffmanDecoder::DecodeString(&input_stream, input.size(),
|
| + &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(std::numeric_limits<uint32_t>::max(),
|
| + encoded);
|
| + EXPECT_TRUE(HpackHuffmanDecoder::DecodeString(&input_stream, input.size(),
|
| + &decoded));
|
| + EXPECT_EQ(input, decoded);
|
| + }
|
| +}
|
| +
|
| +} // namespace test
|
| +} // namespace net
|
|
|