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