// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_BIN_SECURE_SOCKET_UTILS_H_
#define RUNTIME_BIN_SECURE_SOCKET_UTILS_H_

#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>

#include "platform/globals.h"

#include "bin/dartutils.h"
#include "platform/text_buffer.h"

namespace dart {
namespace bin {

const bool SSL_LOG_STATUS = false;
const bool SSL_LOG_DATA = false;
const bool SSL_LOG_CERTS = false;

class SecureSocketUtils : public AllStatic {
 public:
  static constexpr int SSL_ERROR_MESSAGE_BUFFER_SIZE = 1000;

  static void ThrowIOException(int status,
                               const char* exception_type,
                               const char* message,
                               const SSL* ssl);

  static void CheckStatusSSL(int status,
                             const char* type,
                             const char* message,
                             const SSL* ssl);

  static void CheckStatus(int status, const char* type, const char* message);

  static bool IsCurrentTimeInsideCertValidDateRange(X509* root_cert);

  static bool NoPEMStartLine() {
    uint32_t last_error = ERR_peek_last_error();
    return (ERR_GET_LIB(last_error) == ERR_LIB_PEM) &&
           (ERR_GET_REASON(last_error) == PEM_R_NO_START_LINE);
  }

  static uint32_t FetchErrorString(const SSL* ssl, TextBuffer* text_buffer);
};

// Where the argument to the constructor is the handle for an object
// implementing List<int>, this class creates a scope in which a memory-backed
// BIO is allocated. Leaving the scope cleans up the BIO and the buffer that
// was used to create it.
//
// Do not make Dart_ API calls while in a ScopedMemBIO.
// Do not call Dart_PropagateError while in a ScopedMemBIO.
class ScopedMemBIO {
 public:
  explicit ScopedMemBIO(Dart_Handle object) {
    if (!Dart_IsTypedData(object) && !Dart_IsList(object)) {
      Dart_ThrowException(
          DartUtils::NewDartArgumentError("Argument is not a List<int>"));
    }

    uint8_t* bytes = nullptr;
    intptr_t bytes_len = 0;
    bool is_typed_data = false;
    if (Dart_IsTypedData(object)) {
      is_typed_data = true;
      Dart_TypedData_Type typ;
      ThrowIfError(Dart_TypedDataAcquireData(
          object, &typ, reinterpret_cast<void**>(&bytes), &bytes_len));
    } else {
      ASSERT(Dart_IsList(object));
      ThrowIfError(Dart_ListLength(object, &bytes_len));
      bytes = Dart_ScopeAllocate(bytes_len);
      ASSERT(bytes != nullptr);
      ThrowIfError(Dart_ListGetAsBytes(object, 0, bytes, bytes_len));
    }

    object_ = object;
    bytes_ = bytes;
    bytes_len_ = bytes_len;
    bio_ = BIO_new_mem_buf(bytes, bytes_len);
    ASSERT(bio_ != nullptr);
    is_typed_data_ = is_typed_data;
  }

  ~ScopedMemBIO() {
    ASSERT(bio_ != nullptr);
    if (is_typed_data_) {
      BIO_free(bio_);
      ThrowIfError(Dart_TypedDataReleaseData(object_));
    } else {
      BIO_free(bio_);
    }
  }

  BIO* bio() {
    ASSERT(bio_ != nullptr);
    return bio_;
  }

  uint8_t* data() { return bytes_; }
  intptr_t length() { return bytes_len_; }

 private:
  Dart_Handle object_;
  uint8_t* bytes_;
  intptr_t bytes_len_;
  BIO* bio_;
  bool is_typed_data_;

  DISALLOW_ALLOCATION();
  DISALLOW_COPY_AND_ASSIGN(ScopedMemBIO);
};

template <typename T, void (*free_func)(T*)>
class ScopedSSLType {
 public:
  explicit ScopedSSLType(T* obj) : obj_(obj) {}

  ~ScopedSSLType() {
    if (obj_ != nullptr) {
      free_func(obj_);
    }
  }

  T* get() { return obj_; }
  const T* get() const { return obj_; }

  T* release() {
    T* result = obj_;
    obj_ = nullptr;
    return result;
  }

 private:
  T* obj_;

  DISALLOW_ALLOCATION();
  DISALLOW_COPY_AND_ASSIGN(ScopedSSLType);
};

template <typename T, typename E, void (*func)(E*)>
class ScopedSSLStackType {
 public:
  explicit ScopedSSLStackType(T* obj) : obj_(obj) {}

  ~ScopedSSLStackType() {
    if (obj_ != nullptr) {
      OPENSSL_sk_pop_free_ex(reinterpret_cast<OPENSSL_STACK*>(obj_),
                             call_free_func, free_func);
    }
  }

  T* get() { return obj_; }
  const T* get() const { return obj_; }

  T* release() {
    T* result = obj_;
    obj_ = nullptr;
    return result;
  }

 private:
  static void free_func(void* element) { func(reinterpret_cast<E*>(element)); }
  static void call_free_func(void (*free_func)(void*), void* element) {
    free_func(element);
  }

  T* obj_;

  DISALLOW_ALLOCATION();
  DISALLOW_COPY_AND_ASSIGN(ScopedSSLStackType);
};

typedef ScopedSSLType<PKCS12, PKCS12_free> ScopedPKCS12;
typedef ScopedSSLType<X509, X509_free> ScopedX509;
typedef ScopedSSLStackType<STACK_OF(X509), X509, X509_free> ScopedX509Stack;

}  // namespace bin
}  // namespace dart

#endif  // RUNTIME_BIN_SECURE_SOCKET_UTILS_H_
