// 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/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h"

#include <stddef.h>

#include <cstdint>

#include "base/logging.h"
#include "base/macros.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"

namespace http2 {
namespace {
// Converts calls from HpackStringDecoder when decoding a header name into the
// appropriate HpackEntryDecoderListener::OnName* calls.
class NameDecoderListener {
 public:
  explicit NameDecoderListener(HpackEntryDecoderListener* listener)
      : listener_(listener) {}
  bool OnStringStart(bool huffman_encoded, size_t len) {
    listener_->OnNameStart(huffman_encoded, len);
    return true;
  }
  void OnStringData(const char* data, size_t len) {
    listener_->OnNameData(data, len);
  }
  void OnStringEnd() { listener_->OnNameEnd(); }

 private:
  HpackEntryDecoderListener* listener_;
};

// Converts calls from HpackStringDecoder when decoding a header value into
// the appropriate HpackEntryDecoderListener::OnValue* calls.
class ValueDecoderListener {
 public:
  explicit ValueDecoderListener(HpackEntryDecoderListener* listener)
      : listener_(listener) {}
  bool OnStringStart(bool huffman_encoded, size_t len) {
    listener_->OnValueStart(huffman_encoded, len);
    return true;
  }
  void OnStringData(const char* data, size_t len) {
    listener_->OnValueData(data, len);
  }
  void OnStringEnd() { listener_->OnValueEnd(); }

