// Copyright 2000-2016 The OpenSSL Project 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
//
//     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.

#include <openssl/bn.h>

#include <assert.h>
#include <string.h>

#include <utility>

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

#include "../../internal.h"
#include "../../mem_internal.h"


using namespace bssl;

DECLARE_OPAQUE_STRUCT(bignum_ctx, BignumCtx)

BSSL_NAMESPACE_BEGIN

class BignumCtx : public bignum_ctx {
 public:
  ~BignumCtx() {
    // All `BN_CTX_start` calls must be matched with `BN_CTX_end`, otherwise the
    // function may use more memory than expected, potentially without bound if
    // done in a loop. Assert that all `BIGNUM`s have been released.
    assert(used_ == 0 || error_);
  }

  // bignums_ is the stack of `BIGNUM`s managed by this `BN_CTX`.
  Vector<UniquePtr<BIGNUM>> bignums_;
  // stack_ is the stack of `BN_CTX_start` frames. It is the value of `used_` at
  // the time `BN_CTX_start` was called.
  Vector<size_t> stack_;
  // used_ is the number of `BIGNUM`s from `bignums_` that have been used.
  size_t used_ = 0;
  // error_ is whether any operation on this `BN_CTX` failed. All subsequent
  // operations will fail.
  bool error_ = false;
  // defer_error_ is whether an operation on this `BN_CTX` has failed, but no
  // error has been pushed to the queue yet. This is used to defer errors from
  // `BN_CTX_start` to `BN_CTX_get`.
  bool defer_error_ = false;
};

BSSL_NAMESPACE_END

BN_CTX *BN_CTX_new() { return New<BignumCtx>(); }

void BN_CTX_free(BN_CTX *ctx) {
  if (ctx != nullptr) {
    Delete(FromOpaque(ctx));
  }
}

void BN_CTX_start(BN_CTX *ctx) {
  auto *impl = FromOpaque(ctx);

  if (impl->error_) {
    // Once an operation has failed, `impl->stack` no longer matches the number
    // of `BN_CTX_end` calls to come. Do nothing.
    return;
  }

  if (!impl->stack_.Push(impl->used_)) {
    impl->error_ = true;
    // `BN_CTX_start` cannot fail, so defer the error to `BN_CTX_get`.
    impl->defer_error_ = true;
    ERR_clear_error();
  }
}

BIGNUM *BN_CTX_get(BN_CTX *ctx) {
  auto *impl = FromOpaque(ctx);

  // Once any operation has failed, they all do.
  if (impl->error_) {
    if (impl->defer_error_) {
      OPENSSL_PUT_ERROR(BN, BN_R_TOO_MANY_TEMPORARY_VARIABLES);
      impl->defer_error_ = false;
    }
    return nullptr;
  }

  if (impl->used_ == impl->bignums_.size()) {
    UniquePtr<BIGNUM> bn(BN_new());
    if (bn == nullptr || !impl->bignums_.Push(std::move(bn))) {
      OPENSSL_PUT_ERROR(BN, BN_R_TOO_MANY_TEMPORARY_VARIABLES);
      impl->error_ = true;
      return nullptr;
    }
  }

  BIGNUM *ret = impl->bignums_[impl->used_].get();
  BN_zero(ret);
  // This is bounded by `impl->bignums_.size()`, so it cannot overflow.
  impl->used_++;
  return ret;
}

void BN_CTX_end(BN_CTX *ctx) {
  auto *impl = FromOpaque(ctx);

  if (impl->error_) {
    // Once an operation has failed, `impl->stack_` no longer matches the number
    // of `BN_CTX_end` calls to come. Do nothing.
    return;
  }

  assert(!impl->stack_.empty());
  impl->used_ = impl->stack_.back();
  impl->stack_.pop_back();
}
