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

#include "quiche/quic/core/qpack/qpack_instruction_encoder.h"

#include "absl/strings/escaping.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/platform/api/quic_logging.h"
#include "quiche/quic/platform/api/quic_test.h"

namespace quic {
namespace test {

class QpackInstructionWithValuesPeer {
 public:
  static QpackInstructionWithValues CreateQpackInstructionWithValues(
      const QpackInstruction* instruction) {
    QpackInstructionWithValues instruction_with_values;
    instruction_with_values.instruction_ = instruction;
    return instruction_with_values;
  }

  static void set_s_bit(QpackInstructionWithValues* instruction_with_values,
                        bool s_bit) {
    instruction_with_values->s_bit_ = s_bit;
  }

  static void set_varint(QpackInstructionWithValues* instruction_with_values,
                         uint64_t varint) {
    instruction_with_values->varint_ = varint;
  }

  static void set_varint2(QpackInstructionWithValues* instruction_with_values,
                          uint64_t varint2) {
    instruction_with_values->varint2_ = varint2;
  }

  static void set_name(QpackInstructionWithValues* instruction_with_values,
                       absl::string_view name) {
    instruction_with_values->name_ = name;
  }

  static void set_value(QpackInstructionWithValues* instruction_with_values,
                        absl::string_view value) {
    instruction_with_values->value_ = value;
  }
};

namespace {

class QpackInstructionEncoderTest : public QuicTest {
 protected:
  QpackInstructionEncoderTest() : verified_position_(0) {}
  ~QpackInstructionEncoderTest() override = default;

  // Append encoded |instruction| to |output_|.
  void EncodeInstruction(
      const QpackInstructionWithValues& instruction_with_values) {
    encoder_.Encode(instruction_with_values, &output_);
  }

  // Compare substring appended to |output_| since last EncodedSegmentMatches()
  // call against hex-encoded argument.
  bool EncodedSegmentMatches(absl::string_view hex_encoded_expected_substring) {
    auto recently_encoded =
        absl::string_view(output_).substr(verified_position_);
    auto expected = absl::HexStringToBytes(hex_encoded_expected_substring);
    verified_position_ = output_.size();
    return recently_encoded == expected;
  }

 private:
  QpackInstructionEncoder encoder_;
  std::string output_;
  std::string::size_type verified_position_;
};

TEST_F(QpackInstructionEncoderTest, Varint) {
  const QpackInstruction instruction{QpackInstructionOpcode{0x00, 0x80},
                                     {{QpackInstructionFieldType::kVarint, 7}}};

  auto instruction_with_values =
      QpackInstructionWithValuesPeer::CreateQpackInstructionWithValues(
          &instruction);
  QpackInstructionWithValuesPeer::set_varint(&instruction_with_values, 5);
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("05"));

  QpackInstructionWithValuesPeer::set_varint(&instruction_with_values, 127);
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("7f00"));
}

TEST_F(QpackInstructionEncoderTest, SBitAndTwoVarint2) {
  const QpackInstruction instruction{
      QpackInstructionOpcode{0x80, 0xc0},
      {{QpackInstructionFieldType::kSbit, 0x20},
       {QpackInstructionFieldType::kVarint, 5},
       {QpackInstructionFieldType::kVarint2, 8}}};

  auto instruction_with_values =
      QpackInstructionWithValuesPeer::CreateQpackInstructionWithValues(
          &instruction);
  QpackInstructionWithValuesPeer::set_s_bit(&instruction_with_values, true);
  QpackInstructionWithValuesPeer::set_varint(&instruction_with_values, 5);
  QpackInstructionWithValuesPeer::set_varint2(&instruction_with_values, 200);
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("a5c8"));

  QpackInstructionWithValuesPeer::set_s_bit(&instruction_with_values, false);
  QpackInstructionWithValuesPeer::set_varint(&instruction_with_values, 31);
  QpackInstructionWithValuesPeer::set_varint2(&instruction_with_values, 356);
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("9f00ff65"));
}

TEST_F(QpackInstructionEncoderTest, SBitAndVarintAndValue) {
  const QpackInstruction instruction{QpackInstructionOpcode{0xc0, 0xc0},
                                     {{QpackInstructionFieldType::kSbit, 0x20},
                                      {QpackInstructionFieldType::kVarint, 5},
                                      {QpackInstructionFieldType::kValue, 7}}};

  auto instruction_with_values =
      QpackInstructionWithValuesPeer::CreateQpackInstructionWithValues(
          &instruction);
  QpackInstructionWithValuesPeer::set_s_bit(&instruction_with_values, true);
  QpackInstructionWithValuesPeer::set_varint(&instruction_with_values, 100);
  QpackInstructionWithValuesPeer::set_value(&instruction_with_values, "foo");
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("ff458294e7"));

  QpackInstructionWithValuesPeer::set_s_bit(&instruction_with_values, false);
  QpackInstructionWithValuesPeer::set_varint(&instruction_with_values, 3);
  QpackInstructionWithValuesPeer::set_value(&instruction_with_values, "bar");
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("c303626172"));
}

TEST_F(QpackInstructionEncoderTest, Name) {
  const QpackInstruction instruction{QpackInstructionOpcode{0xe0, 0xe0},
                                     {{QpackInstructionFieldType::kName, 4}}};

  auto instruction_with_values =
      QpackInstructionWithValuesPeer::CreateQpackInstructionWithValues(
          &instruction);
  QpackInstructionWithValuesPeer::set_name(&instruction_with_values, "");
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("e0"));

  QpackInstructionWithValuesPeer::set_name(&instruction_with_values, "foo");
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("f294e7"));

  QpackInstructionWithValuesPeer::set_name(&instruction_with_values, "bar");
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("e3626172"));
}

TEST_F(QpackInstructionEncoderTest, Value) {
  const QpackInstruction instruction{QpackInstructionOpcode{0xf0, 0xf0},
                                     {{QpackInstructionFieldType::kValue, 3}}};

  auto instruction_with_values =
      QpackInstructionWithValuesPeer::CreateQpackInstructionWithValues(
          &instruction);
  QpackInstructionWithValuesPeer::set_value(&instruction_with_values, "");
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("f0"));

  QpackInstructionWithValuesPeer::set_value(&instruction_with_values, "foo");
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("fa94e7"));

  QpackInstructionWithValuesPeer::set_value(&instruction_with_values, "bar");
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("f3626172"));
}

TEST_F(QpackInstructionEncoderTest, SBitAndNameAndValue) {
  const QpackInstruction instruction{QpackInstructionOpcode{0xf0, 0xf0},
                                     {{QpackInstructionFieldType::kSbit, 0x08},
                                      {QpackInstructionFieldType::kName, 2},
                                      {QpackInstructionFieldType::kValue, 7}}};

  auto instruction_with_values =
      QpackInstructionWithValuesPeer::CreateQpackInstructionWithValues(
          &instruction);
  QpackInstructionWithValuesPeer::set_s_bit(&instruction_with_values, false);
  QpackInstructionWithValuesPeer::set_name(&instruction_with_values, "");
  QpackInstructionWithValuesPeer::set_value(&instruction_with_values, "");
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("f000"));

  QpackInstructionWithValuesPeer::set_s_bit(&instruction_with_values, true);
  QpackInstructionWithValuesPeer::set_name(&instruction_with_values, "foo");
  QpackInstructionWithValuesPeer::set_value(&instruction_with_values, "bar");
  EncodeInstruction(instruction_with_values);
  EXPECT_TRUE(EncodedSegmentMatches("fe94e703626172"));
}

}  // namespace
}  // namespace test
}  // namespace quic
