| // 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 "http2/decoder/decode_buffer.h" |
| #include "http2/decoder/decode_status.h" |
| #include "http2/hpack/huffman/hpack_huffman_decoder.h" |
| #include "http2/hpack/huffman/hpack_huffman_encoder.h" |
| #include "http2/tools/random_decoder_test.h" |
| #include "common/platform/api/quiche_test.h" |
| #include "common/quiche_text_utils.h" |
| |
| using ::testing::AssertionResult; |
| 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); |
| VERIFY_EQ(encoded_size, encoded.size()); |
| if (!expected_huffman.empty() || plain.empty()) { |
| VERIFY_EQ(encoded, expected_huffman); |
| } |
| input_bytes_expected_ = encoded.size(); |
| auto validator = [plain, this]() -> AssertionResult { |
| VERIFY_EQ(output_buffer_.size(), plain.size()); |
| 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 |