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


struct bignum_ctx {
  ~bignum_ctx() {
    // 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|.
  bssl::Vector<bssl::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.
  bssl::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;
} /* BN_CTX */;

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

void BN_CTX_free(BN_CTX *ctx) { bssl::Delete(ctx); }

void BN_CTX_start(BN_CTX *ctx) {
  if (ctx->error_) {
    // Once an operation has failed, |ctx->stack| no longer matches the number
    // of |BN_CTX_end| calls to come. Do nothing.
    return;
  }

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

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

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

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

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

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