// Copyright 2025 The BoringSSL Authors
//
// 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
//
//     https://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 OPENSSL_HEADER_CRYPTO_MEM_INTERNAL_H
#define OPENSSL_HEADER_CRYPTO_MEM_INTERNAL_H

#include <openssl/mem.h>

#include <algorithm>
#include <memory>
#include <type_traits>
#include <utility>

#include <openssl/err.h>
#include <openssl/span.h>

#include "internal.h"


BSSL_NAMESPACE_BEGIN

// Internal allocation-dependent functions.
//
// This header is separate from crypto/internal.h because there are some files
// which must avoid `OPENSSL_malloc`, to avoid a circular dependency, but
// need other support routines in crypto/internal.h. (See
// `_BORINGSSL_PROHIBIT_OPENSSL_MALLOC`.)


// Memory allocation.

// New behaves like `new` but uses `OPENSSL_malloc` for memory allocation. It
// returns nullptr on allocation error. It only implements single-object
// allocation and not new T[n].
//
// When called with no arguments, it performs value-initialization, not
// default-initialization. This means that, if it selects a non-user-provided
// constructor, the object will be zero-initialized. (As in any C++ type, once
// `T` gains a user-provided constructor, it is responsible for initializing
// all fields explicitly.)
//
// Note: unlike `new`, this does not support non-public constructors.
template <typename T, typename... Args>
T *New(Args &&...args) {
  void *t = OPENSSL_malloc(sizeof(T));
  if (t == nullptr) {
    return nullptr;
  }
  return new (t) T(std::forward<Args>(args)...);
}

// Delete behaves like `delete` but uses `OPENSSL_free` to release memory.
//
// Note: unlike `delete` this does not support non-public destructors.
template <typename T>
void Delete(T *t) {
  if (t != nullptr) {
    t->~T();
    OPENSSL_free(t);
  }
}

namespace internal {

// All types with kAllowUniquePtr set may be used with UniquePtr. Other types
// may be C structs which require a `BORINGSSL_MAKE_DELETER` registration. Where
// an internal type cannot be annotated (e.g. an alias of std::variant), use
// `BORINGSSL_MAKE_DELETER(T, Delete)`.
template <typename T>
struct DeleterImpl<T, std::enable_if_t<T::kAllowUniquePtr>> {
  static void Free(T *t) { Delete(t); }
};

// All types with kAllowRefCountedUniquePtr may be used with UniquePtr, which
// then will behave like std::shared_ptr.
template <typename T>
struct DeleterImpl<T, std::enable_if_t<T::kAllowRefCountedUniquePtr>> {
  static void Free(T *t) { t->DecRefInternal(); }
};

}  // namespace internal

// All types with kAllowRefCountedUniquePtr types also automatically get an
// UpRef function. Other types may be C structs which require a
// `BORINGSSL_MAKE_UP_REF` registration.
template <typename T, typename = std::enable_if_t<T::kAllowRefCountedUniquePtr>>
inline UniquePtr<T> UpRef(const T *v) {
  if (v != nullptr) {
    v->UpRefInternal();
  }
  return UniquePtr<T>(const_cast<T *>(v));
}
template <typename T, typename = std::enable_if_t<T::kAllowRefCountedUniquePtr>>
inline UniquePtr<T> UpRef(const UniquePtr<T> &ptr) {
  return UpRef(ptr.get());
}

// MakeUnique behaves like `std::make_unique` but returns nullptr on allocation
// error.
template <typename T, typename... Args>
UniquePtr<T> MakeUnique(Args &&...args) {
  return UniquePtr<T>(New<T>(std::forward<Args>(args)...));
}


// RefCounted is a common base for ref-counted types. This is an instance of the
// C++ curiously-recurring template pattern, so a type Foo must subclass
// RefCounted<Foo>. It additionally must friend RefCounted<Foo> to allow calling
// the destructor.
template <typename Derived>
class RefCounted {
 public:
  static constexpr bool kAllowRefCountedUniquePtr = true;

  RefCounted(const RefCounted &) = delete;
  RefCounted &operator=(const RefCounted &) = delete;

