blob: b3addbf44cc5a4b47613e48f056c0bad699ae972 [file] [log] [blame]
// Copyright (c) 2018 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.
// A test of roundtrips through the encoder and decoder.
#include <stddef.h>
#include "absl/strings/string_view.h"
#include "quiche/http2/decoder/decode_buffer.h"
#include "quiche/http2/decoder/decode_status.h"
#include "quiche/http2/hpack/huffman/hpack_huffman_decoder.h"
#include "quiche/http2/hpack/huffman/hpack_huffman_encoder.h"
#include "quiche/http2/test_tools/random_decoder_test_base.h"
#include "quiche/common/platform/api/quiche_test.h"
#include "quiche/common/quiche_text_utils.h"
using ::testing::AssertionSuccess;
using ::testing::Combine;
using ::testing::Range;
using ::testing::Values;
namespace http2 {
namespace test {
namespace {
std::string GenAsciiNonControlSet() {
std::string s;
const char space = ' '; // First character after the control characters: 0x20
const char del = 127; // First character after the non-control characters.
for (char c = space; c < del; ++c) {
s.push_back(c);
}
return s;
}
class HpackHuffmanTranscoderTest : public RandomDecoderTest {
protected:
HpackHuffmanTranscoderTest()
: ascii_non_control_set_(GenAsciiNonControlSet()) {
// The decoder may return true, and its accumulator may be empty, at
// many boundaries while decoding, and yet the whole string hasn't
// been decoded.
stop_decode_on_done_ = false;
}
DecodeStatus StartDecoding(DecodeBuffer* b) override {
input_bytes_seen_ = 0;
output_buffer_.clear();
decoder_.Reset();
return ResumeDecoding(b);
}
DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
input_bytes_seen_ += b->Remaining();
absl::string_view sp(b->cursor(), b->Remaining());
if (decoder_.Decode(sp, &output_buffer_)) {
b->AdvanceCursor(b->Remaining());
// Successfully decoded (or buffered) the bytes in absl::string_view.
EXPECT_LE(input_bytes_seen_, input_bytes_expected_);
// Have we reached the end of the encoded string?
if (input_bytes_expected_ == input_bytes_seen_) {
if (decoder_.InputProperlyTerminated()) {
return DecodeStatus::kDecodeDone;
} else {
return DecodeStatus::kDecodeError;
}
}
return DecodeStatus::kDecodeInProgress;
}
return DecodeStatus::kDecodeError;
}
AssertionResult TranscodeAndValidateSeveralWays(
absl::string_view plain, absl::string_view expected_huffman) {
size_t encoded_size = HuffmanSize(plain);
std::string encoded;
HuffmanEncode(plain, encoded_size, &encoded);
HTTP2_VERIFY_EQ(encoded_size, encoded.size());
if (!expected_huffman.empty() || plain.empty()) {
HTTP2_VERIFY_EQ(encoded, expected_huffman);
}
input_bytes_expected_ = encoded.size();
auto validator = [plain, this]() -> AssertionResult {
HTTP2_VERIFY_EQ(output_buffer_.size(), plain.size());
HTTP2_VERIFY_EQ(output_buffer_, plain);
return AssertionSuccess();
};
DecodeBuffer db(encoded);
bool return_non_zero_on_first = false;
return DecodeAndValidateSeveralWays(&db, return_non_zero_on_first,
ValidateDoneAndEmpty(validator));
}
AssertionResult TranscodeAndValidateSeveralWays(absl::string_view plain) {
return TranscodeAndValidateSeveralWays(plain, "");
}
std::string RandomAsciiNonControlString(int length) {
return Random().RandStringWithAlphabet(length, ascii_non_control_set_);
}
std::string RandomBytes(int length) { return Random().RandString(length); }
const std::string ascii_non_control_set_;
HpackHuffmanDecoder decoder_;
std::string output_buffer_;
size_t input_bytes_seen_;
size_t input_bytes_expected_;
};
TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomAsciiNonControlString) {
for (size_t length = 0; length != 20; length++) {
const std::string s = RandomAsciiNonControlString(length);
ASSERT_TRUE(TranscodeAndValidateSeveralWays(s))
<< "Unable to decode:\n\n"
<< quiche::QuicheTextUtils::HexDump(s) << "\n\noutput_buffer_:\n"
<< quiche::QuicheTextUtils::HexDump(output_buffer_);
}
}
TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomBytes) {
for (size_t length = 0; length != 20; length++) {
const std::string s = RandomBytes(length);
ASSERT_TRUE(TranscodeAndValidateSeveralWays(s))
<< "Unable to decode:\n\n"
<< quiche::QuicheTextUtils::HexDump(s) << "\n\noutput_buffer_:\n"
<< quiche::QuicheTextUtils::HexDump(output_buffer_);
}
}
// Two parameters: decoder choice, and the character to round-trip.
class HpackHuffmanTranscoderAdjacentCharTest
: public HpackHuffmanTranscoderTest,
public testing::WithParamInterface<int> {
protected:
HpackHuffmanTranscoderAdjacentCharTest()
: c_(static_cast<char>(GetParam())) {}
const char c_;
};
INSTANTIATE_TEST_SUITE_P(HpackHuffmanTranscoderAdjacentCharTest,
HpackHuffmanTranscoderAdjacentCharTest, Range(0, 256));
// Test c_ adjacent to every other character, both before and after.
TEST_P(HpackHuffmanTranscoderAdjacentCharTest, RoundTripAdjacentChar) {
std::string s;
for (int a = 0; a < 256; ++a) {
s.push_back(static_cast<char>(a));
s.push_back(c_);
s.push_back(static_cast<char>(a));
}
ASSERT_TRUE(TranscodeAndValidateSeveralWays(s));
}
// Two parameters: character to repeat, number of repeats.
class HpackHuffmanTranscoderRepeatedCharTest
: public HpackHuffmanTranscoderTest,
public testing::WithParamInterface<std::tuple<int, int>> {
protected:
HpackHuffmanTranscoderRepeatedCharTest()
: c_(static_cast<char>(std::get<0>(GetParam()))),
length_(std::get<1>(GetParam())) {}
std::string MakeString() { return std::string(length_, c_); }
private:
const char c_;
const size_t length_;
};
INSTANTIATE_TEST_SUITE_P(HpackHuffmanTranscoderRepeatedCharTest,
HpackHuffmanTranscoderRepeatedCharTest,
Combine(Range(0, 256), Values(1, 2, 3, 4, 8, 16, 32)));
TEST_P(HpackHuffmanTranscoderRepeatedCharTest, RoundTripRepeatedChar) {
ASSERT_TRUE(TranscodeAndValidateSeveralWays(MakeString()));
}
} // namespace
} // namespace test
} // namespace http2