// Copyright 2016 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 "net/third_party/http2/hpack/decoder/hpack_entry_collector.h"

#include <sstream>

#include "base/logging.h"
#include "net/third_party/http2/hpack/decoder/hpack_string_collector.h"
#include "net/third_party/http2/hpack/http2_hpack_constants.h"
#include "net/third_party/http2/platform/api/http2_string_utils.h"
#include "net/third_party/http2/tools/failure.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::AssertionResult;

namespace http2 {
namespace test {
namespace {

const HpackEntryType kInvalidHeaderType = static_cast<HpackEntryType>(99);
const size_t kInvalidIndex = 99999999;

}  // namespace

HpackEntryCollector::HpackEntryCollector() {
  Clear();
}

HpackEntryCollector::HpackEntryCollector(const HpackEntryCollector& other)
    : header_type_(other.header_type_),
      index_(other.index_),
      name_(other.name_),
      value_(other.value_),
      started_(other.started_),
      ended_(other.ended_) {}

HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
                                         size_t index_or_size)
    : header_type_(type), index_(index_or_size), started_(true), ended_(true) {}
HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
                                         size_t index,
                                         bool value_huffman,
                                         const Http2String& value)
    : header_type_(type),
      index_(index),
      value_(value, value_huffman),
      started_(true),
      ended_(true) {}
HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
                                         bool name_huffman,
                                         const Http2String& name,
                                         bool value_huffman,
                                         const Http2String& value)
    : header_type_(type),
      index_(0),
      name_(name, name_huffman),
      value_(value, value_huffman),
      started_(true),
      ended_(true) {}

HpackEntryCollector::~HpackEntryCollector() = default;

void HpackEntryCollector::OnIndexedHeader(size_t index) {
  ASSERT_FALSE(started_);
  ASSERT_TRUE(IsClear()) << ToString();
  Init(HpackEntryType::kIndexedHeader, index);
  ended_ = true;
}
void HpackEntryCollector::OnStartLiteralHeader(HpackEntryType header_type,
                                               size_t maybe_name_index) {
  ASSERT_FALSE(started_);
  ASSERT_TRUE(IsClear()) << ToString();
  Init(header_type, maybe_name_index);
}
void HpackEntryCollector::OnNameStart(bool huffman_encoded, size_t len) {
  ASSERT_TRUE(started_);
  ASSERT_FALSE(ended_);
  ASSERT_FALSE(IsClear());
  ASSERT_TRUE(LiteralNameExpected()) << ToString();
  name_.OnStringStart(huffman_encoded, len);
}
void HpackEntryCollector::OnNameData(const char* data, size_t len) {
  ASSERT_TRUE(started_);
  ASSERT_FALSE(ended_);
  ASSERT_TRUE(LiteralNameExpected()) << ToString();
  ASSERT_TRUE(name_.IsInProgress());
  name_.OnStringData(data, len);
}
void HpackEntryCollector::OnNameEnd() {
  ASSERT_TRUE(started_);
  ASSERT_FALSE(ended_);
  ASSERT_TRUE(LiteralNameExpected()) << ToString();
  ASSERT_TRUE(name_.IsInProgress());
  name_.OnStringEnd();
}
void HpackEntryCollector::OnValueStart(bool huffman_encoded, size_t len) {
  ASSERT_TRUE(started_);
  ASSERT_FALSE(ended_);
  if (LiteralNameExpected()) {
    ASSERT_TRUE(name_.HasEnded());
  }
  ASSERT_TRUE(LiteralValueExpected()) << ToString();
  ASSERT_TRUE(value_.IsClear()) << value_.ToString();
  value_.OnStringStart(huffman_encoded, len);
}
void HpackEntryCollector::OnValueData(const char* data, size_t len) {
  ASSERT_TRUE(started_);
  ASSERT_FALSE(ended_);
  ASSERT_TRUE(LiteralValueExpected()) << ToString();
  ASSERT_TRUE(value_.IsInProgress());
  value_.OnStringData(data, len);
}
void HpackEntryCollector::OnValueEnd() {
  ASSERT_TRUE(started_);
  ASSERT_FALSE(ended_);
  ASSERT_TRUE(LiteralValueExpected()) << ToString();
  ASSERT_TRUE(value_.IsInProgress());
  value_.OnStringEnd();
  ended_ = true;
}
void HpackEntryCollector::OnDynamicTableSizeUpdate(size_t size) {
  ASSERT_FALSE(started_);
  ASSERT_TRUE(IsClear()) << ToString();
  Init(HpackEntryType::kDynamicTableSizeUpdate, size);
  ended_ = true;
}