  // These methods are intentionally named differently from `bssl::UpRef` to
  // avoid a collision. Only the implementations of `FOO_up_ref` and `FOO_free`
  // should call these. `DecRefInternal` returns true if the object was freed
  // and false if there are still references.
  void UpRefInternal() const {
    // Safety: the following call does not mutate anything other than the atomic
    // ref-count variable.
    CRYPTO_refcount_inc(&references_);
  }
  bool DecRefInternal() {
    if (CRYPTO_refcount_dec_and_test_zero(&references_)) {
      Derived *d = static_cast<Derived *>(this);
      d->~Derived();
      OPENSSL_free(d);
      return true;
    }
    return false;
  }

 protected:
  // Ensure that only `Derived`, which must inherit from `RefCounted<Derived>`,
  // can call the constructor. This catches bugs where someone inherited from
  // the wrong base.
  class CheckSubClass {
   private:
    friend Derived;
    CheckSubClass() = default;
  };
  RefCounted(CheckSubClass) {
    static_assert(std::is_base_of_v<RefCounted, Derived>,
                  "Derived must subclass RefCounted<Derived>");
  }

  ~RefCounted() { BSSL_CHECK(references_.load() == 0); }

 private:
  mutable CRYPTO_refcount_t references_ = 1;
};


// Containers.

// Array<T> is an owning array of elements of `T`.
template <typename T>
class Array {
 public:
  using value_type = std::remove_cv_t<T>;

  // Array's default constructor creates an empty array.
  Array() {}
  Array(const Array &) = delete;
  Array(Array &&other) { *this = std::move(other); }

  ~Array() { Reset(); }

  Array &operator=(const Array &) = delete;
  Array &operator=(Array &&other) {
    Reset();
    other.Release(&data_, &size_);
    return *this;
  }

  const T *data() const { return data_; }
  T *data() { return data_; }
  size_t size() const { return size_; }
  bool empty() const { return size_ == 0; }

  const T &operator[](size_t i) const {
    BSSL_CHECK(i < size_);
    return data_[i];
  }
  T &operator[](size_t i) {
    BSSL_CHECK(i < size_);
    return data_[i];
  }

  T &front() {
    BSSL_CHECK(size_ != 0);
    return data_[0];
  }
  const T &front() const {
    BSSL_CHECK(size_ != 0);
    return data_[0];
  }
  T &back() {
    BSSL_CHECK(size_ != 0);
    return data_[size_ - 1];
  }
  const T &back() const {
    BSSL_CHECK(size_ != 0);
    return data_[size_ - 1];
  }

  T *begin() { return data_; }
  const T *begin() const { return data_; }
  T *end() { return data_ + size_; }
  const T *end() const { return data_ + size_; }

  void Reset() { Reset(nullptr, 0); }

  // Reset releases the current contents of the array and takes ownership of the
  // raw pointer supplied by the caller.
  void Reset(T *new_data, size_t new_size) {
    std::destroy_n(data_, size_);
    OPENSSL_free(data_);
    data_ = new_data;
    size_ = new_size;
  }

  // Release releases ownership of the array to a raw pointer supplied by the
  // caller.
  void Release(T **out, size_t *out_size) {
    *out = data_;
    *out_size = size_;
    data_ = nullptr;
    size_ = 0;
  }

  // Init replaces the array with a newly-allocated array of `new_size`
  // value-constructed copies of `T`. It returns true on success and false on
  // error. If `T` is a primitive type like `uint8_t`, value-construction means
  // it will be zero-initialized.
  [[nodiscard]] bool Init(size_t new_size) {
    if (!InitUninitialized(new_size)) {
      return false;
    }
    std::uninitialized_value_construct_n(data_, size_);
    return true;
  }

  // InitForOverwrite behaves like `Init` but it default-constructs each element
  // instead. This means that, if `T` is a primitive type, the array will be
  // uninitialized and thus must be filled in by the caller.
  [[nodiscard]] bool InitForOverwrite(size_t new_size) {
    if (!InitUninitialized(new_size)) {
      return false;
    }
    std::uninitialized_default_construct_n(data_, size_);
    return true;
  }

