// 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 "quic/core/qpack/qpack_instructions.h"

#include <limits>

#include "quic/platform/api/quic_logging.h"
#include "common/platform/api/quiche_string_piece.h"

namespace quic {

namespace {

// Validate that
//  * in each instruction, the bits of |value| that are zero in |mask| are zero;
//  * every byte matches exactly one opcode.
void ValidateLangague(const QpackLanguage* language) {
#ifndef NDEBUG
  for (const auto* instruction : *language) {
    DCHECK_EQ(0, instruction->opcode.value & ~instruction->opcode.mask);
  }

  for (uint8_t byte = 0; byte < std::numeric_limits<uint8_t>::max(); ++byte) {
    size_t match_count = 0;
    for (const auto* instruction : *language) {
      if ((byte & instruction->opcode.mask) == instruction->opcode.value) {
        ++match_count;
      }
    }
    DCHECK_EQ(1u, match_count) << static_cast<int>(byte);
  }
#else
  (void)language;
#endif
}

}  // namespace

bool operator==(const QpackInstructionOpcode& a,
                const QpackInstructionOpcode& b) {
  return std::tie(a.value, a.mask) == std::tie(b.value, b.mask);
}

const QpackInstruction* InsertWithNameReferenceInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b10000000, 0b10000000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode,
                           {{QpackInstructionFieldType::kSbit, 0b01000000},
                            {QpackInstructionFieldType::kVarint, 6},
                            {QpackInstructionFieldType::kValue, 7}}};
  return instruction;
}

const QpackInstruction* InsertWithoutNameReferenceInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b01000000, 0b11000000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode,
                           {{QpackInstructionFieldType::kName, 5},
                            {QpackInstructionFieldType::kValue, 7}}};
  return instruction;
}

const QpackInstruction* DuplicateInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b00000000, 0b11100000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 5}}};
  return instruction;
}

const QpackInstruction* SetDynamicTableCapacityInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b00100000, 0b11100000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 5}}};
  return instruction;
}

const QpackLanguage* QpackEncoderStreamLanguage() {
  static const QpackLanguage* const language = new QpackLanguage{
      InsertWithNameReferenceInstruction(),
      InsertWithoutNameReferenceInstruction(), DuplicateInstruction(),
      SetDynamicTableCapacityInstruction()};
  ValidateLangague(language);
  return language;
}

const QpackInstruction* InsertCountIncrementInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b00000000, 0b11000000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 6}}};
  return instruction;
}

const QpackInstruction* HeaderAcknowledgementInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b10000000, 0b10000000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 7}}};
  return instruction;
}

const QpackInstruction* StreamCancellationInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b01000000, 0b11000000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 6}}};
  return instruction;
}

const QpackLanguage* QpackDecoderStreamLanguage() {
  static const QpackLanguage* const language = new QpackLanguage{
      InsertCountIncrementInstruction(), HeaderAcknowledgementInstruction(),
      StreamCancellationInstruction()};
  ValidateLangague(language);
  return language;
}

const QpackInstruction* QpackPrefixInstruction() {
  // This opcode matches every input.
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b00000000, 0b00000000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode,
                           {{QpackInstructionFieldType::kVarint, 8},
                            {QpackInstructionFieldType::kSbit, 0b10000000},
                            {QpackInstructionFieldType::kVarint2, 7}}};
  return instruction;
}

const QpackLanguage* QpackPrefixLanguage() {
  static const QpackLanguage* const language =
      new QpackLanguage{QpackPrefixInstruction()};
  ValidateLangague(language);
  return language;
}

const QpackInstruction* QpackIndexedHeaderFieldInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b10000000, 0b10000000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode,
                           {{QpackInstructionFieldType::kSbit, 0b01000000},
                            {QpackInstructionFieldType::kVarint, 6}}};
  return instruction;
}

const QpackInstruction* QpackIndexedHeaderFieldPostBaseInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b00010000, 0b11110000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode, {{QpackInstructionFieldType::kVarint, 4}}};
  return instruction;
}

const QpackInstruction* QpackLiteralHeaderFieldNameReferenceInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b01000000, 0b11000000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode,
                           {{QpackInstructionFieldType::kSbit, 0b00010000},
                            {QpackInstructionFieldType::kVarint, 4},
                            {QpackInstructionFieldType::kValue, 7}}};
  return instruction;
}

const QpackInstruction* QpackLiteralHeaderFieldPostBaseInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b00000000, 0b11110000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode,
                           {{QpackInstructionFieldType::kVarint, 3},
                            {QpackInstructionFieldType::kValue, 7}}};
  return instruction;
}

const QpackInstruction* QpackLiteralHeaderFieldInstruction() {
  static const QpackInstructionOpcode* const opcode =
      new QpackInstructionOpcode{0b00100000, 0b11100000};
  static const QpackInstruction* const instruction =
      new QpackInstruction{*opcode,
                           {{QpackInstructionFieldType::kName, 3},
                            {QpackInstructionFieldType::kValue, 7}}};
  return instruction;
}

