|  | // 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::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 |