  // CopyFrom replaces the array with a newly-allocated copy of `in`. It returns
  // true on success and false on error.
  //
  // `in` may not alias `this`.
  [[nodiscard]] bool CopyFrom(Span<const T> in) {
    BSSL_CHECK(!spans_alias(MakeConstSpan(*this), in));
    if (!InitUninitialized(in.size())) {
      return false;
    }
    std::uninitialized_copy(in.begin(), in.end(), data_);
    return true;
  }

  // Shrink shrinks the stored size of the array to `new_size`. It crashes if
  // the new size is larger. Note this does not shrink the allocation itself.
  void Shrink(size_t new_size) {
    if (new_size > size_) {
      abort();
    }
    std::destroy_n(data_ + new_size, size_ - new_size);
    size_ = new_size;
  }

 private:
  // InitUninitialized replaces the array with a newly-allocated array of
  // `new_size` elements, but whose constructor has not yet run. On success, the
  // elements must be constructed before returning control to the caller.
  bool InitUninitialized(size_t new_size) {
    Reset();
    if (new_size == 0) {
      return true;
    }

    if (new_size > SIZE_MAX / sizeof(T)) {
      OPENSSL_PUT_ERROR(CRYPTO, ERR_R_OVERFLOW);
      return false;
    }
    data_ = reinterpret_cast<T *>(OPENSSL_malloc(new_size * sizeof(T)));
    if (data_ == nullptr) {
      return false;
    }
    size_ = new_size;
    return true;
  }

  T *data_ = nullptr;
  size_t size_ = 0;
};

// Vector<T> is a resizable array of elements of `T`.
template <typename T>
class Vector {
 public:
  Vector() = default;
  Vector(const Vector &) = delete;
  Vector(Vector &&other) { *this = std::move(other); }
  ~Vector() { clear(); }

  Vector &operator=(const Vector &) = delete;
  Vector &operator=(Vector &&other) {
    clear();
    std::swap(data_, other.data_);
    std::swap(size_, other.size_);
    std::swap(capacity_, other.capacity_);
    return *this;
  }

  const T *data() const { return data_; }
  T *data() { return data_; }
  size_t size() const { return size_; }
  bool empty() const { return size_ == 0; }

  const T &operator[](size_t i) const {
    BSSL_CHECK(i < size_);
    return data_[i];
  }
  T &operator[](size_t i) {
    BSSL_CHECK(i < size_);
    return data_[i];
  }

  T &front() {
    BSSL_CHECK(size_ != 0);
    return data_[0];
  }
  const T &front() const {
    BSSL_CHECK(size_ != 0);
    return data_[0];
  }
  T &back() {
    BSSL_CHECK(size_ != 0);
    return data_[size_ - 1];
  }
  const T &back() const {
    BSSL_CHECK(size_ != 0);
    return data_[size_ - 1];
  }

  T *begin() { return data_; }
  const T *begin() const { return data_; }
  T *end() { return data_ + size_; }
  const T *end() const { return data_ + size_; }

  void clear() {
    std::destroy_n(data_, size_);
    OPENSSL_free(data_);
    data_ = nullptr;
    size_ = 0;
    capacity_ = 0;
  }

  void pop_back() {
    BSSL_CHECK(size_ != 0);
    std::destroy_at(&data_[size_ - 1]);
    size_--;
  }

  // Push adds `elem` at the end of the internal array, growing if necessary. It
  // returns false when allocation fails.
  [[nodiscard]] bool Push(T elem) {
    if (!MaybeGrow(1)) {
      return false;
    }
    new (&data_[size_]) T(std::move(elem));
    size_++;
    return true;
  }

  // CopyFrom replaces the contents of the array with a copy of `in`. It returns
  // true on success and false on allocation error.
  [[nodiscard]] bool CopyFrom(Span<const T> in) {
    Array<T> copy;
    if (!copy.CopyFrom(in)) {
      return false;
    }

    clear();
    copy.Release(&data_, &size_);
    capacity_ = size_;
    return true;
  }

  // Append appends the contents of `in` to the array. It returns true on
  // success and false on allocation error.
  [[nodiscard]] bool Append(Span<const T> in) {
    if (!MaybeGrow(in.size())) {
      return false;
    }
    std::uninitialized_copy(in.begin(), in.end(), data_ + size_);
    size_ += in.size();
    return true;
  }

