// Copyright (c) 2023 Yuki Kimoto
// MIT License

#include "spvm_native.h"

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

#include <assert.h>

static const char* FILE_NAME = "Net/SSLeay.c";

enum {
  SPVM__Net__SSLeay__my__NATIVE_ARGS_MAX_LENGTH = 16,
};

_Thread_local SPVM_ENV* thread_env;

int32_t SPVM__Net__SSLeay__new(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_ssl_ctx = stack[0].oval;
  
  SSL_CTX* ssl_ctx = env->get_pointer(env, stack, obj_ssl_ctx);
  
  SSL* self = SSL_new(ssl_ctx);
  
  if (!self) {
    int64_t ssl_error = ERR_peek_last_error();
    
    char* ssl_error_string = env->get_stack_tmp_buffer(env, stack);
    ERR_error_string_n(ssl_error, ssl_error_string, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE);
    
    env->die(env, stack, "[OpenSSL Error]SSL_new failed:%s.", __func__, FILE_NAME, __LINE__, ssl_error_string);
    
    int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error", &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    error_id = tmp_error_id;
    
    return error_id;
  }
  
  SPVM_OBJ* obj_self = env->new_pointer_object_by_name(env, stack, "Net::SSLeay", self, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  stack[0].oval = obj_self;
  
  env->call_instance_method_by_name(env, stack, "init", 1, &error_id, __func__, FILE_NAME, __LINE__);
  
  if (error_id) { return error_id; }
  
  stack[0].oval = obj_self;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__alert_desc_string_long(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  int32_t type = stack[0].ival;
  
  const char* desc = SSL_alert_desc_string_long(type);
  
  assert(desc);
  
  SPVM_OBJ* obj_desc = env->new_string_nolen(env, stack, desc);
  
  stack[0].oval = obj_desc;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__load_client_CA_file(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_file = stack[0].oval;
  
  if (!obj_file) {
    return env->die(env, stack, "The file $file must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* file = env->get_chars(env, stack, obj_file);
  
  STACK_OF(X509_NAME)* x509_names_stack = SSL_load_client_CA_file(file);
  
  if (!x509_names_stack) {
    int64_t ssl_error = ERR_peek_last_error();
    
    char* ssl_error_string = env->get_stack_tmp_buffer(env, stack);
    ERR_error_string_n(ssl_error, ssl_error_string, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE);
    
    env->die(env, stack, "[OpenSSL Error]SSL_load_client_CA_file failed:%s.", __func__, FILE_NAME, __LINE__, ssl_error_string);
    
    int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error", &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    error_id = tmp_error_id;
    
    return error_id;
  }
  
  int32_t length = sk_X509_NAME_num(x509_names_stack);
  SPVM_OBJ* obj_x509_names = env->new_object_array_by_name(env, stack, "Net::SSLeay::X509_NAME", length, &error_id, __func__, FILE_NAME, __LINE__);
  
  for (int32_t i = 0; i < length; i++) {
    X509_NAME* x509_name_tmp = sk_X509_NAME_value(x509_names_stack, i);
    
    X509_NAME* x509_name = X509_NAME_dup(x509_name_tmp);
    
    SPVM_OBJ* obj_address_x509_name = env->new_pointer_object_by_name(env, stack, "Address", x509_name, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    stack[0].oval = obj_address_x509_name;
    env->call_class_method_by_name(env, stack, "Net::SSLeay::X509_NAME", "new_with_pointer", 1, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    SPVM_OBJ* obj_x509_name = stack[0].oval;
    
    env->set_elem_object(env, stack, obj_x509_names, i, obj_x509_name);
  }
  
  stack[0].oval = obj_x509_names;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__select_next_proto(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_out_ref = stack[0].oval;
  
  int8_t* outlen_ref = stack[1].bref;
  
  SPVM_OBJ* obj_server = stack[2].oval;
  
  int32_t server_len = stack[3].ival;
  
  SPVM_OBJ* obj_client = stack[4].oval;
  
  int32_t client_len = stack[5].ival;
  
  if (!(obj_out_ref && env->length(env, stack, obj_out_ref) == 1)) {
    return env->die(env, stack, "The output reference $out_ref must be 1-length array.", __func__, FILE_NAME, __LINE__);
  }
  
  if (!outlen_ref) {
    return env->die(env, stack, "The reference of the output length $outlen_ref must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  if (!obj_server) {
    return env->die(env, stack, "$server must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* server = env->get_chars(env, stack, obj_server);
  
  if (!obj_client) {
    return env->die(env, stack, "$client must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* client = env->get_chars(env, stack, obj_client);
  
  unsigned char* out_tmp = NULL;
  
  int32_t status = SSL_select_next_proto(&out_tmp, (unsigned char*)outlen_ref, (const unsigned char*)server, server_len, (const unsigned char*)client, client_len);
  
  if (out_tmp) {
    SPVM_OBJ* obj_data = env->new_string(env, stack, (const char*)out_tmp, *outlen_ref);
    
    env->set_elem_object(env, stack, obj_out_ref, 0, obj_data);
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Net__SSLeay___init_native(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  thread_env = env;
  
  char* tmp_buffer = env->get_stack_tmp_buffer(env, stack);
  snprintf(tmp_buffer, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE, "%p", self);
  SPVM_OBJ* obj_address = env->new_string(env, stack, tmp_buffer, strlen(tmp_buffer));
  stack[0].oval = obj_address;
  stack[1].oval = obj_self;
  env->call_class_method_by_name(env, stack, "Net::SSLeay", "INIT_INSTANCE", 2, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  return 0;
}

int32_t SPVM__Net__SSLeay__version(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  int32_t version = SSL_version(self);
  
  stack[0].ival = version;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__get_version(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  const char* version_string = SSL_get_version(self);
  
  SPVM_OBJ* obj_version_string = env->new_string(env, stack, version_string, strlen(version_string));
  
  stack[0].oval = obj_version_string;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__get_mode(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  int64_t ret = SSL_get_mode(self);
  
  stack[0].lval = ret;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__set_mode(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  int64_t mode = stack[1].lval;
  
  int64_t ret = SSL_set_mode(self, mode);
  
  stack[0].lval = ret;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__clear_mode(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  int64_t mode = stack[1].lval;
  
  int64_t ret = SSL_clear_mode(self, mode);
  
  stack[0].lval = ret;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__set_tlsext_host_name(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SPVM_OBJ* obj_name = stack[1].oval;
  
  if (!obj_name) {
    return env->die(env, stack, "The host name $name must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const char* name = env->get_chars(env, stack, obj_name);
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  int32_t status = SSL_set_tlsext_host_name(self, name);
  
  if (!(status == 1)) {
    int64_t ssl_error = ERR_peek_last_error();
    
    char* ssl_error_string = env->get_stack_tmp_buffer(env, stack);
    ERR_error_string_n(ssl_error, ssl_error_string, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE);
    
    env->die(env, stack, "[OpenSSL Error]SSL_set_tlsext_host_name failed:%s.", __func__, FILE_NAME, __LINE__, ssl_error_string);
    
    int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error", &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    error_id = tmp_error_id;
    
    return error_id;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__get_servername(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  int32_t type = stack[1].ival;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  const char* servername = SSL_get_servername(self, type);
  
  SPVM_OBJ* obj_servername = NULL;
  if (servername) {
    obj_servername = env->new_string_nolen(env , stack, servername);
  }
  
  stack[0].oval = obj_servername;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__get_SSL_CTX(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  void* ssl_ctx = SSL_get_SSL_CTX(self);
  
  if (!ssl_ctx) {
    int64_t ssl_error = ERR_peek_last_error();
    
    char* ssl_error_string = env->get_stack_tmp_buffer(env, stack);
    ERR_error_string_n(ssl_error, ssl_error_string, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE);
    
    env->die(env, stack, "[OpenSSL Error]SSL_set_SSL_CTX failed:%s.", __func__, FILE_NAME, __LINE__, ssl_error_string);
    
    int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error", &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    error_id = tmp_error_id;
    
    return error_id;
  }
  
  SPVM_OBJ* obj_address_ssl_ctx = env->new_pointer_object_by_name(env, stack, "Address", ssl_ctx, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  stack[0].oval = obj_address_ssl_ctx;
  env->call_class_method_by_name(env, stack, "Net::SSLeay::SSL_CTX", "new_with_pointer", 1, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  SPVM_OBJ* obj_ssl_ctx = stack[0].oval;
  
  SSL_CTX_up_ref(ssl_ctx);
  
  return 0;
}

int32_t SPVM__Net__SSLeay__set_SSL_CTX(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SPVM_OBJ* obj_ssl_ctx = stack[1].oval;
  
  if (!obj_ssl_ctx) {
    return env->die(env, stack, "The SSL_CTX object $ssl_ctx must be defined(Currently undef is not allowed).", __func__, FILE_NAME, __LINE__);
  }
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  SSL_CTX* ssl_ctx = env->get_pointer(env, stack, obj_ssl_ctx);
  
  SSL_CTX* current_ssl_ctx = SSL_get_SSL_CTX(self);
  
  if (ssl_ctx == current_ssl_ctx) {
    return 0;
  }
  
  // The reference count of current ssl_ctx is decremented if SSL_set_SSL_CTX succeeds.
  SSL_CTX_up_ref(current_ssl_ctx);
  
  // The reference count of ssl_ctx is incremented.
  void* ret_ssl_ctx = SSL_set_SSL_CTX(self, ssl_ctx);
  
  if (!ret_ssl_ctx) {
    
    SSL_CTX_free(current_ssl_ctx);
    
    int64_t ssl_error = ERR_peek_last_error();
    
    char* ssl_error_string = env->get_stack_tmp_buffer(env, stack);
    ERR_error_string_n(ssl_error, ssl_error_string, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE);
    
    env->die(env, stack, "[OpenSSL Error]SSL_set_SSL_CTX failed:%s.", __func__, FILE_NAME, __LINE__, ssl_error_string);
    
    int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error", &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    error_id = tmp_error_id;
    
    return error_id;
  }
  
  return 0;
}

int32_t SPVM__Net__SSLeay__set_fd(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  int32_t fd = stack[1].ival;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  int32_t status = SSL_set_fd(self, fd);
  
  if (!(status == 1)) {
    int64_t ssl_error = ERR_peek_last_error();
    
    char* ssl_error_string = env->get_stack_tmp_buffer(env, stack);
    ERR_error_string_n(ssl_error, ssl_error_string, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE);
    
    env->die(env, stack, "[OpenSSL Error]SSL_set_fd failed:%s.", __func__, FILE_NAME, __LINE__, ssl_error_string);
    
    int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error", &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    error_id = tmp_error_id;
    
    return error_id;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__connect(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  ERR_clear_error();
  
  int32_t status = SSL_connect(self);
  
  if (!(status == 1)) {
    int32_t ssl_operation_error = SSL_get_error(self, status);
    
    assert(ssl_operation_error != SSL_ERROR_NONE);
    
    env->set_field_int_by_name(env, stack, obj_self, "operation_error", ssl_operation_error, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    
    int64_t ssl_error = ERR_peek_last_error();
    
    char* ssl_error_string = env->get_stack_tmp_buffer(env, stack);
    ERR_error_string_n(ssl_error, ssl_error_string, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE);
    
    env->die(env, stack, "[OpenSSL Error]SSL_connect failed:%s.", __func__, FILE_NAME, __LINE__, ssl_error_string);
    
    if (ssl_operation_error == SSL_ERROR_WANT_READ) {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error::SSL_ERROR_WANT_READ", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    else if (ssl_operation_error == SSL_ERROR_WANT_WRITE) {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error::SSL_ERROR_WANT_WRITE", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    else {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    
    return error_id;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__accept(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  ERR_clear_error();
  
  int32_t status = SSL_accept(self);
  
  if (!(status == 1)) {
    int32_t ssl_operation_error = SSL_get_error(self, status);
    
    assert(ssl_operation_error != SSL_ERROR_NONE);
    
    env->set_field_int_by_name(env, stack, obj_self, "operation_error", ssl_operation_error, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    
    int64_t ssl_error = ERR_peek_last_error();
    
    char* ssl_error_string = env->get_stack_tmp_buffer(env, stack);
    ERR_error_string_n(ssl_error, ssl_error_string, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE);
    
    env->die(env, stack, "[OpenSSL Error]SSL_accept failed:%s.", __func__, FILE_NAME, __LINE__, ssl_error_string);
    
    if (ssl_operation_error == SSL_ERROR_WANT_READ) {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error::SSL_ERROR_WANT_READ", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    else if (ssl_operation_error == SSL_ERROR_WANT_WRITE) {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error::SSL_ERROR_WANT_WRITE", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    else {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    
    return error_id;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__read(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  SPVM_OBJ* obj_buf = stack[1].oval;
  
  if (!obj_buf) {
    return env->die(env, stack, "The buffer $buf must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  char* buf = (char*)env->get_chars(env, stack, obj_buf);
  int32_t buf_length = env->length(env, stack, obj_buf);
  
  int32_t num = stack[2].ival;
  if (num < 0) {
    num = buf_length;
  }
  
  int32_t offset = stack[3].ival;
  
  if (!(offset >= 0)) {
    return env->die(env, stack, "The offset $offset must be greater than or equal to 0.", __func__, FILE_NAME, __LINE__);
  }
  
  if (!(offset + num <= buf_length)) {
    return env->die(env, stack, "The offset $offset + $num must be lower than or equal to the length of the buffer $buf.", __func__, FILE_NAME, __LINE__);
  }
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  ERR_clear_error();
  
  int32_t read_length = SSL_read(self, buf + offset, num);
  
  // Handling non-positive return values (error or EOF)
  if (!(read_length >= 0)) {
    int32_t ssl_operation_error = SSL_get_error(self, read_length);
    
    assert(ssl_operation_error != SSL_ERROR_NONE);
    
    // Store the operation error for the user to inspect if needed
    env->set_field_int_by_name(env, stack, obj_self, "operation_error", ssl_operation_error, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    
    // Select the appropriate SPVM error class based on the SSL operation error
    if (ssl_operation_error == SSL_ERROR_WANT_READ) {
      error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error::SSL_ERROR_WANT_READ", &error_id, __func__, FILE_NAME, __LINE__);
    }
    else if (ssl_operation_error == SSL_ERROR_WANT_WRITE) {
      error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error::SSL_ERROR_WANT_WRITE", &error_id, __func__, FILE_NAME, __LINE__);
    }
    else {
      // For other serious errors, get the error string and die
      unsigned long ssl_error = ERR_peek_last_error();
      char* ssl_error_string = env->get_stack_tmp_buffer(env, stack);
      ERR_error_string_n(ssl_error, ssl_error_string, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE);
      
      env->die(env, stack, "[OpenSSL Error]SSL_read failed:%s.", __func__, FILE_NAME, __LINE__, ssl_error_string);
      
      error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error", &error_id, __func__, FILE_NAME, __LINE__);
    }
    
    return error_id;
  }
  
  // Return the actual number of bytes read
  stack[0].ival = read_length;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__write(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SPVM_OBJ* obj_buf = stack[1].oval;
  
  if (!obj_buf) {
    return env->die(env, stack, "The buffer $buf must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  char* buf = (char*)env->get_chars(env, stack, obj_buf);
  int32_t buf_length = env->length(env, stack, obj_buf);
  
  int32_t num = stack[2].ival;
  
  if (num < 0) {
    num = buf_length;
  }
  
  int32_t offset = stack[3].ival;
  
  if (!(offset >= 0)) {
    return env->die(env, stack, "The offset $offset must be greater than or equal to 0.", __func__, FILE_NAME, __LINE__);
  }
  
  if (!(offset + num <= buf_length)) {
    return env->die(env, stack, "The offset $offset + $num must be lower than or equal to the length of the buffer $buf.", __func__, FILE_NAME, __LINE__);
  }
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  ERR_clear_error();
  
  int32_t write_length = SSL_write(self, buf + offset, num);
  
  if (!(write_length >= 0)) {
    int32_t ssl_operation_error = SSL_get_error(self, write_length);
    
    assert(ssl_operation_error != SSL_ERROR_NONE);
    
    env->set_field_int_by_name(env, stack, obj_self, "operation_error", ssl_operation_error, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    
    int64_t ssl_error = ERR_peek_last_error();
    
    char* ssl_error_string = env->get_stack_tmp_buffer(env, stack);
    ERR_error_string_n(ssl_error, ssl_error_string, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE);
    
    env->die(env, stack, "[OpenSSL Error]SSL_write failed:%s.", __func__, FILE_NAME, __LINE__, ssl_error_string);
    
    if (ssl_operation_error == SSL_ERROR_WANT_READ) {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error::SSL_ERROR_WANT_READ", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    else if (ssl_operation_error == SSL_ERROR_WANT_WRITE) {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error::SSL_ERROR_WANT_WRITE", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    else {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    
    return error_id;
  }
  
  stack[0].ival = write_length;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__shutdown(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  ERR_clear_error();
  
  int32_t status = SSL_shutdown(self);
  
  if (status < 0) {
    int32_t ssl_operation_error = SSL_get_error(self, status);
    
    assert(ssl_operation_error != SSL_ERROR_NONE);
    
    env->set_field_int_by_name(env, stack, obj_self, "operation_error", ssl_operation_error, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    
    int64_t ssl_error = ERR_peek_last_error();
    
    char* ssl_error_string = env->get_stack_tmp_buffer(env, stack);
    ERR_error_string_n(ssl_error, ssl_error_string, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE);
    
    env->die(env, stack, "[OpenSSL Error]SSL_shutdown failed:%s.", __func__, FILE_NAME, __LINE__, ssl_error_string);
    
    if (ssl_operation_error == SSL_ERROR_WANT_READ) {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error::SSL_ERROR_WANT_READ", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    else if (ssl_operation_error == SSL_ERROR_WANT_WRITE) {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error::SSL_ERROR_WANT_WRITE", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    else {
      int32_t tmp_error_id = env->get_basic_type_id_by_name(env, stack, "Net::SSLeay::Error", &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      error_id = tmp_error_id;
    }
    
    return error_id;
  }
  
  stack[0].ival = status;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__get_shutdown(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  int32_t ret = SSL_get_shutdown(self);
  
  stack[0].ival = ret;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__get_cipher(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  const char* name = SSL_get_cipher(self);
  
  assert(name);
  
  SPVM_OBJ* obj_name = env->new_string_nolen(env, stack, name);
  
  stack[0].oval = obj_name;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__get_certificate(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  // The increment count of the retrun value is not incremented.
  X509* x509 = SSL_get_certificate(self);
  
  SPVM_OBJ* obj_x509 = NULL;
  
  if (x509) {
    SPVM_OBJ* obj_address_x509 = env->new_pointer_object_by_name(env, stack, "Address", x509, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    stack[0].oval = obj_address_x509;
    env->call_class_method_by_name(env, stack, "Net::SSLeay::X509", "new_with_pointer", 1, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    obj_x509 = stack[0].oval;
    
    X509_up_ref(x509);
  }
  
  stack[0].oval = obj_x509;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__get_peer_certificate(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  // The reference count of the return value is incremented.
  X509* x509 = SSL_get_peer_certificate(self);
  
  SPVM_OBJ* obj_x509 = NULL;
  
  if (x509) {
    SPVM_OBJ* obj_address_x509 = env->new_pointer_object_by_name(env, stack, "Address", x509, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    stack[0].oval = obj_address_x509;
    env->call_class_method_by_name(env, stack, "Net::SSLeay::X509", "new_with_pointer", 1, &error_id, __func__, FILE_NAME, __LINE__);
    if (error_id) { return error_id; }
    obj_x509 = stack[0].oval;
  }
  
  stack[0].oval = obj_x509;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__get_peer_cert_chain(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  STACK_OF(X509)* x509s_stack = SSL_get_peer_cert_chain(self);
  
  SPVM_OBJ* obj_x509s = NULL;
  if (x509s_stack) {
    int32_t length = sk_X509_num(x509s_stack);
    obj_x509s = env->new_object_array_by_name(env, stack, "Net::SSLeay::X509", length, &error_id, __func__, FILE_NAME, __LINE__);
    for (int32_t i = 0; i < length; i++) {
      X509* x509 = sk_X509_value(x509s_stack, i);
      
      SPVM_OBJ* obj_address_x509 = env->new_pointer_object_by_name(env, stack, "Address", x509, &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      stack[0].oval = obj_address_x509;
      env->call_class_method_by_name(env, stack, "Net::SSLeay::X509", "new_with_pointer", 1, &error_id, __func__, FILE_NAME, __LINE__);
      if (error_id) { return error_id; }
      SPVM_OBJ* obj_x509 = stack[0].oval;
      
      X509_up_ref(x509);
      
      env->set_elem_object(env, stack, obj_x509s, i, obj_x509);
    }
  }
  
  stack[0].oval = obj_x509s;
  
  return 0;
}

int32_t SPVM__Net__SSLeay__get0_alpn_selected(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SPVM_OBJ* obj_data_ref = stack[1].oval;
  
  int32_t* len_ref = stack[2].iref;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  if (!(obj_data_ref &&  env->length(env, stack, obj_data_ref) == 1)) {
    return env->die(env, stack, "The data reference $data_ref must be 1-length array.", __func__, FILE_NAME, __LINE__);
  }
  
  if (!len_ref) {
    return env->die(env, stack, "The reference of the length $len_ref must be defined.", __func__, FILE_NAME, __LINE__);
  }
  
  const unsigned char* data_tmp = NULL;
  unsigned int len_tmp = -1;
  SSL_get0_alpn_selected(self, &data_tmp, &len_tmp);
  
  if (data_tmp) {
    SPVM_OBJ* obj_data = env->new_string(env, stack, (const char*)data_tmp, (int32_t)len_tmp);
    
    env->set_elem_object(env, stack, obj_data_ref, 0, obj_data);
  }
  
  *len_ref = len_tmp;
  
  return 0;
}

static void SPVM__Net__SSLeay__my__msg_callback(int write_p, int version, int content_type, const void* buf, size_t len, SSL* ssl, void* native_arg) {
  
  int32_t error_id = 0;
  
  SPVM_ENV* env = thread_env;
  
  SPVM_VALUE* stack = env->new_stack(env);
  
  int32_t scope_id = env->enter_scope(env, stack);
  
  char* tmp_buffer = env->get_stack_tmp_buffer(env, stack);
  snprintf(tmp_buffer, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE, "%p", ssl);
  stack[0].oval = env->new_string(env, stack, tmp_buffer, strlen(tmp_buffer));
  env->call_class_method_by_name(env, stack, "Net::SSLeay", "GET_INSTANCE", 1, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) {
    env->print_exception_to_stderr(env, stack);
    
    goto END_OF_FUNC;
  }
  SPVM_OBJ* obj_self = stack[0].oval;
  
  assert(obj_self);
  
  SPVM_OBJ* obj_cb = env->get_field_object_by_name(env, stack, obj_self, "msg_callback", &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) {
    env->print_exception_to_stderr(env, stack);
    
    goto END_OF_FUNC;
  }
  
  SPVM_OBJ* obj_buf = env->new_string(env, stack, buf, len);
  
  stack[0].oval = obj_cb;
  stack[1].ival = write_p;
  stack[2].ival = version;
  stack[3].ival = content_type;
  stack[4].oval = obj_buf;
  stack[5].ival = len;
  stack[6].oval = obj_self;
  env->call_instance_method_by_name(env, stack, "", 7, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) {
    env->print_exception_to_stderr(env, stack);
    
    goto END_OF_FUNC;
  }
  int32_t ret = stack[0].ival;
  
  END_OF_FUNC:
  
  env->leave_scope(env, stack, scope_id);
  
  env->free_stack(env, stack);
  
  return;
}

int32_t SPVM__Net__SSLeay__set_msg_callback(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SPVM_OBJ* obj_cb = stack[1].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  void (*native_cb)(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg) = NULL;
  
  if (obj_cb) {
    native_cb = &SPVM__Net__SSLeay__my__msg_callback;
  }
  
  SSL_set_msg_callback(self, native_cb);
  
  env->set_field_object_by_name(env, stack, obj_self, "msg_callback", obj_cb, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  return 0;
}

int32_t SPVM__Net__SSLeay__DESTROY(SPVM_ENV* env, SPVM_VALUE* stack) {
  
  int32_t error_id = 0;
  
  SPVM_OBJ* obj_self = stack[0].oval;
  
  SSL* self = env->get_pointer(env, stack, obj_self);
  
  char* tmp_buffer = env->get_stack_tmp_buffer(env, stack);
  snprintf(tmp_buffer, SPVM_NATIVE_C_STACK_TMP_BUFFER_SIZE, "%p", self);
  stack[0].oval = env->new_string(env, stack, tmp_buffer, strlen(tmp_buffer));
  env->call_class_method_by_name(env, stack, "Net::SSLeay", "DELETE_INSTANCE", 1, &error_id, __func__, FILE_NAME, __LINE__);
  if (error_id) { return error_id; }
  
  if (!env->no_free(env, stack, obj_self)) {
    SSL_free(self);
  }
  
  return 0;
}