const QpackLanguage* QpackRequestStreamLanguage() {
  static const QpackLanguage* const language =
      new QpackLanguage{QpackIndexedHeaderFieldInstruction(),
                        QpackIndexedHeaderFieldPostBaseInstruction(),
                        QpackLiteralHeaderFieldNameReferenceInstruction(),
                        QpackLiteralHeaderFieldPostBaseInstruction(),
                        QpackLiteralHeaderFieldInstruction()};
  ValidateLangague(language);
  return language;
}

// static
QpackInstructionWithValues QpackInstructionWithValues::InsertWithNameReference(
    bool is_static,
    uint64_t name_index,
    quiche::QuicheStringPiece value) {
  QpackInstructionWithValues instruction_with_values;
  instruction_with_values.instruction_ = InsertWithNameReferenceInstruction();
  instruction_with_values.s_bit_ = is_static;
  instruction_with_values.varint_ = name_index;
  instruction_with_values.value_ = value;

  return instruction_with_values;
}

// static
QpackInstructionWithValues
QpackInstructionWithValues::InsertWithoutNameReference(
    quiche::QuicheStringPiece name,
    quiche::QuicheStringPiece value) {
  QpackInstructionWithValues instruction_with_values;
  instruction_with_values.instruction_ =
      InsertWithoutNameReferenceInstruction();
  instruction_with_values.name_ = name;
  instruction_with_values.value_ = value;

  return instruction_with_values;
}

// static
QpackInstructionWithValues QpackInstructionWithValues::Duplicate(
    uint64_t index) {
  QpackInstructionWithValues instruction_with_values;
  instruction_with_values.instruction_ = DuplicateInstruction();
  instruction_with_values.varint_ = index;

  return instruction_with_values;
}

// static
QpackInstructionWithValues QpackInstructionWithValues::SetDynamicTableCapacity(
    uint64_t capacity) {
  QpackInstructionWithValues instruction_with_values;
  instruction_with_values.instruction_ = SetDynamicTableCapacityInstruction();
  instruction_with_values.varint_ = capacity;

  return instruction_with_values;
}

// static
QpackInstructionWithValues QpackInstructionWithValues::InsertCountIncrement(
    uint64_t increment) {
  QpackInstructionWithValues instruction_with_values;
  instruction_with_values.instruction_ = InsertCountIncrementInstruction();
  instruction_with_values.varint_ = increment;

  return instruction_with_values;
}

// static
QpackInstructionWithValues QpackInstructionWithValues::HeaderAcknowledgement(
    uint64_t stream_id) {
  QpackInstructionWithValues instruction_with_values;
  instruction_with_values.instruction_ = HeaderAcknowledgementInstruction();
  instruction_with_values.varint_ = stream_id;

  return instruction_with_values;
}

// static
QpackInstructionWithValues QpackInstructionWithValues::StreamCancellation(
    uint64_t stream_id) {
  QpackInstructionWithValues instruction_with_values;
  instruction_with_values.instruction_ = StreamCancellationInstruction();
  instruction_with_values.varint_ = stream_id;

  return instruction_with_values;
}

// static
QpackInstructionWithValues QpackInstructionWithValues::Prefix(
    uint64_t required_insert_count) {
  QpackInstructionWithValues instruction_with_values;
  instruction_with_values.instruction_ = QpackPrefixInstruction();
  instruction_with_values.varint_ = required_insert_count;
  instruction_with_values.varint2_ = 0;    // Delta Base.
  instruction_with_values.s_bit_ = false;  // Delta Base sign.

  return instruction_with_values;
}

// static
QpackInstructionWithValues QpackInstructionWithValues::IndexedHeaderField(
    bool is_static,
    uint64_t index) {
  QpackInstructionWithValues instruction_with_values;
  instruction_with_values.instruction_ = QpackIndexedHeaderFieldInstruction();
  instruction_with_values.s_bit_ = is_static;
  instruction_with_values.varint_ = index;

  return instruction_with_values;
}

// static
QpackInstructionWithValues
QpackInstructionWithValues::LiteralHeaderFieldNameReference(
    bool is_static,
    uint64_t index,
    quiche::QuicheStringPiece value) {
  QpackInstructionWithValues instruction_with_values;
  instruction_with_values.instruction_ =
      QpackLiteralHeaderFieldNameReferenceInstruction();
  instruction_with_values.s_bit_ = is_static;
  instruction_with_values.varint_ = index;
  instruction_with_values.value_ = value;

  return instruction_with_values;
}

// static
QpackInstructionWithValues QpackInstructionWithValues::LiteralHeaderField(
    quiche::QuicheStringPiece name,
    quiche::QuicheStringPiece value) {
  QpackInstructionWithValues instruction_with_values;
  instruction_with_values.instruction_ = QpackLiteralHeaderFieldInstruction();
  instruction_with_values.name_ = name;
  instruction_with_values.value_ = value;

  return instruction_with_values;
}

}  // namespace quic