  // AppendMove moves the contents of `in` and appends them to the array. It
  // returns true on success and false on allocation error.
  [[nodiscard]] bool AppendMove(Span<T> in) {
    if (!MaybeGrow(in.size())) {
      return false;
    }
    std::uninitialized_move(in.begin(), in.end(), data_ + size_);
    size_ += in.size();
    return true;
  }

  // EraseIf removes all elements that satisfy the predicate `pred`.
  template <typename Pred>
  void EraseIf(Pred pred) {
    auto it = std::remove_if(begin(), end(), pred);
    std::destroy(it, end());
    size_ = it - begin();
  }

 private:
  // If there is no room for `num` elements, creates a new backing array with
  // double the size of the old one and copies elements over.
  [[nodiscard]] bool MaybeGrow(size_t num) {
    constexpr size_t kDefaultSize = 16;
    constexpr size_t kMaxCapacity = SIZE_MAX / sizeof(T);
    if (num > kMaxCapacity - size_) {
      OPENSSL_PUT_ERROR(CRYPTO, ERR_R_OVERFLOW);
      return false;
    }
    size_t new_capacity = size_ + num;
    // No need to grow if we have room.
    if (capacity_ >= new_capacity) {
      return true;
    }
    // Always grow to at least kDefaultSize to avoid several small mallocs at
    // the start.
    new_capacity = std::max(new_capacity, std::min(kDefaultSize, kMaxCapacity));
    // At least double the old capacity for linear amortized behavior.
    if (capacity_ <= kMaxCapacity / 2) {
      new_capacity = std::max(new_capacity, capacity_ * 2);
    }
    T *new_data =
        reinterpret_cast<T *>(OPENSSL_malloc(new_capacity * sizeof(T)));
    if (new_data == nullptr) {
      return false;
    }
    size_t new_size = size_;
    std::uninitialized_move(begin(), end(), new_data);
    clear();
    data_ = new_data;
    size_ = new_size;
    capacity_ = new_capacity;
    return true;
  }

  // data_ is a pointer to `capacity_` objects of size `T`, the first `size_` of
  // which are constructed.
  T *data_ = nullptr;
  // `size_` is the number of elements stored in this Vector.
  size_t size_ = 0;
  // `capacity_` is the number of elements allocated in this Vector.
  size_t capacity_ = 0;
};

// A PackedSize is an integer that can store values from 0 to N, represented as
// a minimal-width integer.
template <size_t N>
using PackedSize = std::conditional_t<
    N <= 0xff, uint8_t,
    std::conditional_t<N <= 0xffff, uint16_t,
                       std::conditional_t<N <= 0xffffffff, uint32_t, size_t>>>;

// An InplaceVector is like a Vector, but stores up to N elements inline in the
// object. It is inspired by std::inplace_vector in C++26.
template <typename T, size_t N>
class InplaceVector {
 public:
  using value_type = std::remove_cv_t<T>;

  InplaceVector() = default;
  InplaceVector(const InplaceVector &other) { *this = other; }
  InplaceVector(InplaceVector &&other) { *this = std::move(other); }
  ~InplaceVector() { clear(); }
  InplaceVector &operator=(const InplaceVector &other) {
    if (this != &other) {
      CopyFrom(other);
    }
    return *this;
  }
  InplaceVector &operator=(InplaceVector &&other) {
    clear();
    std::uninitialized_move(other.begin(), other.end(), data());
    size_ = other.size();
    return *this;
  }

  const T *data() const { return reinterpret_cast<const T *>(storage_); }
  T *data() { return reinterpret_cast<T *>(storage_); }
  size_t size() const { return size_; }
  static constexpr size_t capacity() { return N; }
  bool empty() const { return size_ == 0; }

  const T &operator[](size_t i) const {
    BSSL_CHECK(i < size_);
    return data()[i];
  }
  T &operator[](size_t i) {
    BSSL_CHECK(i < size_);
    return data()[i];
  }

  T &front() {
    BSSL_CHECK(size_ != 0);
    return data()[0];
  }
  const T &front() const {
    BSSL_CHECK(size_ != 0);
    return data()[0];
  }
  T &back() {
    BSSL_CHECK(size_ != 0);
    return data()[size_ - 1];
  }
  const T &back() const {
    BSSL_CHECK(size_ != 0);
    return data()[size_ - 1];
  }

