// 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/varint/hpack_varint_decoder.h"

#include "net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"

namespace http2 {

DecodeStatus HpackVarintDecoder::Start(uint8_t prefix_value,
                                       uint8_t prefix_length,
                                       DecodeBuffer* db) {
  DCHECK_LE(3u, prefix_length);
  DCHECK_LE(prefix_length, 8u);

  // |prefix_mask| defines the sequence of low-order bits of the first byte
  // that encode the prefix of the value. It is also the marker in those bits
  // of the first byte indicating that at least one extension byte is needed.
  const uint8_t prefix_mask = (1 << prefix_length) - 1;

  // Ignore the bits that aren't a part of the prefix of the varint.
  value_ = prefix_value & prefix_mask;

  if (value_ < prefix_mask) {
    MarkDone();
    return DecodeStatus::kDecodeDone;
  }

  offset_ = 0;
  return Resume(db);
}

DecodeStatus HpackVarintDecoder::StartExtended(uint8_t prefix_length,
                                               DecodeBuffer* db) {
  DCHECK_LE(3u, prefix_length);
  DCHECK_LE(prefix_length, 8u);

  value_ = (1 << prefix_length) - 1;
  offset_ = 0;
  return Resume(db);
}

DecodeStatus HpackVarintDecoder::Resume(DecodeBuffer* db) {
  if (decode_64_bits_) {
    HTTP2_RELOADABLE_FLAG_COUNT(http2_varint_decode_64_bits);
    // There can be at most 10 continuation bytes.  Offset is zero for the
    // first one and increases by 7 for each subsequent one.
    const uint8_t kMaxOffset = 63;
    CheckNotDone();

    // Process most extension bytes without the need for overflow checking.
    while (offset_ < kMaxOffset) {
      if (db->Empty()) {
        return DecodeStatus::kDecodeInProgress;
      }

      uint8_t byte = db->DecodeUInt8();
      uint64_t summand = byte & 0x7f;

      // Shifting a 7 bit value to the left by at most 56 places can never
      // overflow on uint64_t.
      DCHECK_LE(offset_, 56);
      DCHECK_LE(summand, std::numeric_limits<uint64_t>::max() >> offset_);

      summand <<= offset_;

      // At this point,
      // |value_| is at most (2^prefix_length - 1) + (2^49 - 1), and
      // |summand| is at most 255 << 56 (which is smaller than 2^63),
      // so adding them can never overflow on uint64_t.
      DCHECK_LE(value_, std::numeric_limits<uint64_t>::max() - summand);

      value_ += summand;

      // Decoding ends if continuation flag is not set.
      if ((byte & 0x80) == 0) {
        MarkDone();
        return DecodeStatus::kDecodeDone;
      }

      offset_ += 7;
    }

    if (db->Empty()) {
      return DecodeStatus::kDecodeInProgress;
    }

    DCHECK_EQ(kMaxOffset, offset_);

    uint8_t byte = db->DecodeUInt8();
    // No more extension bytes are allowed after this.
    if ((byte & 0x80) == 0) {
      uint64_t summand = byte & 0x7f;
      // Check for overflow in left shift.
      if (summand <= std::numeric_limits<uint64_t>::max() >> offset_) {
        summand <<= offset_;
        // Check for overflow in addition.
        if (value_ <= std::numeric_limits<uint64_t>::max() - summand) {
          value_ += summand;
          MarkDone();
          return DecodeStatus::kDecodeDone;
        }
      }
    }

    // Signal error if value is too large or there are too many extension bytes.
    DLOG(WARNING) << "Variable length int encoding is too large or too long. "
                  << DebugString();
    MarkDone();
    return DecodeStatus::kDecodeError;
  }

  // Old code path.  TODO(bnc): remove.
  DCHECK(!decode_64_bits_);
  const uint8_t kMaxOffset = 28;
  CheckNotDone();
  do {
    if (db->Empty()) {
      return DecodeStatus::kDecodeInProgress;
    }
    uint8_t byte = db->DecodeUInt8();
    if (offset_ == kMaxOffset && byte != 0)
      break;
    DCHECK(offset_ <= kMaxOffset - 7 || byte == 0);
    // Shifting a 7 bit value to the left by at most 21 places can never
    // overflow on uint32_t.  Shifting 0 to the left cannot overflow either.
    value_ += (byte & 0x7f) << offset_;
    if ((byte & 0x80) == 0) {
      MarkDone();
      return DecodeStatus::kDecodeDone;
    }
    offset_ += 7;
  } while (offset_ <= kMaxOffset);
  DLOG(WARNING) << "Variable length int encoding is too large or too long. "
                << DebugString();
  MarkDone();
  return DecodeStatus::kDecodeError;
}

uint64_t HpackVarintDecoder::value() const {
  CheckDone();
  return value_;
}

void HpackVarintDecoder::set_value(uint64_t v) {
  MarkDone();
  value_ = v;
}

Http2String HpackVarintDecoder::DebugString() const {
  return Http2StrCat("HpackVarintDecoder(value=", value_, ", offset=", offset_,
                     ")");
}

DecodeStatus HpackVarintDecoder::StartForTest(uint8_t prefix_value,
                                              uint8_t prefix_length,
                                              DecodeBuffer* db) {
  return Start(prefix_value, prefix_length, db);
}

DecodeStatus HpackVarintDecoder::StartExtendedForTest(uint8_t prefix_length,
                                                      DecodeBuffer* db) {
  return StartExtended(prefix_length, db);
}

DecodeStatus HpackVarintDecoder::ResumeForTest(DecodeBuffer* db) {
  return Resume(db);
}

}  // namespace http2
