text: // 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/http2/hpack/decoder/hpack_entry_collector.h"
  
  #include <sstream>
  
  #include "base/logging.h"
  #include "net/http2/hpack/decoder/hpack_string_collector.h"
  #include "net/http2/hpack/http2_hpack_constants.h"
  #include "net/http2/platform/api/http2_string_utils.h"
  #include "net/http2/tools/failure.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  using ::testing::AssertionResult;
  
  namespace net {
  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() {}
  
  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 net
  