  T *begin() { return data(); }
  const T *begin() const { return data(); }
  T *end() { return data() + size_; }
  const T *end() const { return data() + size_; }

  void clear() { Shrink(0); }

  void pop_back() {
    BSSL_CHECK(size_ != 0);
    Shrink(size_ - 1);
  }

  // Shrink resizes the vector to `new_size`, which must not be larger than the
  // current size. Unlike `Resize`, this can be called when `T` is not
  // default-constructible.
  void Shrink(size_t new_size) {
    BSSL_CHECK(new_size <= size_);
    std::destroy_n(data() + new_size, size_ - new_size);
    size_ = static_cast<PackedSize<N>>(new_size);
  }

  // TryResize resizes the vector to `new_size` and returns true, or returns
  // false if `new_size` is too large. Any newly-added elements are
  // value-initialized.
  [[nodiscard]] bool TryResize(size_t new_size) {
    if (new_size <= size_) {
      Shrink(new_size);
      return true;
    }
    if (new_size > capacity()) {
      return false;
    }
    std::uninitialized_value_construct_n(data() + size_, new_size - size_);
    size_ = static_cast<PackedSize<N>>(new_size);
    return true;
  }

  // TryResizeForOverwrite behaves like `TryResize`, but newly-added elements
  // are default-initialized, so POD types may contain uninitialized values that
  // the caller is responsible for filling in.
  [[nodiscard]] bool TryResizeForOverwrite(size_t new_size) {
    if (new_size <= size_) {
      Shrink(new_size);
      return true;
    }
    if (new_size > capacity()) {
      return false;
    }
    std::uninitialized_default_construct_n(data() + size_, new_size - size_);
    size_ = static_cast<PackedSize<N>>(new_size);
    return true;
  }

  // TryCopyFrom sets the vector to a copy of `in` and returns true, or returns
  // false if `in` is too large.
  //
  // `in` may not alias `this`.
  [[nodiscard]] bool TryCopyFrom(Span<const T> in) {
    BSSL_CHECK(!spans_alias(MakeConstSpan(*this), in));
    if (in.size() > capacity()) {
      return false;
    }
    clear();
    std::uninitialized_copy(in.begin(), in.end(), data());
    size_ = in.size();
    return true;
  }

  // TryAppend appends the vector by a copy of `in` and returns true, or
  // returns false if `in` is too large.
  [[nodiscard]] bool TryAppend(Span<const T> in) {
    if (in.size() > capacity() - size()) {
      return false;
    }
    std::uninitialized_copy(in.begin(), in.end(), &data()[size_]);
    size_ += in.size();
    return true;
  }

  // TryPushBack appends `val` to the vector and returns a pointer to the
  // newly-inserted value, or nullptr if the vector is at capacity.
  [[nodiscard]] T *TryPushBack(T val) {
    if (size() >= capacity()) {
      return nullptr;
    }
    T *ret = &data()[size_];
    new (ret) T(std::move(val));
    size_++;
    return ret;
  }

  // The following methods behave like their `Try*` counterparts, but abort the
  // program on failure.
  void Resize(size_t size) { BSSL_CHECK(TryResize(size)); }
  void ResizeForOverwrite(size_t size) {
    BSSL_CHECK(TryResizeForOverwrite(size));
  }
  void CopyFrom(Span<const T> in) { BSSL_CHECK(TryCopyFrom(in)); }
  void Append(Span<const T> in) { BSSL_CHECK(TryAppend(in)); }
  T &PushBack(T val) {
    T *ret = TryPushBack(std::move(val));
    BSSL_CHECK(ret != nullptr);
    return *ret;
  }

  // EraseIf removes all elements that satisfy the predicate `pred`.
  template <typename Pred>
  void EraseIf(Pred pred) {
    auto it = std::remove_if(begin(), end(), pred);
    Shrink(it - begin());
  }

 private:
  alignas(T) char storage_[sizeof(T[N])];
  PackedSize<N> size_ = 0;
};


BSSL_NAMESPACE_END

#endif  // OPENSSL_HEADER_CRYPTO_MEM_INTERNAL_H
