// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef CRASHPAD_UTIL_NUMERIC_CHECKED_RANGE_H_
#define CRASHPAD_UTIL_NUMERIC_CHECKED_RANGE_H_

#include <limits>

#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/numerics/safe_math.h"
#include "util/misc/implicit_cast.h"

namespace crashpad {

//! \brief Ensures that a range, composed of a base and size, does not overflow
//!     its data type.
template <typename ValueType, typename SizeType = ValueType>
class CheckedRange {
 public:
  CheckedRange(ValueType base, SizeType size) {
    static_assert(!std::numeric_limits<SizeType>::is_signed,
                  "SizeType must be unsigned");
    SetRange(base, size);
  }

  //! \brief Sets the range’s base and size to \a base and \a size,
  //!     respectively.
  void SetRange(ValueType base, SizeType size) {
    base_ = base;
    size_ = size;
  }

  //! \brief The range’s base.
  ValueType base() const { return base_; }

  //! \brief The range’s size.
  SizeType size() const { return size_; }

  //! \brief The range’s end (its base plus its size).
  ValueType end() const { return base_ + size_; }

  //! \brief Returns the validity of the range.
  //!
  //! \return `true` if the range is valid, `false` otherwise.
  //!
  //! A range is valid if its size can be converted to the range’s data type
  //! without data loss, and if its end (base plus size) can be computed without
  //! overflowing its data type.
  bool IsValid() const {
    if (!base::IsValueInRangeForNumericType<ValueType, SizeType>(size_)) {
      return false;
    }
    base::CheckedNumeric<ValueType> checked_end(base_);
    checked_end += implicit_cast<ValueType>(size_);
    return checked_end.IsValid();
  }

  //! \brief Returns whether the range contains another value.
  //!
  //! \param[in] value The (possibly) contained value.
  //!
  //! \return `true` if the range contains \a value, `false` otherwise.
  //!
  //! A range contains a value if the value is greater than or equal to its
  //! base, and less than its end (base plus size).
  //!
  //! This method must only be called if IsValid() would return `true`.
  bool ContainsValue(ValueType value) const {
    DCHECK(IsValid());

    return value >= base() && value < end();
  }

  //! \brief Returns whether the range contains another range.
  //!
  //! \param[in] that The (possibly) contained range.
  //!
  //! \return `true` if `this` range, the containing range, contains \a that,
  //!     the contained range. `false` otherwise.
  //!
  //! A range contains another range when the contained range’s base is greater
  //! than or equal to the containing range’s base, and the contained range’s
  //! end is less than or equal to the containing range’s end.
  //!
  //! This method must only be called if IsValid() would return `true` for both
  //! CheckedRange objects involved.
  bool ContainsRange(const CheckedRange<ValueType, SizeType>& that) const {
    DCHECK(IsValid());
    DCHECK(that.IsValid());

    return that.base() >= base() && that.end() <= end();
  }

  //! \brief Returns whether the range overlaps another range.
  //!
  //! \param[in] that The (possibly) overlapping range.
  //!
  //! \return `true` if `this` range, the first range, overlaps \a that,
  //!     the provided range. `false` otherwise.
  //!
  //! Ranges are considered to be closed-open [base, end) for this test. Zero
  //! length ranges are never considered to overlap another range.
  //!
  //! This method must only be called if IsValid() would return `true` for both
  //! CheckedRange objects involved.
  bool OverlapsRange(const CheckedRange<ValueType, SizeType>& that) const {
    DCHECK(IsValid());
    DCHECK(that.IsValid());

    if (size() == 0 || that.size() == 0)
      return false;

    return base() < that.end() && that.base() < end();
  }

 private:
  ValueType base_;
  SizeType size_;
};

}  // namespace crashpad

#endif  // CRASHPAD_UTIL_NUMERIC_CHECKED_RANGE_H_
