blob: 0c832d9e83aa8e7a0cb02d72f929611395d0d04e [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 "http2/hpack/varint/hpack_varint_decoder.h"
// Test HpackVarintDecoder against data encoded via HpackBlockBuilder,
// which uses HpackVarintEncoder under the hood.
#include <stddef.h>
#include <iterator>
#include <set>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "http2/hpack/tools/hpack_block_builder.h"
#include "http2/platform/api/http2_logging.h"
#include "http2/tools/random_decoder_test.h"
#include "common/platform/api/quiche_test.h"
#include "common/quiche_text_utils.h"
using ::testing::AssertionFailure;
using ::testing::AssertionSuccess;
namespace http2 {
namespace test {
namespace {
// Returns the highest value with the specified number of extension bytes
// and the specified prefix length (bits).
uint64_t HiValueOfExtensionBytes(uint32_t extension_bytes,
uint32_t prefix_length) {
return (1 << prefix_length) - 2 +
(extension_bytes == 0 ? 0 : (1LLU << (extension_bytes * 7)));
}
class HpackVarintRoundTripTest : public RandomDecoderTest {
protected:
HpackVarintRoundTripTest() : prefix_length_(0) {}
DecodeStatus StartDecoding(DecodeBuffer* b) override {
QUICHE_CHECK_LT(0u, b->Remaining());
uint8_t prefix = b->DecodeUInt8();
return decoder_.Start(prefix, prefix_length_, b);
}
DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
return decoder_.Resume(b);
}
void DecodeSeveralWays(uint64_t expected_value, uint32_t expected_offset) {
// The validator is called after each of the several times that the input
// DecodeBuffer is decoded, each with a different segmentation of the input.
// Validate that decoder_.value() matches the expected value.
Validator validator = [expected_value, this](
const DecodeBuffer& /*db*/,
DecodeStatus /*status*/) -> AssertionResult {
if (decoder_.value() != expected_value) {
return AssertionFailure()
<< "Value doesn't match expected: " << decoder_.value()
<< " != " << expected_value;
}
return AssertionSuccess();
};
// First validate that decoding is done and that we've advanced the cursor
// the expected amount.
validator = ValidateDoneAndOffset(expected_offset, validator);
// StartDecoding, above, requires the DecodeBuffer be non-empty so that it
// can call Start with the prefix byte.
bool return_non_zero_on_first = true;
DecodeBuffer b(buffer_);
EXPECT_TRUE(
DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, validator));
EXPECT_EQ(expected_value, decoder_.value());
EXPECT_EQ(expected_offset, b.Offset());
}
void EncodeNoRandom(uint64_t value, uint8_t prefix_length) {
QUICHE_DCHECK_LE(3, prefix_length);
QUICHE_DCHECK_LE(prefix_length, 8);
prefix_length_ = prefix_length;
HpackBlockBuilder bb;
bb.AppendHighBitsAndVarint(0, prefix_length_, value);
buffer_ = bb.buffer();
ASSERT_LT(0u, buffer_.size());
const uint8_t prefix_mask = (1 << prefix_length_) - 1;
ASSERT_EQ(static_cast<uint8_t>(buffer_[0]),
static_cast<uint8_t>(buffer_[0]) & prefix_mask);
}
void Encode(uint64_t value, uint8_t prefix_length) {
EncodeNoRandom(value, prefix_length);
// Add some random bits to the prefix (the first byte) above the mask.
uint8_t prefix = buffer_[0];
buffer_[0] = prefix | (Random().Rand8() << prefix_length);
const uint8_t prefix_mask = (1 << prefix_length_) - 1;
ASSERT_EQ(prefix, buffer_[0] & prefix_mask);
}
// This is really a test of HpackBlockBuilder, making sure that the input to
// HpackVarintDecoder is as expected, which also acts as confirmation that
// my thinking about the encodings being used by the tests, i.e. cover the
// range desired.
void ValidateEncoding(uint64_t value,
uint64_t minimum,
uint64_t maximum,
size_t expected_bytes) {
ASSERT_EQ(expected_bytes, buffer_.size());
if (expected_bytes > 1) {
const uint8_t prefix_mask = (1 << prefix_length_) - 1;
EXPECT_EQ(prefix_mask, buffer_[0] & prefix_mask);
size_t last = expected_bytes - 1;
for (size_t ndx = 1; ndx < last; ++ndx) {
// Before the last extension byte, we expect the high-bit set.
uint8_t byte = buffer_[ndx];
if (value == minimum) {
EXPECT_EQ(0x80, byte) << "ndx=" << ndx;
} else if (value == maximum) {
if (expected_bytes < 11) {
EXPECT_EQ(0xff, byte) << "ndx=" << ndx;
}
} else {
EXPECT_EQ(0x80, byte & 0x80) << "ndx=" << ndx;
}
}
// The last extension byte should not have the high-bit set.
uint8_t byte = buffer_[last];
if (value == minimum) {
if (expected_bytes == 2) {
EXPECT_EQ(0x00, byte);
} else {
EXPECT_EQ(0x01, byte);
}
} else if (value == maximum) {
if (expected_bytes < 11) {
EXPECT_EQ(0x7f, byte);
}
} else {
EXPECT_EQ(0x00, byte & 0x80);
}
} else {
const uint8_t prefix_mask = (1 << prefix_length_) - 1;
EXPECT_EQ(value, static_cast<uint32_t>(buffer_[0] & prefix_mask));
EXPECT_LT(value, prefix_mask);
}
}
void EncodeAndDecodeValues(const std::set<uint64_t>& values,
uint8_t prefix_length,
size_t expected_bytes) {
QUICHE_CHECK(!values.empty());
const uint64_t minimum = *values.begin();
const uint64_t maximum = *values.rbegin();
for (const uint64_t value : values) {
Encode(value, prefix_length); // Sets buffer_.
std::string msg = absl::StrCat("value=", value, " (0x", absl::Hex(value),
"), prefix_length=", prefix_length,
", expected_bytes=", expected_bytes, "\n",
quiche::QuicheTextUtils::HexDump(buffer_));
if (value == minimum) {
HTTP2_LOG(INFO) << "Checking minimum; " << msg;
} else if (value == maximum) {
HTTP2_LOG(INFO) << "Checking maximum; " << msg;
}
SCOPED_TRACE(msg);
ValidateEncoding(value, minimum, maximum, expected_bytes);
DecodeSeveralWays(value, expected_bytes);
// Append some random data to the end of buffer_ and repeat. That random
// data should be ignored.
buffer_.append(Random().RandString(1 + Random().Uniform(10)));
DecodeSeveralWays(value, expected_bytes);
// If possible, add extension bytes that don't change the value.
if (1 < expected_bytes) {
buffer_.resize(expected_bytes);
for (uint8_t total_bytes = expected_bytes + 1; total_bytes <= 6;
++total_bytes) {
// Mark the current last byte as not being the last one.
EXPECT_EQ(0x00, 0x80 & buffer_.back());
buffer_.back() |= 0x80;
buffer_.push_back('\0');
DecodeSeveralWays(value, total_bytes);
}
}
}
}
// Encode values (all or some of it) in [start, start+range). Check
// that |start| is the smallest value and |start+range-1| is the largest value
// corresponding to |expected_bytes|, except if |expected_bytes| is maximal.
void EncodeAndDecodeValuesInRange(uint64_t start,
uint64_t range,
uint8_t prefix_length,
size_t expected_bytes) {
const uint8_t prefix_mask = (1 << prefix_length) - 1;
const uint64_t beyond = start + range;
HTTP2_LOG(INFO)
<< "############################################################";
HTTP2_LOG(INFO) << "prefix_length=" << static_cast<int>(prefix_length);
HTTP2_LOG(INFO) << "prefix_mask=" << std::hex
<< static_cast<int>(prefix_mask);
HTTP2_LOG(INFO) << "start=" << start << " (" << std::hex << start << ")";
HTTP2_LOG(INFO) << "range=" << range << " (" << std::hex << range << ")";
HTTP2_LOG(INFO) << "beyond=" << beyond << " (" << std::hex << beyond << ")";
HTTP2_LOG(INFO) << "expected_bytes=" << expected_bytes;
if (expected_bytes < 11) {
// Confirm the claim that beyond requires more bytes.
Encode(beyond, prefix_length);
EXPECT_EQ(expected_bytes + 1, buffer_.size())
<< quiche::QuicheTextUtils::HexDump(buffer_);
}
std::set<uint64_t> values;
if (range < 200) {
// Select all values in the range.
for (uint64_t offset = 0; offset < range; ++offset) {
values.insert(start + offset);
}
} else {
// Select some values in this range, including the minimum and maximum
// values that require exactly |expected_bytes| extension bytes.
values.insert({start, start + 1, beyond - 2, beyond - 1});
while (values.size() < 100) {
values.insert(Random().UniformInRange(start, beyond - 1));
}
}
EncodeAndDecodeValues(values, prefix_length, expected_bytes);
}
HpackVarintDecoder decoder_;
std::string buffer_;
uint8_t prefix_length_;
};
// To help me and future debuggers of varint encodings, this HTTP2_LOGs out the
// transition points where a new extension byte is added.
TEST_F(HpackVarintRoundTripTest, Encode) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint64_t a = HiValueOfExtensionBytes(0, prefix_length);
const uint64_t b = HiValueOfExtensionBytes(1, prefix_length);
const uint64_t c = HiValueOfExtensionBytes(2, prefix_length);
const uint64_t d = HiValueOfExtensionBytes(3, prefix_length);
const uint64_t e = HiValueOfExtensionBytes(4, prefix_length);
const uint64_t f = HiValueOfExtensionBytes(5, prefix_length);
const uint64_t g = HiValueOfExtensionBytes(6, prefix_length);
const uint64_t h = HiValueOfExtensionBytes(7, prefix_length);
const uint64_t i = HiValueOfExtensionBytes(8, prefix_length);
const uint64_t j = HiValueOfExtensionBytes(9, prefix_length);
HTTP2_LOG(INFO)
<< "############################################################";
HTTP2_LOG(INFO) << "prefix_length=" << prefix_length << " a=" << a
<< " b=" << b << " c=" << c << " d=" << d
<< " e=" << e << " f=" << f << " g=" << g
<< " h=" << h << " i=" << i << " j=" << j;
std::vector<uint64_t> values = {
0, 1, // Force line break.
a - 1, a, a + 1, a + 2, a + 3, // Force line break.
b - 1, b, b + 1, b + 2, b + 3, // Force line break.
c - 1, c, c + 1, c + 2, c + 3, // Force line break.
d - 1, d, d + 1, d + 2, d + 3, // Force line break.
e - 1, e, e + 1, e + 2, e + 3, // Force line break.
f - 1, f, f + 1, f + 2, f + 3, // Force line break.
g - 1, g, g + 1, g + 2, g + 3, // Force line break.
h - 1, h, h + 1, h + 2, h + 3, // Force line break.
i - 1, i, i + 1, i + 2, i + 3, // Force line break.
j - 1, j, j + 1, j + 2, j + 3, // Force line break.
};
for (uint64_t value : values) {
EncodeNoRandom(value, prefix_length);
std::string dump = quiche::QuicheTextUtils::HexDump(buffer_);
HTTP2_LOG(INFO) << absl::StrFormat("%10llu %0#18x ", value, value)
<< quiche::QuicheTextUtils::HexDump(buffer_).substr(7);
}
}
}
TEST_F(HpackVarintRoundTripTest, FromSpec1337) {
DecodeBuffer b(absl::string_view("\x1f\x9a\x0a"));
uint32_t prefix_length = 5;
uint8_t p = b.DecodeUInt8();
EXPECT_EQ(1u, b.Offset());
EXPECT_EQ(DecodeStatus::kDecodeDone, decoder_.Start(p, prefix_length, &b));
EXPECT_EQ(3u, b.Offset());
EXPECT_EQ(1337u, decoder_.value());
EncodeNoRandom(1337, prefix_length);
EXPECT_EQ(3u, buffer_.size());
EXPECT_EQ('\x1f', buffer_[0]);
EXPECT_EQ('\x9a', buffer_[1]);
EXPECT_EQ('\x0a', buffer_[2]);
}
// Test all the values that fit into the prefix (one less than the mask).
TEST_F(HpackVarintRoundTripTest, ValidatePrefixOnly) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint8_t prefix_mask = (1 << prefix_length) - 1;
EncodeAndDecodeValuesInRange(0, prefix_mask, prefix_length, 1);
}
}
// Test all values that require exactly 1 extension byte.
TEST_F(HpackVarintRoundTripTest, ValidateOneExtensionByte) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint64_t start = HiValueOfExtensionBytes(0, prefix_length) + 1;
EncodeAndDecodeValuesInRange(start, 128, prefix_length, 2);
}
}
// Test *some* values that require exactly 2 extension bytes.
TEST_F(HpackVarintRoundTripTest, ValidateTwoExtensionBytes) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint64_t start = HiValueOfExtensionBytes(1, prefix_length) + 1;
const uint64_t range = 127 << 7;
EncodeAndDecodeValuesInRange(start, range, prefix_length, 3);
}
}
// Test *some* values that require 3 extension bytes.
TEST_F(HpackVarintRoundTripTest, ValidateThreeExtensionBytes) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint64_t start = HiValueOfExtensionBytes(2, prefix_length) + 1;
const uint64_t range = 127 << 14;
EncodeAndDecodeValuesInRange(start, range, prefix_length, 4);
}
}
// Test *some* values that require 4 extension bytes.
TEST_F(HpackVarintRoundTripTest, ValidateFourExtensionBytes) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint64_t start = HiValueOfExtensionBytes(3, prefix_length) + 1;
const uint64_t range = 127 << 21;
EncodeAndDecodeValuesInRange(start, range, prefix_length, 5);
}
}
// Test *some* values that require 5 extension bytes.
TEST_F(HpackVarintRoundTripTest, ValidateFiveExtensionBytes) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint64_t start = HiValueOfExtensionBytes(4, prefix_length) + 1;
const uint64_t range = 127llu << 28;
EncodeAndDecodeValuesInRange(start, range, prefix_length, 6);
}
}
// Test *some* values that require 6 extension bytes.
TEST_F(HpackVarintRoundTripTest, ValidateSixExtensionBytes) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint64_t start = HiValueOfExtensionBytes(5, prefix_length) + 1;
const uint64_t range = 127llu << 35;
EncodeAndDecodeValuesInRange(start, range, prefix_length, 7);
}
}
// Test *some* values that require 7 extension bytes.
TEST_F(HpackVarintRoundTripTest, ValidateSevenExtensionBytes) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint64_t start = HiValueOfExtensionBytes(6, prefix_length) + 1;
const uint64_t range = 127llu << 42;
EncodeAndDecodeValuesInRange(start, range, prefix_length, 8);
}
}
// Test *some* values that require 8 extension bytes.
TEST_F(HpackVarintRoundTripTest, ValidateEightExtensionBytes) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint64_t start = HiValueOfExtensionBytes(7, prefix_length) + 1;
const uint64_t range = 127llu << 49;
EncodeAndDecodeValuesInRange(start, range, prefix_length, 9);
}
}
// Test *some* values that require 9 extension bytes.
TEST_F(HpackVarintRoundTripTest, ValidateNineExtensionBytes) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint64_t start = HiValueOfExtensionBytes(8, prefix_length) + 1;
const uint64_t range = 127llu << 56;
EncodeAndDecodeValuesInRange(start, range, prefix_length, 10);
}
}
// Test *some* values that require 10 extension bytes.
TEST_F(HpackVarintRoundTripTest, ValidateTenExtensionBytes) {
for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
const uint64_t start = HiValueOfExtensionBytes(9, prefix_length) + 1;
const uint64_t range = std::numeric_limits<uint64_t>::max() - start;
EncodeAndDecodeValuesInRange(start, range, prefix_length, 11);
}
}
} // namespace
} // namespace test
} // namespace http2