/*
 * 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.x509



import std.core.StringBuilder
import std.time.DateTime
import std.collection.*
import stdx.encoding.hex.toHexString
import stdx.crypto.common.*

const NULL_BYTE = "\0"

extend<T> CPointer<T> {
    func ifNotNull<R>(block: (CPointer<T>) -> R): ?R {
        if (isNull()) {
            None
        } else {
            block(this)
        }
    }
}

extend<T> Option<T> {
    func exists() {
        match (this) {
            case Some(_) => true
            case None => false
        }
    }

    func missing() {
        !exists()
    }
}

public class X509Exception <: Exception {
    public init() {
        super()
    }

    public init(message: String) {
        super(message)
    }

    protected override func getClassName(): String {
        return "X509Exception"
    }
}

func runOnRawPtr<T, R>(data: Array<T>, action: (CPointer<T>, UIntNative) -> R): R where T <: CType {
    unsafe {
        let handle = acquireArrayRawData(data)
        try {
            return action(handle.pointer, UIntNative(data.size))
        } finally {
            releaseArrayRawData(handle)
        }
    }
}

func getRawPubKey(key: PublicKey): CPointer<Unit> {
    var data = key.encodeToDer().body
    unsafe {
        var keyPtr = CPointer<Unit>()
        let dataHandle: CPointerHandle<Byte> = acquireArrayRawData(data)
        try (pubPtr = malloc<CPointer<Byte>>()) {
            pubPtr.pointer.write(dataHandle.pointer)
            keyPtr = getPubKeyPtr(pubPtr.pointer, data.size)
            if (keyPtr.isNull()) {
                throw X509Exception("Fail to load public key.")
            }
        } finally {
            releaseArrayRawData(dataHandle)
        }
        keyPtr
    }
}

func getRawPriKey(key: PrivateKey): CPointer<Unit> {
    var data = key.encodeToDer().body
    unsafe {
        var keyPtr = CPointer<Unit>()
        let dataHandle: CPointerHandle<Byte> = acquireArrayRawData(data)
        try (priPtr = malloc<CPointer<Byte>>()) {
            priPtr.pointer.write(dataHandle.pointer)
            keyPtr = getPriKeyPtr(priPtr.pointer, data.size)
            if (keyPtr.isNull()) {
                throw X509Exception("Fail to load private key.")
            }
        } finally {
            releaseArrayRawData(dataHandle)
        }
        keyPtr
    }
}

func getRawName(name: X509Name): CPointer<Unit> {
    var data = name.blob.blob.body
    unsafe {
        var namePtr = CPointer<Unit>()
        let dataHandle: CPointerHandle<Byte> = acquireArrayRawData(data)
        try (priPtr = malloc<CPointer<Byte>>()) {
            priPtr.pointer.write(dataHandle.pointer)
            namePtr = getNamePtr(priPtr.pointer, data.size)
            if (namePtr.isNull()) {
                throw X509Exception("Fail to load X509Name.")
            }
        } finally {
            releaseArrayRawData(dataHandle)
        }
        namePtr
    }
}

func getCertBody(cert: CPointer<Unit>): Array<Byte> {
    unsafe {
        var certlen = getCertLen(cert, CPointer<CPointer<Byte>>())
        if (certlen < 0) {
            throw X509Exception("Fail to init X509.")
        }
        var certBody = Array<Byte>(certlen, repeat: 0)
        let data: CPointerHandle<Byte> = acquireArrayRawData(certBody)
        try (certPtr = malloc<CPointer<Byte>>()) {
            certPtr.pointer.write(data.pointer)
            certlen = getCertLen(cert, certPtr.pointer)
            if (certlen < 0) {
                throw X509Exception("Fail to init X509.")
            }
        } finally {
            releaseArrayRawData(data)
        }
        certBody
    }
}

let KEY_USAGE_STRINGS: Array<(String, String)> = [
    ("EncipherOnly", "Encipher Only"),
    ("CRLSign", "CRL Sign"),
    ("CertSign", "Certificate Sign"),
    ("KeyAgreement", "Key Agreement"),
    ("DataEncipherment", "Data Encipherment"),
    ("KeyEncipherment", "Key Encipherment"),
    ("NonRepudiation", "Non Repudiation"),
    ("DigitalSignature", "Digital Signature"),
    ("DecipherOnly", "Decipher Only")
]
let EXT_KEY_USAGE_STRINGS: Array<(String, String)> = [
    ("ServerAuth", "TLS Web Server Authentication"),
    ("ClientAuth", "TLS Web Client Authentication"),
    ("EmailProtection", "E-mail Protection"),
    ("CodeSigning", "Code Signing"),
    ("OCSPSigning", "OCSP Signing"),
    ("TimeStamping", "Time Stamping")
]

func setTimeString(strBuilder: StringBuilder, timeString: String) {
    if (timeString.size == 1) {
        strBuilder.append("0")
    }
    strBuilder.append(timeString)
}

func getTimeString(data: DateTime): String {
    let zData = data.inUTC()
    let strBuilder = StringBuilder()
    strBuilder.append(zData.year.toString())
    setTimeString(strBuilder, zData.month.toInteger().toString())
    setTimeString(strBuilder, zData.dayOfMonth.toString())
    setTimeString(strBuilder, zData.hour.toString())
    setTimeString(strBuilder, zData.minute.toString())
    setTimeString(strBuilder, zData.second.toString())
    strBuilder.append("Z")
    strBuilder.toString()
}

func formIpv6(ipAddress: IP) {
    const IPV6_STR_LEN = 8
    var arr = Array<String>(IPV6_STR_LEN, repeat: "")
    const UNIT_SIZE = 2
    for (i in 0..arr.size) {
        arr[i] = toHexString(ipAddress[UNIT_SIZE * i]) + toHexString(ipAddress[UNIT_SIZE * i + 1])
    }
    return arr |> collectString<String>(delimiter: ":")
}

func ipAddressToString(ipAddress: IP): String {
    match (ipAddress.size) {
        // IPv4
        case 4 => return ipAddress |> collectString<Byte>(delimiter: ".")
        // IPv6
        case 16 => return formIpv6(ipAddress)
        case _ => throw X509Exception("Illegal IP address.")
    }
}

func getAltNameString(dnsNames: Array<String>, emails: Array<String>, ips: Array<IP>) {
    var strBuilder = StringBuilder()
    let dnsName = "DNS:"
    let emailName = "email:"
    let ipName = "IP:"
    addString(strBuilder, dnsNames, dnsName)
    addString(strBuilder, emails, emailName)
    addString(strBuilder, Array(ips.size, {i => ipAddressToString(ips[i])}), ipName)
    strBuilder.toString()
}

func addString(strBuilder: StringBuilder, extensions: Array<String>, name: String) {
    for (i in 0..extensions.size) {
        if (i == 0 && strBuilder.size != 0) {
            strBuilder.append(", ")
        }
        strBuilder.append(name)
        strBuilder.append(extensions[i])
        if (i != extensions.size - 1) {
            strBuilder.append(", ")
        }
    }
}

func getKeyUsageString(usages: ?KeyUsage) {
    var res: String
    match (usages) {
        case None => res = ""
        case Some(usage) => res = usage.toString()
    }
    for (stringTuple in KEY_USAGE_STRINGS) {
        res = res.replace(stringTuple[0], stringTuple[1])
    }
    res
}

func getExtKeyUsageString(usages: ?ExtKeyUsage) {
    var res: String
    match (usages) {
        case None => res = ""
        case Some(usage) => res = usage.toString()
    }
    for (stringTuple in EXT_KEY_USAGE_STRINGS) {
        res = res.replace(stringTuple[0], stringTuple[1])
    }
    res
}

func getDigest(signatureAlgorithm: ?SignatureAlgorithm) {
    match (signatureAlgorithm) {
        case None => unsafe { sha256() }
        case Some(signatureAlgorithm) => signatureAlgorithm.getDigest()
    }
}

func getKeyType(signatureAlgorithm: ?SignatureAlgorithm) {
    match (signatureAlgorithm) {
        case None => 0
        case Some(signatureAlgorithm) => signatureAlgorithm.getKeyType()
    }
}

func x509MallocOrThrow<T>(count!: Int64 = 1): CPointer<T> where T <: CType {
    let result: CPointer<T> = LibC.malloc<T>(count: count)
    if (result.isNull()) {
        throw X509Exception("Failed to allocate memory.")
    }
    result
}

@When[os == "Windows"]
foreign func DYN_CJ_SystemRootCerts(msg: CPointer<DynMsg>): CString

@When[os == "Windows"]
func CJ_SystemRootCerts(): CString {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJ_SystemRootCerts(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

foreign {
    func DYN_EVP_md5(msg: CPointer<DynMsg>): CPointer<Unit>

    func DYN_EVP_sha1(msg: CPointer<DynMsg>): CPointer<Unit>

    func DYN_EVP_sha256(msg: CPointer<DynMsg>): CPointer<Unit>

    func DYN_EVP_sha384(msg: CPointer<DynMsg>): CPointer<Unit>

    func DYN_EVP_sha512(msg: CPointer<DynMsg>): CPointer<Unit>

    func DYN_i2d_X509(name: CPointer<Byte>, c: CPointer<CPointer<Byte>>, msg: CPointer<DynMsg>): Int32

    func DYN_CJCreateCert(
        pubKey: CPointer<Unit>,
        priKey: CPointer<Unit>,
        issuer: CPointer<Unit>,
        subject: CPointer<Unit>,
        digest: CPointer<Unit>,
        certInfo: CPointer<X509CertInfo>,
        msg: CPointer<DynMsg>
    ): CPointer<Unit>

    func DYN_CJGetCertLen(cert: CPointer<Unit>, out: CPointer<CPointer<Byte>>, msg: CPointer<DynMsg>): Int64

    func DYN_CJCertFree(cert: CPointer<Unit>, msg: CPointer<DynMsg>): Unit

    func DYN_CJKeyFree(key: CPointer<Unit>, msg: CPointer<DynMsg>): Unit

    func DYN_CJGetPubKeyPtr(pubKey: CPointer<CPointer<Byte>>, length: Int64, msg: CPointer<DynMsg>): CPointer<Unit>

    func DYN_CJGetPriKeyPtr(priKey: CPointer<CPointer<Byte>>, length: Int64, msg: CPointer<DynMsg>): CPointer<Unit>

    func DYN_CJGetNamePtr(nameKey: CPointer<CPointer<Byte>>, lenght: Int64, msg: CPointer<DynMsg>): CPointer<Unit>

    func DYN_CJCheckKeyType(key: CPointer<Unit>, keyType: Int64, msg: CPointer<DynMsg>): Bool

    func DYN_CJGetX509DnsNames(
        derBlob: CPointer<Byte>,
        length: UIntNative,
        result: CPointer<ByteArrayResult>,
        msg: CPointer<DynMsg>
    ): Unit

    func DYN_CJGetX509EmailAddresses(
        derBlob: CPointer<Byte>,
        length: UIntNative,
        result: CPointer<ByteArrayResult>,
        msg: CPointer<DynMsg>
    ): Unit

    func DYN_CJGetX509IpAddresses(
        derBlob: CPointer<Byte>,
        length: UIntNative,
        result: CPointer<ByteArrayResult>,
        msg: CPointer<DynMsg>
    ): Unit

    func DYN_CJGetX509KeyUsage(derBlob: CPointer<Byte>, length: UIntNative, msg: CPointer<DynMsg>): UInt16

    func DYN_CJGetX509ExtKeyUsage(
        derBlob: CPointer<Byte>,
        length: UIntNative,
        result: CPointer<UInt16Result>,
        msg: CPointer<DynMsg>
    ): Unit

    func DYN_CJX509ReqNew(msg: CPointer<DynMsg>): CPointer<Unit>

    func DYN_CJX509ReqFree(req: CPointer<Unit>, msg: CPointer<DynMsg>): Unit

    func DYN_CJGetX509ReqDer(req: CPointer<Unit>, c: CPointer<CPointer<Byte>>, msg: CPointer<DynMsg>): Int64

    func DYN_OPENSSL_sk_num(nameStack: CPointer<Unit>, msg: CPointer<DynMsg>): Int64

    func DYN_CJLoadPrivateKey(c: CPointer<CPointer<Byte>>, length: Int64, msg: CPointer<DynMsg>): CPointer<Unit>

    func DYN_CJX509ReqSetSubject(req: CPointer<Unit>, name: CPointer<Unit>, msg: CPointer<DynMsg>): Int64

    func DYN_CJX509ReqSetPubkey(req: CPointer<Unit>, pkey: CPointer<Unit>, msg: CPointer<DynMsg>): Int64

    func DYN_CJX509ReqSign(req: CPointer<Unit>, pkey: CPointer<Unit>, md: CPointer<Unit>, msg: CPointer<DynMsg>): Int64

    func DYN_CJNameStackNew(msg: CPointer<DynMsg>): CPointer<Unit>

    func DYN_CJNameStackFree(nameStack: CPointer<Unit>, msg: CPointer<DynMsg>): Unit

    func DYN_CJAddName(nameStack: CPointer<Unit>, nid: Int64, value: CString, msg: CPointer<DynMsg>): Int64

    func DYN_CJReqAddExtension(req: CPointer<Unit>, nameStack: CPointer<Unit>, msg: CPointer<DynMsg>): Int64

    func DYN_CJVerifyX509Cert(
        cert: CPointer<RawX509Cert>,
        roots: CPointer<RawX509CertArray>,
        intermediates: CPointer<RawX509CertArray>,
        msg: CPointer<DynMsg>
    ): Int32

    func DYN_CJGetX509CsrDnsNames(
        derBlob: CPointer<Byte>,
        length: UIntNative,
        result: CPointer<ByteArrayResult>,
        msg: CPointer<DynMsg>
    ): Unit

    func DYN_CJGetX509CsrEmailAddresses(
        derBlob: CPointer<Byte>,
        length: UIntNative,
        result: CPointer<ByteArrayResult>,
        msg: CPointer<DynMsg>
    ): Unit

    func DYN_CJGetX509CsrIpAddresses(
        derBlob: CPointer<Byte>,
        length: UIntNative,
        result: CPointer<ByteArrayResult>,
        msg: CPointer<DynMsg>
    ): Unit

    // return type CPointer<Unit> map to C type (X509_NAME *)
    func DYN_CJNameNew(msg: CPointer<DynMsg>): CPointer<Unit>

    // name type CPointer<Unit> map to C type (X509_NAME *)
    func DYN_CJNameFree(name: CPointer<Unit>, msg: CPointer<DynMsg>): Unit

    // name type CPointer<Unit> map to C type (X509_NAME *)
    func DYN_CJX509NameAddEntry(
        name: CPointer<Unit>,
        field: CString,
        nameType: Int32,
        bytes: CString,
        msg: CPointer<DynMsg>
    ): Int32

    // name type CPointer<Unit> map to C type (X509_NAME *)
    func DYN_CJGetNameDer(name: CPointer<Unit>, c: CPointer<CPointer<Byte>>, msg: CPointer<DynMsg>): Int32

    func MallocDynMsg(): CPointer<DynMsg>

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

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

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

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

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

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

func checkKeyType(key: CPointer<Unit>, keyType: Int64): Bool {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJCheckKeyType(key, keyType, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func getNameDer(name: CPointer<Unit>, c: CPointer<CPointer<Byte>>): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJGetNameDer(name, c, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func getNamePtr(nameKey: CPointer<CPointer<Byte>>, lenght: Int64): CPointer<Unit> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJGetNamePtr(nameKey, lenght, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func x509ReqSign(req: CPointer<Unit>, pkey: CPointer<Unit>, md: CPointer<Unit>): Int64 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJX509ReqSign(req, pkey, md, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func x509ReqSetPubkey(req: CPointer<Unit>, pkey: CPointer<Unit>): Int64 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJX509ReqSetPubkey(req, pkey, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func x509ReqSetSubject(req: CPointer<Unit>, name: CPointer<Unit>): Int64 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJX509ReqSetSubject(req, name, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func getX509ReqDer(req: CPointer<Unit>, c: CPointer<CPointer<Byte>>): Int64 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJGetX509ReqDer(req, c, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func x509ReqFree(req: CPointer<Unit>): Unit {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJX509ReqFree(req, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func x509ReqNew(): CPointer<Unit> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJX509ReqNew(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func x509NameAddEntry(
    name: CPointer<Unit>,
    field: CString,
    nameType: Int32,
    bytes: CString
): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJX509NameAddEntry(name, field, nameType, bytes, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func nameFree(name: CPointer<Unit>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_CJNameFree(name, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func nameNew(): CPointer<Unit> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJNameNew(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func reqAddExtension(req: CPointer<Unit>, nameStack: CPointer<Unit>): Int64 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJReqAddExtension(req, nameStack, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func addName(nameStack: CPointer<Unit>, nid: Int64, value: CString): Int64 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJAddName(nameStack, nid, value, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func nameStackNew(): CPointer<Unit> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJNameStackNew(dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func nameStackFree(nameStack: CPointer<Unit>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_CJNameStackFree(nameStack, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func getX509CsrDnsNames(derBlob: CPointer<Byte>, length: UIntNative, result: CPointer<ByteArrayResult>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_CJGetX509CsrDnsNames(derBlob, length, result, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func getX509CsrEmailAddresses(derBlob: CPointer<Byte>, length: UIntNative, result: CPointer<ByteArrayResult>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_CJGetX509CsrEmailAddresses(derBlob, length, result, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func getX509CsrIpAddresses(derBlob: CPointer<Byte>, length: UIntNative, result: CPointer<ByteArrayResult>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_CJGetX509CsrIpAddresses(derBlob, length, result, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func verifyX509Cert(
    cert: CPointer<RawX509Cert>,
    roots: CPointer<RawX509CertArray>,
    intermediates: CPointer<RawX509CertArray>
): Int32 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJVerifyX509Cert(cert, roots, intermediates, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func getX509ExtKeyUsage(derBlob: CPointer<Byte>, length: UIntNative, result: CPointer<UInt16Result>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_CJGetX509ExtKeyUsage(derBlob, length, result, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func stackNameNum(nameStack: CPointer<Unit>): Int64 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_OPENSSL_sk_num(nameStack, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func getX509KeyUsage(derBlob: CPointer<Byte>, length: UIntNative): UInt16 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJGetX509KeyUsage(derBlob, length, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func createCert(
    pubKey: CPointer<Unit>,
    priKey: CPointer<Unit>,
    issuer: CPointer<Unit>,
    subject: CPointer<Unit>,
    digest: CPointer<Unit>,
    certInfo: CPointer<X509CertInfo>
): CPointer<Unit> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJCreateCert(pubKey, priKey, issuer, subject, digest, certInfo, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func getX509IpAddresses(derBlob: CPointer<Byte>, length: UIntNative, result: CPointer<ByteArrayResult>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_CJGetX509IpAddresses(derBlob, length, result, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func getX509EmailAddresses(derBlob: CPointer<Byte>, length: UIntNative, result: CPointer<ByteArrayResult>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_CJGetX509EmailAddresses(derBlob, length, result, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func getX509DnsNames(derBlob: CPointer<Byte>, length: UIntNative, result: CPointer<ByteArrayResult>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_CJGetX509DnsNames(derBlob, length, result, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func getPriKeyPtr(priKey: CPointer<CPointer<Byte>>, length: Int64) {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJGetPriKeyPtr(priKey, length, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func getCertLen(cert: CPointer<Unit>, out: CPointer<CPointer<Byte>>): Int64 {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJGetCertLen(cert, out, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

func certFree(cert: CPointer<Unit>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_CJCertFree(cert, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func keyFree(cert: CPointer<Unit>): Unit {
    let dynMsgPtr = generateDynMsg()
    unsafe { DYN_CJKeyFree(cert, dynMsgPtr) }
    checkError(dynMsgPtr)
}

func getPubKeyPtr(pubKey: CPointer<CPointer<Byte>>, length: Int64): CPointer<Unit> {
    let dynMsgPtr = generateDynMsg()
    let res = unsafe { DYN_CJGetPubKeyPtr(pubKey, length, dynMsgPtr) }
    checkError(dynMsgPtr)
    return res
}

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

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

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