/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

package stdx.crypto.keys

import stdx.crypto.digest.*
import stdx.crypto.common.*

const RSA_id: Int32 = 6
const EC_id: Int32 = 408

// curve id
const NID_secp224r1: Int32 = 713
const NID_X9_62_prime256v1: Int32 = 415
const NID_secp384r1: Int32 = 715
const NID_secp521r1: Int32 = 716
const NID_brainpoolP256r1: Int32 = 927
const NID_brainpoolP320r1: Int32 = 929
const NID_brainpoolP384r1: Int32 = 931
const NID_brainpoolP512r1: Int32 = 933
const NID_sm2: Int32 = 1172
const PKCS1_PADDING_SIZE: Int32 = 11
const RSA_PKCS1_PADDING: Int32 = 1
const RSA_PKCS1_OAEP_PADDING: Int32 = 4
const RSA_PKCS1_PSS_PADDING: Int32 = 6

const CJ_FAIL: Int32 = -1
const CJ_NEED_READ: Int32 = -2
const CJ_NEED_WRITE: Int32 = -3
const CJ_OK: Int32 = 1

// sm2
foreign func DYN_EVP_PKEY_CTX_set1_id(ctx: CPointer<UInt64>, id: CPointer<Byte>, len: Int64, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_DigestVerifyInit(ctx: CPointer<UInt64>, pctx: CPointer<UInt64>, md: CPointer<UInt64>,
    e: CPointer<UInt64>, pkey: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_DigestVerifyUpdate(ctx: CPointer<UInt64>, data: CPointer<Byte>, len: UIntNative, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_DigestVerifyFinal(ctx: CPointer<UInt64>, sig: CPointer<Byte>, len: UIntNative, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_MD_CTX_new(msg: CPointer<DynMsg>): CPointer<UInt64>

foreign func DYN_EVP_MD_CTX_free(ctx: CPointer<UInt64>, msg: CPointer<DynMsg>): Unit

foreign func DYN_EVP_MD_CTX_set_pkey_ctx(ctx: CPointer<UInt64>, pctx: CPointer<UInt64>, msg: CPointer<DynMsg>): Unit

foreign func DYN_EVP_DigestSignInit(ctx: CPointer<UInt64>, pctx: CPointer<UInt64>, md: CPointer<UInt64>,
    e: CPointer<UInt64>, pkey: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_DigestSignUpdate(ctx: CPointer<UInt64>, data: CPointer<Byte>, len: UIntNative, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_DigestSignFinal(ctx: CPointer<UInt64>, sig: CPointer<Byte>, len: CPointer<Int64>,
    msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_keygen(ctx: CPointer<UInt64>, ppkey: CPointer<CPointer<UInt64>>, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_paramgen_init(ctx: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

// context new & free
foreign func DYN_EVP_PKEY_CTX_new(pkey: CPointer<UInt64>, e: CPointer<UInt64>, msg: CPointer<DynMsg>): CPointer<UInt64>

foreign func DYN_EVP_PKEY_CTX_new_id(id: Int32, e: CPointer<UInt64>, msg: CPointer<DynMsg>): CPointer<UInt64>

foreign func DYN_EVP_PKEY_free(c: CPointer<UInt64>, msg: CPointer<DynMsg>): Unit

foreign func DYN_EVP_PKEY_CTX_free(ctx: CPointer<UInt64>, msg: CPointer<DynMsg>): Unit

foreign func DYN_EVP_PKEY_keygen_init(ctx: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_generate(ctx: CPointer<UInt64>, ppkey: CPointer<CPointer<UInt64>>, msg: CPointer<DynMsg>): Int32

// keys type setting
foreign func DYN_BN_free(bn: CPointer<BIGNUM>, msg: CPointer<DynMsg>): Unit

foreign func DYN_BN_bin2bn(bn: CPointer<Byte>, len: Int32, ret: CPointer<BIGNUM>, msg: CPointer<DynMsg>): CPointer<BIGNUM>

foreign func DYN_EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx: CPointer<UInt64>, exp: CPointer<BIGNUM>, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_CTX_set_rsa_keygen_bits(ctx: CPointer<UInt64>, bit: Int32, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx: CPointer<UInt64>, nid: Int32, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_get_id(pkey: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_is_a(pkey: CPointer<UInt64>, type_name: CString, msg: CPointer<DynMsg>): Int32

// encrypt & decrypt
foreign func DYN_EVP_PKEY_encrypt_init(ctx: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_encrypt(
    ctx: CPointer<UInt64>,
    out: CPointer<Byte>,
    outlen: CPointer<UIntNative>,
    indata: CPointer<Byte>,
    inlen: UIntNative,
    msg: CPointer<DynMsg>
): Int32

foreign func DYN_EVP_PKEY_decrypt_init(ctx: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_decrypt(
    ctx: CPointer<UInt64>,
    out: CPointer<Byte>,
    outlen: CPointer<UIntNative>,
    indata: CPointer<Byte>,
    inlen: UIntNative,
    msg: CPointer<DynMsg>
): Int32

// sign & verify
foreign func DYN_EVP_PKEY_sign_init(ctx: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_sign(
    ctx: CPointer<UInt64>,
    sig: CPointer<Byte>,
    siglen: CPointer<UIntNative>,
    tbs: CPointer<Byte>,
    tbslen: UIntNative,
    msg: CPointer<DynMsg>
): Int32

foreign func DYN_EVP_PKEY_verify_init(ctx: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_verify(
    ctx: CPointer<UInt64>,
    sig: CPointer<Byte>,
    siglen: UIntNative,
    tbs: CPointer<Byte>,
    tbslen: UIntNative,
    msg: CPointer<DynMsg>
): Int32

foreign func DYN_EVP_PKEY_get_size(pkey: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

// DER format
foreign func DYN_i2d_PrivateKey(pkey: CPointer<UInt64>, c: CPointer<CPointer<Byte>>, msg: CPointer<DynMsg>): Int32

foreign func DYN_d2i_PrivateKey(id: Int32, ppkey: CPointer<CPointer<UInt64>>, c: CPointer<CPointer<Byte>>,
    length: Int64, msg: CPointer<DynMsg>): CPointer<UInt64>

foreign func DYN_d2i_PUBKEY(ppkey: CPointer<CPointer<UInt64>>, c: CPointer<CPointer<Byte>>, length: Int64,
    msg: CPointer<DynMsg>): CPointer<UInt64>

foreign func DYN_i2d_PUBKEY(pkey: CPointer<UInt64>, c: CPointer<CPointer<Byte>>, msg: CPointer<DynMsg>): Int32

// digest setting
foreign func DYN_EVP_md5(msg: CPointer<DynMsg>): CPointer<UInt64>

foreign func DYN_EVP_sha1(msg: CPointer<DynMsg>): CPointer<UInt64>

foreign func DYN_EVP_sha224(msg: CPointer<DynMsg>): CPointer<UInt64>

foreign func DYN_EVP_sha256(msg: CPointer<DynMsg>): CPointer<UInt64>

foreign func DYN_EVP_sha384(msg: CPointer<DynMsg>): CPointer<UInt64>

foreign func DYN_EVP_sha512(msg: CPointer<DynMsg>): CPointer<UInt64>

foreign func DYN_EVP_sm3(msg: CPointer<DynMsg>): CPointer<UInt64>

// PSS option setting
foreign func DYN_EVP_PKEY_CTX_set_rsa_padding(ctx: CPointer<UInt64>, pad: Int32, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_CTX_set_signature_md(ctx: CPointer<UInt64>, md: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

foreign func DYN_EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx: CPointer<UInt64>, saltlen: Int32, msg: CPointer<DynMsg>): Int32

// OAEP option setting
foreign func DYN_CJ_KEYS_OAEPSetting(ctx: CPointer<UInt64>, label: CString, md: CPointer<UInt64>,
    mgf1: CPointer<UInt64>, msg: CPointer<DynMsg>): Int32

foreign func MallocDynMsg(): CPointer<DynMsg>

foreign func FreeDynMsg(dynMsgPtr: CPointer<DynMsg>): Unit

class EVPKEYCTX {
    var ptr: CPointer<UInt64>
    let e = CPointer<UInt64>()
    init(pkey: CPointer<UInt64>) {
        unsafe {
            this.ptr = keyCtxNew(pkey, e)
            if (this.ptr.isNull()) {
                throw CryptoException("create key ctx error.")
            }
        }
    }

    ~init() {
        if (!ptr.isNull()) {
            keyCtxFree(ptr)
        }
    }
}
// map struct bignum_st in openssl
@C
struct BIGNUM {
    var d: CPointer<IntNative> = CPointer()
    var top: Int32 = 0
    var dmax: Int32 = 0
    var neg: Int32 = 0
    var flags: Int32 = 0
}

func keysOAEPSetting(ctx: CPointer<UInt64>, label: CString, md: CPointer<UInt64>, mgf1: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJ_KEYS_OAEPSetting(ctx, label, md, mgf1, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func keyCtxNew(pkey: CPointer<UInt64>, e: CPointer<UInt64>): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_CTX_new(pkey, e, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func keyCtxNewId(id: Int32, e: CPointer<UInt64>): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_CTX_new_id(id, e, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func keyFree(c: CPointer<UInt64>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_EVP_PKEY_free(c, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func keyCtxFree(ctx: CPointer<UInt64>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_EVP_PKEY_CTX_free(ctx, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func keygenInit(ctx: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_keygen_init(ctx, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func keyGenerate(ctx: CPointer<UInt64>, ppkey: CPointer<CPointer<UInt64>>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_generate(ctx, ppkey, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func bnFree(bn: CPointer<BIGNUM>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_BN_free(bn, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func bin2bn(bn: CPointer<Byte>, len: Int32, ret: CPointer<BIGNUM>): CPointer<BIGNUM> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_BN_bin2bn(bn, len, ret, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func setRSAPubExp(ctx: CPointer<UInt64>, exp: CPointer<BIGNUM>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, exp, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func setRSABits(ctx: CPointer<UInt64>, bit: Int32): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bit, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func setCurveNid(ctx: CPointer<UInt64>, nid: Int32): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func getPkeyId(pkey: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_get_id(pkey, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func pkeyIs(pkey: CPointer<UInt64>, name: String): Bool {
    let dynMsgPtr = generateDynMsg()
    var code: Int32 = 0
    try (cstr = unsafe { LibC.mallocCString(name) }.asResource()) {
        code = unsafe { DYN_EVP_PKEY_is_a(pkey, cstr.value, dynMsgPtr) }
    }
    checkError(dynMsgPtr)
    return code == 1
}

func encryInit(ctx: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_encrypt_init(ctx, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func encrypt(
    ctx: CPointer<UInt64>,
    out: CPointer<Byte>,
    outlen: CPointer<UIntNative>,
    indata: CPointer<Byte>,
    inlen: UIntNative
): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_encrypt(ctx, out, outlen, indata, inlen, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func decryptInit(ctx: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_decrypt_init(ctx, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func decrypt(
    ctx: CPointer<UInt64>,
    out: CPointer<Byte>,
    outlen: CPointer<UIntNative>,
    indata: CPointer<Byte>,
    inlen: UIntNative
): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_decrypt(ctx, out, outlen, indata, inlen, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func signInit(ctx: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_sign_init(ctx, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func sign(
    ctx: CPointer<UInt64>,
    sig: CPointer<Byte>,
    siglen: CPointer<UIntNative>,
    tbs: CPointer<Byte>,
    tbslen: UIntNative
): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_sign(ctx, sig, siglen, tbs, tbslen, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func verifyInit(ctx: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_verify_init(ctx, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func verify(
    ctx: CPointer<UInt64>,
    sig: CPointer<Byte>,
    siglen: UIntNative,
    tbs: CPointer<Byte>,
    tbslen: UIntNative
): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_verify(ctx, sig, siglen, tbs, tbslen, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func getSize(pkey: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_get_size(pkey, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func privateKey2d(pkey: CPointer<UInt64>, c: CPointer<CPointer<Byte>>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_i2d_PrivateKey(pkey, c, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func d2PrivateKey(id: Int32, ppkey: CPointer<CPointer<UInt64>>, c: CPointer<CPointer<Byte>>, length: Int64): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_d2i_PrivateKey(id, ppkey, c, length, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func d2Pubkey(ppkey: CPointer<CPointer<UInt64>>, c: CPointer<CPointer<Byte>>, length: Int64): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_d2i_PUBKEY(ppkey, c, length, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func pubKey2d(pkey: CPointer<UInt64>, c: CPointer<CPointer<Byte>>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_i2d_PUBKEY(pkey, c, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func md5(): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_md5(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func sha1(): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_sha1(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func sha224(): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_sha224(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func sha256(): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_sha256(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func sha384(): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_sha384(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func sha512(): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_sha512(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func setRSAPadding(ctx: CPointer<UInt64>, pad: Int32): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_CTX_set_rsa_padding(ctx, pad, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func setSignatureMD(ctx: CPointer<UInt64>, md: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_CTX_set_signature_md(ctx, md, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func setRSAPssSaltLen(ctx: CPointer<UInt64>, saltlen: Int32): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, saltlen, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func sm3(): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_sm3(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func keyCtxSetId(ctx: CPointer<UInt64>, id: CPointer<Byte>, len: Int64): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_CTX_set1_id(ctx, id, len, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func digestVerifyInit(ctx: CPointer<UInt64>, pctx: CPointer<UInt64>, md: CPointer<UInt64>, e: CPointer<UInt64>,
    pkey: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_DigestVerifyInit(ctx, pctx, md, e, pkey, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func digestVerifyUpdate(ctx: CPointer<UInt64>, data: CPointer<Byte>, len: UIntNative): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_DigestVerifyUpdate(ctx, data, len, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func digestVerifyFinal(ctx: CPointer<UInt64>, sig: CPointer<Byte>, len: UIntNative): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_DigestVerifyFinal(ctx, sig, len, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func mdCtxNew(): CPointer<UInt64> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_MD_CTX_new(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func mdCtxFree(ctx: CPointer<UInt64>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_EVP_MD_CTX_free(ctx, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func setKeyCtx(ctx: CPointer<UInt64>, pctx: CPointer<UInt64>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_EVP_MD_CTX_set_pkey_ctx(ctx, pctx, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func digestSignInit(ctx: CPointer<UInt64>, pctx: CPointer<UInt64>, md: CPointer<UInt64>, e: CPointer<UInt64>,
    pkey: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_DigestSignInit(ctx, pctx, md, e, pkey, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func digestSignUpdate(ctx: CPointer<UInt64>, data: CPointer<Byte>, len: UIntNative): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_DigestSignUpdate(ctx, data, len, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func digestSignFinal(ctx: CPointer<UInt64>, sig: CPointer<Byte>, len: CPointer<Int64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_DigestSignFinal(ctx, sig, len, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func keygen(ctx: CPointer<UInt64>, ppkey: CPointer<CPointer<UInt64>>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_keygen(ctx, ppkey, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func paramgenInit(ctx: CPointer<UInt64>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_EVP_PKEY_paramgen_init(ctx, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func generateDynMsg(): CPointer<DynMsg> {
    unsafe {
        let dynMsgPtr = MallocDynMsg()
        if (dynMsgPtr.isNull()) {
            throw CryptoException("Malloc failed.")
        }
        return dynMsgPtr
    }
}

func checkError(dynMsgPtr: CPointer<DynMsg>): Unit {
    unsafe {
        if (dynMsgPtr.isNull()) {
            throw CryptoException("Null pointer check failed.")
        }
        try {
            if (!dynMsgPtr.read().found) {
                let funcName = CString(dynMsgPtr.read().funcName).toString()
                throw CryptoException("Can not load openssl library or function ${funcName}.")
            }
        } finally {
            FreeDynMsg(dynMsgPtr)
        }
    }
}

@C
protected struct DynMsg {
    var found = true
    var funcName = CPointer<UInt8>()
}

@C
protected struct ExceptionData {
    var message: CPointer<Byte> = CPointer() // this is always allocated using malloc
    var constMessage: CPointer<Byte> = CPointer() // this is never allocated

    protected prop hasException: Bool {
        get() {
            !message.isNull() || !constMessage.isNull()
        }
    }

    protected func throwException(fallback!: String): Nothing {
        if (!message.isNull()) {
            throw CryptoException(CString(message).toString())
        }
        if (!constMessage.isNull()) {
            throw CryptoException(CString(constMessage).toString())
        }

        throw CryptoException(fallback)
    }

    protected mut func clear(): Unit {
        if (!message.isNull()) {
            unsafe { CRYPTO_free(message) }
            message = CPointer()
        }
    }

    protected static func create(): CPointer<ExceptionData> {
        unsafe {
            let ptr = LibC.malloc<ExceptionData>(count: 1)
            if (ptr.isNull()) {
                throw CryptoException("malloc failed")
            }
            ptr.write(ExceptionData())
            return ptr
        }
    }

    protected static func free(exception: CPointer<ExceptionData>): Unit {
        unsafe {
            if (exception.isNull()) {
                return
            }

            var data = exception.read()
            data.clear()
            LibC.free(exception)
        }
    }

    protected static func withException<R>(block: (CPointer<ExceptionData>) -> R): R {
        let exception = ExceptionData.create()
        try {
            block(exception)
        } finally {
            ExceptionData.free(exception)
        }
    }
}