 private:
  HpackEntryDecoderListener* listener_;
};
}  // namespace

DecodeStatus HpackEntryDecoder::Start(DecodeBuffer* db,
                                      HpackEntryDecoderListener* listener) {
  DCHECK(db != nullptr);
  DCHECK(listener != nullptr);
  DCHECK(db->HasData());
  DecodeStatus status = entry_type_decoder_.Start(db);
  switch (status) {
    case DecodeStatus::kDecodeDone:
      // The type of the entry and its varint fit into the current decode
      // buffer.
      if (entry_type_decoder_.entry_type() == HpackEntryType::kIndexedHeader) {
        // The entry consists solely of the entry type and varint.
        // This is by far the most common case in practice.
        listener->OnIndexedHeader(entry_type_decoder_.varint());
        return DecodeStatus::kDecodeDone;
      }
      state_ = EntryDecoderState::kDecodedType;
      return Resume(db, listener);
    case DecodeStatus::kDecodeInProgress:
      // Hit the end of the decode buffer before fully decoding
      // the entry type and varint.
      DCHECK_EQ(0u, db->Remaining());
      state_ = EntryDecoderState::kResumeDecodingType;
      return status;
    case DecodeStatus::kDecodeError:
      // The varint must have been invalid (too long).
      return status;
  }

  HTTP2_BUG << "Unreachable";
  return DecodeStatus::kDecodeError;
}

DecodeStatus HpackEntryDecoder::Resume(DecodeBuffer* db,
                                       HpackEntryDecoderListener* listener) {
  DCHECK(db != nullptr);
  DCHECK(listener != nullptr);

  DecodeStatus status;

  do {
    switch (state_) {
      case EntryDecoderState::kResumeDecodingType:
        // entry_type_decoder_ returned kDecodeInProgress when last called.
        DVLOG(1) << "kResumeDecodingType: db->Remaining=" << db->Remaining();
        status = entry_type_decoder_.Resume(db);
        if (status != DecodeStatus::kDecodeDone) {
          return status;
        }
        state_ = EntryDecoderState::kDecodedType;
        HTTP2_FALLTHROUGH;

      case EntryDecoderState::kDecodedType:
        // entry_type_decoder_ returned kDecodeDone, now need to decide how
        // to proceed.
        DVLOG(1) << "kDecodedType: db->Remaining=" << db->Remaining();
        if (DispatchOnType(listener)) {
          // All done.
          return DecodeStatus::kDecodeDone;
        }
        continue;

      case EntryDecoderState::kStartDecodingName:
        DVLOG(1) << "kStartDecodingName: db->Remaining=" << db->Remaining();
        {
          NameDecoderListener ncb(listener);
          status = string_decoder_.Start(db, &ncb);
        }
        if (status != DecodeStatus::kDecodeDone) {
          // On the assumption that the status is kDecodeInProgress, set
          // state_ accordingly; unnecessary if status is kDecodeError, but
          // that will only happen if the varint encoding the name's length
          // is too long.
          state_ = EntryDecoderState::kResumeDecodingName;
          return status;
        }
        state_ = EntryDecoderState::kStartDecodingValue;
        HTTP2_FALLTHROUGH;

      case EntryDecoderState::kStartDecodingValue:
        DVLOG(1) << "kStartDecodingValue: db->Remaining=" << db->Remaining();
        {
          ValueDecoderListener vcb(listener);
          status = string_decoder_.Start(db, &vcb);
        }
        if (status == DecodeStatus::kDecodeDone) {
          // Done with decoding the literal value, so we've reached the
          // end of the header entry.
          return status;
        }
        // On the assumption that the status is kDecodeInProgress, set
        // state_ accordingly; unnecessary if status is kDecodeError, but
        // that will only happen if the varint encoding the value's length
        // is too long.
        state_ = EntryDecoderState::kResumeDecodingValue;
        return status;

      case EntryDecoderState::kResumeDecodingName:
        // The literal name was split across decode buffers.
        DVLOG(1) << "kResumeDecodingName: db->Remaining=" << db->Remaining();
        {
          NameDecoderListener ncb(listener);
          status = string_decoder_.Resume(db, &ncb);
        }
        if (status != DecodeStatus::kDecodeDone) {
          // On the assumption that the status is kDecodeInProgress, set
          // state_ accordingly; unnecessary if status is kDecodeError, but
          // that will only happen if the varint encoding the name's length
          // is too long.
          state_ = EntryDecoderState::kResumeDecodingName;
          return status;
        }
        state_ = EntryDecoderState::kStartDecodingValue;
        break;

      case EntryDecoderState::kResumeDecodingValue:
        // The literal value was split across decode buffers.
        DVLOG(1) << "kResumeDecodingValue: db->Remaining=" << db->Remaining();
        {
          ValueDecoderListener vcb(listener);
          status = string_decoder_.Resume(db, &vcb);
        }
        if (status == DecodeStatus::kDecodeDone) {
          // Done with decoding the value, therefore the entry as a whole.
          return status;
        }
        // On the assumption that the status is kDecodeInProgress, set
        // state_ accordingly; unnecessary if status is kDecodeError, but
        // that will only happen if the varint encoding the value's length
        // is too long.
        state_ = EntryDecoderState::kResumeDecodingValue;
        return status;
    }
  } while (true);
}

bool HpackEntryDecoder::DispatchOnType(HpackEntryDecoderListener* listener) {
  const HpackEntryType entry_type = entry_type_decoder_.entry_type();
  const uint32_t varint = static_cast<uint32_t>(entry_type_decoder_.varint());
  switch (entry_type) {
    case HpackEntryType::kIndexedHeader:
      // The entry consists solely of the entry type and varint. See:
      // http://httpwg.org/specs/rfc7541.html#indexed.header.representation
      listener->OnIndexedHeader(varint);
      return true;

    case HpackEntryType::kIndexedLiteralHeader:
    case HpackEntryType::kUnindexedLiteralHeader:
    case HpackEntryType::kNeverIndexedLiteralHeader:
      // The entry has a literal value, and if the varint is zero also has a
      // literal name preceding the value. See:
      // http://httpwg.org/specs/rfc7541.html#literal.header.representation
      listener->OnStartLiteralHeader(entry_type, varint);
      if (varint == 0) {
        state_ = EntryDecoderState::kStartDecodingName;
      } else {
        state_ = EntryDecoderState::kStartDecodingValue;
      }
      return false;

    case HpackEntryType::kDynamicTableSizeUpdate:
      // The entry consists solely of the entry type and varint. FWIW, I've
      // never seen this type of entry in production (primarily browser
      // traffic) so if you're designing an HPACK successor someday, consider
      // dropping it or giving it a much longer prefix. See:
      // http://httpwg.org/specs/rfc7541.html#encoding.context.update
      listener->OnDynamicTableSizeUpdate(varint);
      return true;
  }

  HTTP2_BUG << "Unreachable, entry_type=" << entry_type;
  return true;
}

void HpackEntryDecoder::OutputDebugString(std::ostream& out) const {
  out << "HpackEntryDecoder(state=" << state_ << ", " << entry_type_decoder_
      << ", " << string_decoder_ << ")";
}

Http2String HpackEntryDecoder::DebugString() const {
  std::stringstream s;
  s << *this;
  return s.str();
}

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

std::ostream& operator<<(std::ostream& out,
                         HpackEntryDecoder::EntryDecoderState state) {
  typedef HpackEntryDecoder::EntryDecoderState EntryDecoderState;
  switch (state) {
    case EntryDecoderState::kResumeDecodingType:
      return out << "kResumeDecodingType";
    case EntryDecoderState::kDecodedType:
      return out << "kDecodedType";
    case EntryDecoderState::kStartDecodingName:
      return out << "kStartDecodingName";
    case EntryDecoderState::kResumeDecodingName:
      return out << "kResumeDecodingName";
    case EntryDecoderState::kStartDecodingValue:
      return out << "kStartDecodingValue";
    case EntryDecoderState::kResumeDecodingValue:
      return out << "kResumeDecodingValue";
  }
  return out << static_cast<int>(state);
}

}  // namespace http2