void HpackEntryCollector::Clear() {
  header_type_ = kInvalidHeaderType;
  index_ = kInvalidIndex;
  name_.Clear();
  value_.Clear();
  started_ = ended_ = false;
}
bool HpackEntryCollector::IsClear() const {
  return header_type_ == kInvalidHeaderType && index_ == kInvalidIndex &&
         name_.IsClear() && value_.IsClear() && !started_ && !ended_;
}
bool HpackEntryCollector::IsComplete() const {
  return started_ && ended_;
}
bool HpackEntryCollector::LiteralNameExpected() const {
  switch (header_type_) {
    case HpackEntryType::kIndexedLiteralHeader:
    case HpackEntryType::kUnindexedLiteralHeader:
    case HpackEntryType::kNeverIndexedLiteralHeader:
      return index_ == 0;
    default:
      return false;
  }
}
bool HpackEntryCollector::LiteralValueExpected() const {
  switch (header_type_) {
    case HpackEntryType::kIndexedLiteralHeader:
    case HpackEntryType::kUnindexedLiteralHeader:
    case HpackEntryType::kNeverIndexedLiteralHeader:
      return true;
    default:
      return false;
  }
}
AssertionResult HpackEntryCollector::ValidateIndexedHeader(
    size_t expected_index) const {
  VERIFY_TRUE(started_);
  VERIFY_TRUE(ended_);
  VERIFY_EQ(HpackEntryType::kIndexedHeader, header_type_);
  VERIFY_EQ(expected_index, index_);
  return ::testing::AssertionSuccess();
}
AssertionResult HpackEntryCollector::ValidateLiteralValueHeader(
    HpackEntryType expected_type,
    size_t expected_index,
    bool expected_value_huffman,
    Http2StringPiece expected_value) const {
  VERIFY_TRUE(started_);
  VERIFY_TRUE(ended_);
  VERIFY_EQ(expected_type, header_type_);
  VERIFY_NE(0u, expected_index);
  VERIFY_EQ(expected_index, index_);
  VERIFY_TRUE(name_.IsClear());
  VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman));
  return ::testing::AssertionSuccess();
}
AssertionResult HpackEntryCollector::ValidateLiteralNameValueHeader(
    HpackEntryType expected_type,
    bool expected_name_huffman,
    Http2StringPiece expected_name,
    bool expected_value_huffman,
    Http2StringPiece expected_value) const {
  VERIFY_TRUE(started_);
  VERIFY_TRUE(ended_);
  VERIFY_EQ(expected_type, header_type_);
  VERIFY_EQ(0u, index_);
  VERIFY_SUCCESS(name_.Collected(expected_name, expected_name_huffman));
  VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman));
  return ::testing::AssertionSuccess();
}
AssertionResult HpackEntryCollector::ValidateDynamicTableSizeUpdate(
    size_t size) const {
  VERIFY_TRUE(started_);
  VERIFY_TRUE(ended_);
  VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, header_type_);
  VERIFY_EQ(index_, size);
  return ::testing::AssertionSuccess();
}

void HpackEntryCollector::AppendToHpackBlockBuilder(
    HpackBlockBuilder* hbb) const {
  ASSERT_TRUE(started_ && ended_) << *this;
  switch (header_type_) {
    case HpackEntryType::kIndexedHeader:
      hbb->AppendIndexedHeader(index_);
      return;

    case HpackEntryType::kDynamicTableSizeUpdate:
      hbb->AppendDynamicTableSizeUpdate(index_);
      return;

    case HpackEntryType::kIndexedLiteralHeader:
    case HpackEntryType::kUnindexedLiteralHeader:
    case HpackEntryType::kNeverIndexedLiteralHeader:
      ASSERT_TRUE(value_.HasEnded()) << *this;
      if (index_ != 0) {
        CHECK(name_.IsClear());
        hbb->AppendNameIndexAndLiteralValue(header_type_, index_,
                                            value_.huffman_encoded, value_.s);
      } else {
        CHECK(name_.HasEnded()) << *this;
        hbb->AppendLiteralNameAndValue(header_type_, name_.huffman_encoded,
                                       name_.s, value_.huffman_encoded,
                                       value_.s);
      }
      return;

    default:
      ADD_FAILURE() << *this;
  }
}

Http2String HpackEntryCollector::ToString() const {
  Http2String result("Type=");
  switch (header_type_) {
    case HpackEntryType::kIndexedHeader:
      result += "IndexedHeader";
      break;
    case HpackEntryType::kDynamicTableSizeUpdate:
      result += "DynamicTableSizeUpdate";
      break;
    case HpackEntryType::kIndexedLiteralHeader:
      result += "IndexedLiteralHeader";
      break;
    case HpackEntryType::kUnindexedLiteralHeader:
      result += "UnindexedLiteralHeader";
      break;
    case HpackEntryType::kNeverIndexedLiteralHeader:
      result += "NeverIndexedLiteralHeader";
      break;
    default:
      if (header_type_ == kInvalidHeaderType) {
        result += "<unset>";
      } else {
        Http2StrAppend(&result, header_type_);
      }
  }
  if (index_ != 0) {
    Http2StrAppend(&result, " Index=", index_);
  }
  if (!name_.IsClear()) {
    Http2StrAppend(&result, " Name", name_.ToString());
  }
  if (!value_.IsClear()) {
    Http2StrAppend(&result, " Value", value_.ToString());
  }
  if (!started_) {
    EXPECT_FALSE(ended_);
    Http2StrAppend(&result, " !started");
  } else if (!ended_) {
    Http2StrAppend(&result, " !ended");
  } else {
    Http2StrAppend(&result, " Complete");
  }
  return result;
}

void HpackEntryCollector::Init(HpackEntryType type, size_t maybe_index) {
  ASSERT_TRUE(IsClear()) << ToString();
  header_type_ = type;
  index_ = maybe_index;
  started_ = true;
}

bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b) {
  return a.name() == b.name() && a.value() == b.value() &&
         a.index() == b.index() && a.header_type() == b.header_type() &&
         a.started() == b.started() && a.ended() == b.ended();
}
bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b) {
  return !(a == b);
}

std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v) {
  return out << v.ToString();
}

}  // namespace test
}  // namespace http2
