/* com_wolfssl_WolfSSLContext.c
 *
 * Copyright (C) 2006-2025 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 */

#include <stdio.h>
#include <stdint.h>

#ifdef WOLFSSL_USER_SETTINGS
    #include <wolfssl/wolfcrypt/settings.h>
#else
    #include <wolfssl/options.h>
#endif
#include <wolfssl/ssl.h>
#include <wolfssl/wolfcrypt/asn.h>
#include <wolfssl/error-ssl.h>

#include "com_wolfssl_globals.h"
#include "com_wolfssl_WolfSSLContext.h"

/* global object refs for verify, CRL callbacks */
static jobject g_verifyCbIfaceObj;

#ifdef HAVE_CRL
static jobject g_crlCtxCbIfaceObj;
#endif

/* custom I/O native fn prototypes */
int  NativeIORecvCb(WOLFSSL *ssl, char *buf, int sz, void *ctx);
int  NativeIOSendCb(WOLFSSL *ssl, char *buf, int sz, void *ctx);
int  NativeGenCookieCb(WOLFSSL *ssl, unsigned char *buf, int sz, void *ctx);
int  NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store);
void NativeCtxMissingCRLCallback(const char* url);
int  NativeMacEncryptCb(WOLFSSL* ssl, unsigned char* macOut,
        const unsigned char* macIn, unsigned int macInSz, int macContent,
        int macVerify, unsigned char* encOut, const unsigned char* encIn,
        unsigned int encSz, void* ctx);
int  NativeDecryptVerifyCb(WOLFSSL* ssl, unsigned char* decOut,
        const unsigned char* decIn, unsigned int decSz, int content,
        int verify, unsigned int* padSz, void* ctx);
int  NativeVerifyDecryptCb(WOLFSSL* ssl, unsigned char* decOut,
        const unsigned char* decIn, unsigned int decSz, int content,
        int macVerify, unsigned int* padSz, void* ctx);
int  NativeEccSignCb(WOLFSSL* ssl, const unsigned char* in, unsigned int inSz,
        unsigned char* out, unsigned int* outSz, const unsigned char* keyDer,
        unsigned int keySz, void* ctx);
int  NativeEccVerifyCb(WOLFSSL* ssl, const unsigned char* sig,
        unsigned int sigSz, const unsigned char* hash, unsigned int hashSz,
        const unsigned char* keyDer, unsigned int keySz, int* result,
        void* ctx);
int  NativeEccSharedSecretCb(WOLFSSL* ssl, ecc_key* otherKey,
        unsigned char* publicKeyDer, unsigned int* pubKeySz,
        unsigned char* out, unsigned int* outlen, int side, void* ctx);
int  NativeRsaSignCb(WOLFSSL* ssl, const unsigned char* in, unsigned int inSz,
        unsigned char* out, unsigned int* outSz, const unsigned char* keyDer,
        unsigned int keySz, void* ctx);
int  NativeRsaVerifyCb(WOLFSSL* ssl, unsigned char* sig, unsigned int sigSz,
        unsigned char** out, const unsigned char* keyDer, unsigned int keySz,
        void* ctx);
int  NativeRsaEncCb(WOLFSSL* ssl, const unsigned char* in, unsigned int inSz,
        unsigned char* out, unsigned int* outSz, const unsigned char* keyDer,
        unsigned int keySz, void* ctx);
int  NativeRsaDecCb(WOLFSSL* ssl, unsigned char* in, unsigned int inSz,
        unsigned char** out, const unsigned char* keyDer, unsigned int keySz,
        void* ctx);

#if defined(HAVE_PK_CALLBACKS) && defined(HAVE_ECC)

/* get JavaEnv from JavaVM
 * sets needDetach == 1 if DeteachCurrentThread() needs to be called
 * upon caller exit/cleanup
 * return 0 on success, negative on error */
static int GetJNIEnvFromVM(JavaVM* vm, JNIEnv** jenv, int* needsDetach)
{
    jint ret;

    ret = (int)((*vm)->GetEnv(vm, (void**)jenv, JNI_VERSION_1_6));
    if (ret == JNI_EDETACHED) {
#ifdef __ANDROID__
        ret = (*vm)->AttachCurrentThread(vm, jenv, NULL);
#else
        ret = (*vm)->AttachCurrentThread(vm, (void**)jenv, NULL);
#endif
        if (ret) {
            return -1;
        }
        *needsDetach = 1;
    } else if (ret != JNI_OK) {
        return -1;
    }

    return 0;
}

#endif /* HAVE_PK_CALLBACKS && HAVE_ECC */

/* check is exception has occurred, if so describes and clears it.
 * returns 0 if no exception occurred, 1 if occurred. */
static int CheckException(JNIEnv* jenv)
{
    int ret = 0;

    if (jenv == NULL)
        return ret;

    if ((*jenv)->ExceptionOccurred(jenv)) {
        ret = 1;
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
    }

    return ret;
}

/* throw WolfSSLJNIException with given message, detach current
 * thread from VM if needsDetach is set to 1 */
static void throwWolfSSLJNIExceptionWithMsg(JNIEnv* jenv, const char* msg,
                                            int needsDetach)
{
    jclass class = NULL;

    if (g_vm == NULL || jenv == NULL || msg == NULL)
        return;

    class = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return;
    }

    (*jenv)->ThrowNew(jenv, class, msg);

    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return;
}

JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLContext_newContext(JNIEnv* jenv,
        jclass jcl, jlong method)
{

    (void)jenv;
    (void)jcl;

    /* wolfSSL checks for NULL method ptr */
    return (jlong)(uintptr_t)wolfSSL_CTX_new((WOLFSSL_METHOD*)(uintptr_t)method);
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setTmpDH
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jbyteArray p, jint pSz,
   jbyteArray g, jint gSz)
{
#ifndef NO_DH
    int ret = 0;
    jclass excClass;
    unsigned char* pBuf = NULL;
    unsigned char* gBuf = NULL;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL || p == NULL || g == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    if (ctx == NULL) {
        excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLException");
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            return (jint)SSL_FAILURE;
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null in "
                "setTmpDH");
        return (jint)SSL_FAILURE;
    }

    pBuf = (unsigned char*)XMALLOC((int)pSz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
    if (pBuf == NULL) {
        ret = MEMORY_E;
    }

    if (ret == 0) {
        XMEMSET(pBuf, 0, pSz);
        gBuf = (unsigned char*)XMALLOC((int)gSz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
        if (gBuf == NULL) {
            ret = MEMORY_E;
        }
    }

    if (ret == 0) {
        XMEMSET(gBuf, 0, gSz);
        (*jenv)->GetByteArrayRegion(jenv, p, 0, pSz, (jbyte*)pBuf);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            ret = SSL_FAILURE;
        }
    }

    if (ret == 0) {
        (*jenv)->GetByteArrayRegion(jenv, g, 0, gSz, (jbyte*)gBuf);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            ret = SSL_FAILURE;
        }
    }

    if (ret == 0) {
        ret = wolfSSL_CTX_SetTmpDH(ctx, pBuf, pSz, gBuf, gSz);
    }

    if (pBuf != NULL) {
        XMEMSET(pBuf, 0, (int)pSz);
        XFREE(pBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
    }
    if (gBuf != NULL) {
        XMEMSET(gBuf, 0, (int)gSz);
        XFREE(gBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
    }

    return (jint)ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)p;
    (void)pSz;
    (void)g;
    (void)gSz;
    return (jint)NOT_COMPILED_IN;
#endif /* !NO_DH */
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setTmpDHFile
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jstring file, jint format)
{
#if !defined(NO_DH) && !defined(NO_FILESYSTEM)
    int ret;
    const char* fname;
    jclass excClass;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (file == NULL) {
        return (jint)SSL_BAD_FILE;
    }

    if (ctx == NULL) {
        excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLException");
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            return (jint)SSL_FAILURE;
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null in "
                "setTmpDHFile");
        return (jint)SSL_FAILURE;
    }

    fname = (*jenv)->GetStringUTFChars(jenv, file, 0);

    ret = wolfSSL_CTX_SetTmpDH_file(ctx, fname, format);

    (*jenv)->ReleaseStringUTFChars(jenv, file, fname);

    return (jint)ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)file;
    (void)format;
    return (jint)NOT_COMPILED_IN;
#endif /* !NO_DH && !NO_FILESYSTEM */
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_useCertificateFile
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jstring file, jint format)
{
#ifndef NO_FILESYSTEM
    jint ret = 0;
    jclass excClass;
    const char* certFile;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL) {
        return (jint)SSL_FAILURE;
    }

    if (file == NULL) {
        excClass = (*jenv)->FindClass(jenv, "java/lang/NullPointerException");
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        /* throw NullPointerException */
        (*jenv)->ThrowNew(jenv, excClass,
                "Input certificate file is NULL");

        return (jint)SSL_FAILURE;
    }

    certFile = (*jenv)->GetStringUTFChars(jenv, file, 0);

    ret = (jint) wolfSSL_CTX_use_certificate_file(ctx, certFile, (int)format);

    (*jenv)->ReleaseStringUTFChars(jenv, file, certFile);

    return ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)file;
    (void)format;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_usePrivateKeyFile
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jstring file, jint format)
{
#ifndef NO_FILESYSTEM
    jint ret = 0;
    jclass excClass;
    const char* keyFile;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL) {
        return (jint)SSL_FAILURE;
    }

    if (file == NULL) {
        excClass = (*jenv)->FindClass(jenv, "java/lang/NullPointerException");
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        /* throw NullPointerException */
        (*jenv)->ThrowNew(jenv, excClass,
                "Input private key file is NULL");

        return (jint)SSL_FAILURE;
    }

    keyFile = (*jenv)->GetStringUTFChars(jenv, file, 0);

    ret = (jint) wolfSSL_CTX_use_PrivateKey_file(ctx, keyFile, (int)format);

    (*jenv)->ReleaseStringUTFChars(jenv, file, keyFile);

    return ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)file;
    (void)format;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_loadVerifyLocations
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jstring file, jstring path)
{
#ifndef NO_FILESYSTEM
    jint ret = 0;
    jclass excClass;
    const char* caFile;
    const char* caPath;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL) {
        return (jint)SSL_FAILURE;
    }

    if (file == NULL && path == NULL) {
        excClass = (*jenv)->FindClass(jenv, "java/lang/NullPointerException");
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        /* throw NullPointerException */
        (*jenv)->ThrowNew(jenv, excClass,
                "Input file and path are both NULL");

        return (jint)SSL_FAILURE;
    }

    if (file) {
        caFile = (*jenv)->GetStringUTFChars(jenv, file, 0);
    } else {
        caFile = NULL;
    }

    if (path) {
        caPath = (*jenv)->GetStringUTFChars(jenv, path, 0);
    } else {
        caPath = NULL;
    }

    ret = (jint) wolfSSL_CTX_load_verify_locations(ctx, caFile, caPath);

    if (caFile)
        (*jenv)->ReleaseStringUTFChars(jenv, file, caFile);
    if (caPath)
        (*jenv)->ReleaseStringUTFChars(jenv, path, caPath);

    return ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)file;
    (void)path;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_useCertificateChainFile
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jstring file)
{
#ifndef NO_FILESYSTEM
    jint ret = 0;
    jclass excClass;
    const char* chainFile;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL) {
        return (jint)SSL_FAILURE;
    }

    /* throw exception if no input file */
    if (file == NULL) {
        excClass = (*jenv)->FindClass(jenv, "java/lang/NullPointerException");
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        /* throw NullPointerException */
        (*jenv)->ThrowNew(jenv, excClass,
                "Input certificate chain file is NULL");

        return (jint)SSL_FAILURE;
    }

    chainFile = (*jenv)->GetStringUTFChars(jenv, file, 0);

    ret = (jint) wolfSSL_CTX_use_certificate_chain_file(ctx, chainFile);

    (*jenv)->ReleaseStringUTFChars(jenv, file, chainFile);

    return ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)file;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_freeContext
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jenv;
    (void)jcl;

    /* release verify callback object if set */
    if (g_verifyCbIfaceObj != NULL) {
        (*jenv)->DeleteGlobalRef(jenv, g_verifyCbIfaceObj);
        g_verifyCbIfaceObj = NULL;
    }

#ifdef HAVE_CRL
    /* release global CRL callback object if set */
    if (g_crlCtxCbIfaceObj != NULL) {
        (*jenv)->DeleteGlobalRef(jenv, g_crlCtxCbIfaceObj);
        g_crlCtxCbIfaceObj = NULL;
    }
#endif /* HAVE_CRL */

    /* wolfSSL checks for null pointer */
    wolfSSL_CTX_free(ctx);
}

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setVerify(JNIEnv* jenv,
    jobject jcl, jlong ctxPtr, jint mode, jobject callbackIface)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL) {
        return;
    }

    /* release verify callback object if set before */
    if (g_verifyCbIfaceObj != NULL) {
        (*jenv)->DeleteGlobalRef(jenv, g_verifyCbIfaceObj);
        g_verifyCbIfaceObj = NULL;
    }

    if (!callbackIface) {
        wolfSSL_CTX_set_verify(ctx, mode, NULL);
    }
    else {
        /* store Java verify Interface object */
        g_verifyCbIfaceObj = (*jenv)->NewGlobalRef(jenv, callbackIface);
        if (g_verifyCbIfaceObj == NULL) {
            printf("error storing global callback interface\n");
        }

        /* set verify mode, register Java callback with wolfSSL */
        wolfSSL_CTX_set_verify(ctx, mode, NativeVerifyCallback);
    }
}

int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store)
{
    JNIEnv*   jenv;
    jint      vmret  = 0;
    jint      retval = -1;
    jclass    excClass = NULL;
    jclass    verifyClass = NULL;
    jmethodID verifyMethod = NULL;
    jobjectRefType refcheck;

    if (!g_vm) {
        /* we can't throw an exception yet, so just return 0 (failure) */
        return 0;
    }

    /* get JNIEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return -101;    /* failed to attach JNIEnv to thread */
        }
    } else if (vmret != JNI_OK) {
        return -102;        /* unable to get JNIEnv from JavaVM */
    }

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLException");
    if( (*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return -103;
    }

    /* check if our stored object reference is valid */
    refcheck = (*jenv)->GetObjectRefType(jenv, g_verifyCbIfaceObj);
    if (refcheck == 2) {

        /* lookup WolfSSLVerifyCallback class from global object ref */
        verifyClass = (*jenv)->GetObjectClass(jenv, g_verifyCbIfaceObj);
        if (!verifyClass) {
            if ((*jenv)->ExceptionOccurred(jenv)) {
                (*jenv)->ExceptionDescribe(jenv);
                (*jenv)->ExceptionClear(jenv);
            }

            (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLVerifyCallback class reference");
            return -104;
        }

        verifyMethod = (*jenv)->GetMethodID(jenv, verifyClass,
                                            "verifyCallback", "(IJ)I");
        if (verifyMethod == 0) {
            if ((*jenv)->ExceptionOccurred(jenv)) {
                (*jenv)->ExceptionDescribe(jenv);
                (*jenv)->ExceptionClear(jenv);
            }

            (*jenv)->ThrowNew(jenv, excClass,
                "Error getting verifyCallback method from JNI");
            return -105;
        }

        retval = (*jenv)->CallIntMethod(jenv, g_verifyCbIfaceObj,
                verifyMethod, preverify_ok, (jlong)(uintptr_t)store);

        if ((*jenv)->ExceptionOccurred(jenv)) {
            /* exception occurred on the Java side during method call */
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            return -106;
        }

    } else {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }

        (*jenv)->ThrowNew(jenv, excClass,
                "Object reference invalid in NativeVerifyCallback");
        return -1;
    }

    return retval;
}

JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLContext_setOptions
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jlong op)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL || ctx == NULL) {
        return 0;
    }

    return (jlong)wolfSSL_CTX_set_options(ctx, (long)op);
}

JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLContext_getOptions
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL || ctx == NULL) {
        return 0;
    }

    return (jlong)wolfSSL_CTX_get_options(ctx);
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_memsaveCertCache
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jbyteArray mem, jint sz,
    jintArray used)
{
#ifdef PERSIST_CERT_CACHE
    int ret;
    int usedTmp;
    unsigned char* memBuf = NULL;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    jclass excClass = NULL;
    (void)jcl;

    if (jenv == NULL || ctx == NULL || mem == NULL || sz <= 0) {
        return (jint)BAD_FUNC_ARG;
    }

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return (jint)SSL_FAILURE;
    }

    memBuf = (unsigned char*)XMALLOC((int)sz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
    if (memBuf == NULL) {
        return (jint)MEMORY_E;
    }
    XMEMSET(memBuf, 0, (int)sz);

    ret = wolfSSL_CTX_memsave_cert_cache(ctx, memBuf, (int)sz, &usedTmp);

    /* set used value for return */
    (*jenv)->SetIntArrayRegion(jenv, used, 0, 1, &usedTmp);
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);

        XFREE(memBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);

        (*jenv)->ThrowNew(jenv, excClass,
                "Failed to set array region in native memsaveCertCache");

        return (jint)SSL_FAILURE;
    }

    /* set jbyteArray for return */
    if (usedTmp >= 0) {
        (*jenv)->SetByteArrayRegion(jenv, mem, 0, usedTmp, (jbyte*)memBuf);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);

            XFREE(memBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);

            (*jenv)->ThrowNew(jenv, excClass,
                    "Failed to set byte region in native memsaveCertCache");

            return (jint)SSL_FAILURE;
        }
    }

    XFREE(memBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);

    return (jint)ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)mem;
    (void)sz;
    (void)used;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_memrestoreCertCache
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jbyteArray mem, jint sz)
{
#ifdef PERSIST_CERT_CACHE
    int ret = WOLFSSL_FAILURE;
    byte* buff = NULL;
    word32 buffSz = 0;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;
    (void)sz;

    if (jenv == NULL || ctx == NULL || mem == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    buff = (byte*)(*jenv)->GetByteArrayElements(jenv, mem, NULL);
    buffSz = (*jenv)->GetArrayLength(jenv, mem);

    if (buff != NULL && buffSz > 0) {
        ret = wolfSSL_CTX_memrestore_cert_cache(ctx, buff, buffSz);
    }

    (*jenv)->ReleaseByteArrayElements(jenv, mem, (jbyte*)buff, JNI_ABORT);

    return (jint)ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)mem;
    (void)sz;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_getCertCacheMemsize
  (JNIEnv* jenv, jobject jcl, jlong ctx)
{
    (void)jenv;
    (void)jcl;
#ifdef PERSIST_CERT_CACHE
    /* wolfSSL checks for null pointer */
    return (jint)wolfSSL_CTX_get_cert_cache_memsize((WOLFSSL_CTX*)(uintptr_t)ctx);
#else
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLContext_setCacheSize
  (JNIEnv* jenv, jobject jcl, jlong ctx, jlong sz)
{
    (void)jenv;
    (void)jcl;

    return (jlong)wolfSSL_CTX_sess_set_cache_size(
                    (WOLFSSL_CTX*)(uintptr_t)ctx, (long)sz);
}


JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLContext_getCacheSize
  (JNIEnv* jenv, jobject jcl, jlong ctx)
{
    (void)jenv;
    (void)jcl;

    return (jlong)wolfSSL_CTX_sess_get_cache_size((WOLFSSL_CTX*)(uintptr_t)ctx);
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setCipherList
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jstring list)
{
    jint ret = 0;
    jclass excClass;
    const char* cipherList;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL) {
        return (jint)SSL_FAILURE;
    }

    if (list == NULL) {
        excClass = (*jenv)->FindClass(jenv, "java/lang/NullPointerException");
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }

        /* throw NullPointerException */
        (*jenv)->ThrowNew(jenv, excClass,
                "Input cipher list is NULL");

        return (jint)SSL_FAILURE;
    }

    cipherList = (*jenv)->GetStringUTFChars(jenv, list, 0);

    ret = (jint) wolfSSL_CTX_set_cipher_list(ctx, cipherList);

    (*jenv)->ReleaseStringUTFChars(jenv, list, cipherList);

    return ret;
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_loadVerifyBuffer
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jbyteArray in, jlong sz, jint format)
{
    int ret = WOLFSSL_FAILURE;
    byte* buff = NULL;
    word32 buffSz = 0;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;
    (void)sz;

    if (jenv == NULL || ctx == NULL || in == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    buff = (byte*)(*jenv)->GetByteArrayElements(jenv, in, NULL);
    buffSz = (*jenv)->GetArrayLength(jenv, in);

    if (buff != NULL && buffSz > 0) {
        ret = wolfSSL_CTX_load_verify_buffer(ctx, buff, buffSz, format);
    }

    (*jenv)->ReleaseByteArrayElements(jenv, in, (jbyte*)buff, JNI_ABORT);

    return (jint)ret;
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_useCertificateBuffer
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jbyteArray in, jlong sz,
   jint format)
{
    int ret = WOLFSSL_FAILURE;
    byte* buff = NULL;
    word32 buffSz = 0;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;
    (void)sz;

    if (jenv == NULL || ctx == NULL || in == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    buff = (byte*)(*jenv)->GetByteArrayElements(jenv, in, NULL);
    buffSz = (*jenv)->GetArrayLength(jenv, in);

    if (buff != NULL && buffSz > 0) {
        ret = wolfSSL_CTX_use_certificate_buffer(ctx, buff, buffSz, format);
    }

    (*jenv)->ReleaseByteArrayElements(jenv, in, (jbyte*)buff, JNI_ABORT);

    return (jint)ret;
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_usePrivateKeyBuffer
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jbyteArray in, jlong sz,
   jint format)
{
    int ret = WOLFSSL_FAILURE;
    byte* buff = NULL;
    word32 buffSz = 0;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;
    (void)sz;

    if (jenv == NULL || ctx == NULL || in == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    buff = (byte*)(*jenv)->GetByteArrayElements(jenv, in, NULL);
    buffSz = (*jenv)->GetArrayLength(jenv, in);

    if (buff != NULL && buffSz > 0) {
        ret = wolfSSL_CTX_use_PrivateKey_buffer(ctx, buff, buffSz, format);
    }

    (*jenv)->ReleaseByteArrayElements(jenv, in, (jbyte*)buff, JNI_ABORT);

    return (jint)ret;
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_useCertificateChainBuffer
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jbyteArray in, jlong sz)
{
    int ret = WOLFSSL_FAILURE;
    byte* buff = NULL;
    word32 buffSz = 0;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;
    (void)sz;

    if (jenv == NULL || ctx == NULL || in == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    buff = (byte*)(*jenv)->GetByteArrayElements(jenv, in, NULL);
    buffSz = (*jenv)->GetArrayLength(jenv, in);

    if (buff != NULL && buffSz > 0) {
        ret = wolfSSL_CTX_use_certificate_chain_buffer(ctx, buff, buffSz);
    }

    (*jenv)->ReleaseByteArrayElements(jenv, in, (jbyte*)buff, JNI_ABORT);

    return (jint)ret;
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_useCertificateChainBufferFormat
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jbyteArray in, jlong sz, jint format)
{
    int ret = WOLFSSL_FAILURE;
    byte* buff = NULL;
    word32 buffSz = 0;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;
    (void)sz;

    if (jenv == NULL || ctx == NULL || in == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    buff = (byte*)(*jenv)->GetByteArrayElements(jenv, in, NULL);
    buffSz = (*jenv)->GetArrayLength(jenv, in);

    if (buff != NULL && buffSz > 0) {
        ret = wolfSSL_CTX_use_certificate_chain_buffer_format(
                ctx, buff, buffSz, format);
    }

    (*jenv)->ReleaseByteArrayElements(jenv, in, (jbyte*)buff, JNI_ABORT);

    return (jint)ret;
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setGroupMessages
  (JNIEnv* jenv, jobject jcl, jlong ctx)
{
    (void)jenv;
    (void)jcl;

    if (ctx == 0) {
        return (jint)BAD_FUNC_ARG;
    }

    return (jint)wolfSSL_CTX_set_group_messages((WOLFSSL_CTX*)(uintptr_t)ctx);
}

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setIORecv(JNIEnv* jenv,
        jobject jcl, jlong ctxPtr)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

    if(ctx != NULL) {
        /* set I/O recv callback */
        wolfSSL_SetIORecv(ctx, NativeIORecvCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when setting IORecv");
    }
}

int NativeIORecvCb(WOLFSSL *ssl, char *buf, int sz, void *ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jbyteArray inData;

    if (!g_vm || !ssl || !buf || !ctx) {
        /* can't throw exception yet, just return error */
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return WOLFSSL_CBIO_ERR_GENERAL;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeIORecvCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference in "
            "NativeIORecvCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID in NativeIORecvCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* find WolfSSLContext.getAssociatedContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID in "
            "NativeIORecvCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* get WolfSSLContext(ctx) object from Java WolfSSLSession object */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeIORecvCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* get WolfSSLContext class reference from object */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference in "
            "NativeIORecvCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* make sure cached recv callback method ID is not null */
    if (!g_sslIORecvMethodId) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Cached recv callback method ID is null in NativeIORecvCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* create jbyteArray to hold received data */
    inData = (*jenv)->NewByteArray(jenv, sz);
    if (!inData) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Error creating jbyteArray in NativeIORecvCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* call Java send callback, ignore native ctx since Java
     * handles it */
    retval = (*jenv)->CallIntMethod(jenv, ctxRef, g_sslIORecvMethodId,
                                (jobject)(*g_cachedSSLObj),
                                inData, (jint)sz);

    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inData);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* copy jbyteArray into char array */
    if (retval >= 0) {
        (*jenv)->GetByteArrayRegion(jenv, inData, 0, retval,
                (jbyte*)buf);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, inData);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return WOLFSSL_CBIO_ERR_GENERAL;
        }
    }

    /* delete local refs, detach JNIEnv from thread */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    (*jenv)->DeleteLocalRef(jenv, inData);
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setIOSend
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
    }

    if (ctx != NULL) {
        /* set I/O send callback */
        wolfSSL_SetIOSend(ctx, NativeIOSendCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when setting IOSend");
    }
}

int NativeIOSendCb(WOLFSSL *ssl, char *buf, int sz, void *ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jmethodID  sendCbMethodId;        /* internalIOSendCallback ID */
    jbyteArray outData;               /* jbyteArray for data to send */

    if (!g_vm || !ssl || !buf || !ctx) {
        /* can't throw exception yet, just return error */
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return WOLFSSL_CBIO_ERR_GENERAL;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeIOSendCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID in NativeIOSendCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* find WolfSSLContext.getAssociatedContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID in "
            "NativeIOSendCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* get WolfSSLContext(ctx) object from Java WolfSSLSession object */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeIOSendCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* get WolfSSLContext class reference from Java land */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference in "
            "NativeIOSendCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    /* call internal I/O recv callback */
    sendCbMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
            "internalIOSendCallback",
            "(Lcom/wolfssl/WolfSSLSession;[BI)I");
    if (!sendCbMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Error getting internalIOSendCallback method from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return WOLFSSL_CBIO_ERR_GENERAL;
    }

    if (sz >= 0)
    {
        /* create jbyteArray to hold received data */
        outData = (*jenv)->NewByteArray(jenv, sz);
        if (!outData) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "Error getting internalIOSendCallback method from JNI");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return WOLFSSL_CBIO_ERR_GENERAL;
        }

        (*jenv)->SetByteArrayRegion(jenv, outData, 0, sz, (jbyte*)buf);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, outData);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return WOLFSSL_CBIO_ERR_GENERAL;
        }

        /* call Java send callback, ignore native ctx since Java
         * handles it */
        retval = (*jenv)->CallIntMethod(jenv, ctxRef, sendCbMethodId,
                                (jobject)(*g_cachedSSLObj), outData, (jint)sz);

        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, outData);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return WOLFSSL_CBIO_ERR_GENERAL;
        }

        /* delete local refs */
        (*jenv)->DeleteLocalRef(jenv, outData);
    }

    /* delete local refs, detach JNIEnv from thread */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setGenCookie
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
    }

#ifdef WOLFSSL_DTLS
    if (ctx != NULL) {
        /* set gen cookie callback */
        wolfSSL_CTX_SetGenCookie(ctx, NativeGenCookieCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when setting "
                "genCookieCb");
    }
#else
    (*jenv)->ThrowNew(jenv, excClass,
            "wolfSSL not compiled with DTLS support (WOLFSSL_DTLS)");
#endif
}

int NativeGenCookieCb(WOLFSSL *ssl, unsigned char *buf, int sz, void *ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jmethodID  cookieCbMethodId;      /* internalGenCookieCallback ID */
    jbyteArray inData;                /* jbyteArray to hold cookie data */

    (void)ctx;

    if (!g_vm || !ssl || !buf) {
        /* can't throw exception yet, just return error */
        return GEN_COOKIE_E;
    }

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return GEN_COOKIE_E;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return GEN_COOKIE_E;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return GEN_COOKIE_E;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeGenCookieCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return GEN_COOKIE_E;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference in "
            "NativeGenCookieCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return GEN_COOKIE_E;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID in "
            "NativeGenCookieCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return GEN_COOKIE_E;
    }

    /* find WolfSSLSession.getAssociatedContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID in "
            "NativeGenCookieCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return GEN_COOKIE_E;
    }

    /* get WolfSSLContext(ctx) object from WolfSSLSession object */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeGenCookieCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return GEN_COOKIE_E;
    }

    /* get WolfSSLContext class reference from Java land */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference in "
            "NativeGenCookieCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return GEN_COOKIE_E;
    }

    /* call internal gen cookie callback */
    cookieCbMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
            "internalGenCookieCallback",
            "(Lcom/wolfssl/WolfSSLSession;[BI)I");

   if (!cookieCbMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Error getting internalGenCookieCallback method from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return GEN_COOKIE_E;
    }

    if (sz >= 0)
    {
        /* create jbyteArray to hold cookie data */
        inData = (*jenv)->NewByteArray(jenv, sz);
        if (!inData) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "Error getting internalGenCookieCallback method from JNI");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return GEN_COOKIE_E;
        }

        /* call Java cookie callback */
        retval = (*jenv)->CallIntMethod(jenv, ctxRef, cookieCbMethodId,
                                    (jobject)(*g_cachedSSLObj),
                                    inData, (jint)sz);

        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, inData);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return GEN_COOKIE_E;
        }

        /* copy jbyteArray into char array */
        if (retval >= 0) {
            (*jenv)->GetByteArrayRegion(jenv, inData, 0, retval,
                    (jbyte*)buf);
            if ((*jenv)->ExceptionOccurred(jenv)) {
                (*jenv)->ExceptionDescribe(jenv);
                (*jenv)->ExceptionClear(jenv);
                (*jenv)->DeleteLocalRef(jenv, ctxRef);
                (*jenv)->DeleteLocalRef(jenv, inData);
                if (needsDetach)
                    (*g_vm)->DetachCurrentThread(g_vm);
                return GEN_COOKIE_E;
            }
        }

        /* delete local refs */
        (*jenv)->DeleteLocalRef(jenv, inData);
    }

    /* delete local refs, detach JNIEnv from thread */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_enableCRL
  (JNIEnv* jenv, jobject jcl, jlong ctx, jint options)
{
    (void)jenv;
    (void)jcl;
#ifdef HAVE_CRL
    if (!ctx)
        return (jint)BAD_FUNC_ARG;

    return (jint)wolfSSL_CTX_EnableCRL((WOLFSSL_CTX*)(uintptr_t)ctx, options);
#else
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_disableCRL
  (JNIEnv* jenv, jobject jcl, jlong ctx)
{
    (void)jenv;
    (void)jcl;
#ifdef HAVE_CRL
    if (!ctx)
        return (jint)BAD_FUNC_ARG;

    return (jint)wolfSSL_CTX_DisableCRL((WOLFSSL_CTX*)(uintptr_t)ctx);
#else
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_loadCRL
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jstring path, jint type,
   jint monitor)
{
#if defined(HAVE_CRL) && !defined(NO_FILESYSTEM)
    int ret;
    const char* crlPath;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL || ctx == NULL || path == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    crlPath = (*jenv)->GetStringUTFChars(jenv, path, 0);

    ret = wolfSSL_CTX_LoadCRL(ctx, crlPath, type, monitor);

    (*jenv)->ReleaseStringUTFChars(jenv, path, crlPath);

    return (jint)ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)path;
    (void)type;
    (void)monitor;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setCRLCb
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jobject cb)
{
#ifdef HAVE_CRL
    int ret = 0;
    jclass excClass;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL || ctx == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    /* release global CRL callback object if set */
    if (g_crlCtxCbIfaceObj != NULL) {
        (*jenv)->DeleteGlobalRef(jenv, g_crlCtxCbIfaceObj);
        g_crlCtxCbIfaceObj = NULL;
    }

    if (cb != NULL) {
        /* store Java CRL callback Interface object */
        g_crlCtxCbIfaceObj = (*jenv)->NewGlobalRef(jenv, cb);

        if (!g_crlCtxCbIfaceObj) {
            excClass = (*jenv)->FindClass(jenv,
                                    "com/wolfssl/WolfSSLJNIException");
            if ((*jenv)->ExceptionOccurred(jenv)) {
                (*jenv)->ExceptionDescribe(jenv);
                (*jenv)->ExceptionClear(jenv);
            }
            (*jenv)->ThrowNew(jenv, excClass,
                    "error storing global missing CTX CRL callback interface");
        }

        ret = wolfSSL_CTX_SetCRL_Cb(ctx, NativeCtxMissingCRLCallback);
    }

    return (jint)ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)cb;
    return (jint)NOT_COMPILED_IN;
#endif
}

#ifdef HAVE_CRL

void NativeCtxMissingCRLCallback(const char* url)
{
    JNIEnv*   jenv;
    jint      vmret  = 0;
    jclass    excClass;
    jclass    crlClass = NULL;
    jmethodID crlMethod;
    jstring missingUrl = NULL;
    jobjectRefType refcheck;

    /* get JNIEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            printf("Failed to attach JNIEnv to thread\n");
        }
    } else if (vmret != JNI_OK) {
        printf("Unable to get JNIEnv from JavaVM\n");
    }

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

    /* check if our stored object reference is valid */
    refcheck = (*jenv)->GetObjectRefType(jenv, g_crlCtxCbIfaceObj);
    if (refcheck == 2) {

        /* lookup WolfSSLMissingCRLCallback class from global object ref */
        crlClass = (*jenv)->GetObjectClass(jenv, g_crlCtxCbIfaceObj);
        if (!crlClass) {
            (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLMissingCRLCallback class reference");
            return;
        }

        crlMethod = (*jenv)->GetMethodID(jenv, crlClass,
                                            "missingCRLCallback",
                                            "(Ljava/lang/String;)V");
        if (crlMethod == 0) {
            if ((*jenv)->ExceptionOccurred(jenv)) {
                (*jenv)->ExceptionDescribe(jenv);
                (*jenv)->ExceptionClear(jenv);
            }

            (*jenv)->ThrowNew(jenv, excClass,
                "Error getting missingCRLCallback method from JNI");
            return;
        }

        /* create jstring from char* */
        missingUrl = (*jenv)->NewStringUTF(jenv, url);

        (*jenv)->CallVoidMethod(jenv, g_crlCtxCbIfaceObj, crlMethod,
                missingUrl);

        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            return;
        }

    } else {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }

        (*jenv)->ThrowNew(jenv, excClass,
                "Object reference invalid in NativeMissingCRLCallback");
    }
}

#endif /* HAVE_CRL */

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_enableOCSP
  (JNIEnv* jenv, jobject jcl, jlong ctx, jlong options)
{
    (void)jenv;
    (void)jcl;
#ifdef HAVE_OCSP
    /* wolfSSL checks for null pointer */
    return (jint)wolfSSL_CTX_EnableOCSP((WOLFSSL_CTX*)(uintptr_t)ctx,
                                        (long)options);
#else
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_disableOCSP
  (JNIEnv* jenv, jobject jcl, jlong ctx)
{
    (void)jenv;
    (void)jcl;
#ifdef HAVE_OCSP
    /* wolfSSL checks for null pointer */
    return (jint)wolfSSL_CTX_DisableOCSP((WOLFSSL_CTX*)(uintptr_t)ctx);
#else
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setOCSPOverrideUrl
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jstring urlString)
{
#ifdef HAVE_OCSP
    jint ret = 0;
    jclass excClass;
    const char* url;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    /* wolfSSL checks ctx for NULL */
    if (jenv == NULL)
        return (jint)BAD_FUNC_ARG;

    if (urlString == NULL)
    {
        excClass = (*jenv)->FindClass(jenv, "java/lang/NullPointerException");
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }

        /* throw NullPointerException */
        (*jenv)->ThrowNew(jenv, excClass,
                "Input URL is NULL in setOCSPOverrideUrl()");

        return (jint)SSL_FAILURE;
    }

    url = (*jenv)->GetStringUTFChars(jenv, urlString, 0);

    ret = (jint) wolfSSL_CTX_SetOCSP_OverrideURL(ctx, url);

    (*jenv)->ReleaseStringUTFChars(jenv, urlString, url);

    return ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)urlString;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setMacEncryptCb
  (JNIEnv* jenv, jobject jcl, jlong ctx)
{
    (void)jcl;

    /* find exception class */
    jclass excClass = (*jenv)->FindClass(jenv,
            "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

#ifdef ATOMIC_USER
    if(ctx) {
        /* set MAC encrypt callback */
        wolfSSL_CTX_SetMacEncryptCb((WOLFSSL_CTX*)(uintptr_t)ctx,
                                    NativeMacEncryptCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when "
                "setting MacEncrypt");
    }
#else
        (*jenv)->ThrowNew(jenv, excClass,
                "wolfSSL not compiled with ATOMIC_USER");
#endif
}

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setDecryptVerifyCb
  (JNIEnv* jenv, jobject jcl, jlong ctx)
{
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

#ifdef ATOMIC_USER
    if(ctx) {
        /* set decrypt/verify callback */
        wolfSSL_CTX_SetDecryptVerifyCb((WOLFSSL_CTX*)(uintptr_t)ctx,
                NativeDecryptVerifyCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when "
                "setting DecryptVerify");
    }
#else
        (*jenv)->ThrowNew(jenv, excClass,
                "wolfSSL not compiled with ATOMIC_USER");
#endif /* ATOMIC_USER */
}

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setVerifyDecryptCb
  (JNIEnv* jenv, jobject jcl, jlong ctx)
{
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

#ifdef ATOMIC_USER
    if(ctx) {
        /* set verify/decrypt callback */
        wolfSSL_CTX_SetVerifyDecryptCb((WOLFSSL_CTX*)(uintptr_t)ctx,
                NativeVerifyDecryptCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when "
                "setting VerifyDecrypt");
    }
#else
        (*jenv)->ThrowNew(jenv, excClass,
                "wolfSSL not compiled with ATOMIC_USER");
#endif /* ATOMIC_USER */
}

#ifdef ATOMIC_USER

int NativeMacEncryptCb(WOLFSSL* ssl, unsigned char* macOut,
        const unsigned char* macIn, unsigned int macInSz, int macContent,
        int macVerify, unsigned char* encOut, const unsigned char* encIn,
        unsigned int encSz, void* ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jmethodID  macEncryptMethodId;    /* internalMacEncryptCallback ID */

    jobject    macOutBB = NULL;
    jobject    encOutBB = NULL;
    jobject    encInBB = NULL;

    int        hmacSize;
    jbyteArray j_macIn;

    (void)ctx;

    if (!g_vm || !ssl || !macOut || !macIn || !encOut || !encIn) {
        return -1;
    }

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return -1;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return -1;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeMacEncryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference in "
            "NativeMacEncryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID in "
            "NativeMacEncryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* find getContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID in "
            "NativeMacEncryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext ctx object from Java land */
    ctxRef = (*jenv)->CallObjectMethod(jenv,
            (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeMacEncryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext class reference from Java land */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference in "
            "NativeMacEncryptCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get ref to internal MAC encrypt callback */
    macEncryptMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
            "internalMacEncryptCallback",
            "(Lcom/wolfssl/WolfSSLSession;Ljava/nio/ByteBuffer;"
            "[BJIILjava/nio/ByteBuffer;Ljava/nio/ByteBuffer;J)I");

    if (!macEncryptMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Error getting internalMacEncryptCallback method from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        retval = -1;
    }

    if (retval == 0)
    {
        hmacSize = wolfSSL_GetHmacSize((WOLFSSL*)ssl);

        /* create ByteBuffer to wrap macOut */
        macOutBB = (*jenv)->NewDirectByteBuffer(jenv, macOut, hmacSize);
        if (!macOutBB) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "failed to create macOut ByteBuffer");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        /* create jbyteArray to hold macIn, since macIn is read-only */
        j_macIn = (*jenv)->NewByteArray(jenv, macInSz);
        if (!j_macIn) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "failed to create macIn ByteBuffer");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, macOutBB);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        (*jenv)->SetByteArrayRegion(jenv, j_macIn, 0, macInSz,
                (jbyte*)macIn);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, macOutBB);
            (*jenv)->DeleteLocalRef(jenv, j_macIn);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        /* create ByteBuffer to wrap encOut */
        encOutBB = (*jenv)->NewDirectByteBuffer(jenv, encOut, encSz);
        if (!encOutBB) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "failed to create encOut ByteBuffer");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, macOutBB);
            (*jenv)->DeleteLocalRef(jenv, j_macIn);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        /* create ByteBuffer to wrap encIn - use encOut b/c it's not a
         * const, but points to same memory. This will be important
         * in Java-land in order to have an updated encIn array after
         * doing the MAC operation. */
        encInBB = (*jenv)->NewDirectByteBuffer(jenv, encOut, encSz);
        if (!encInBB) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "failed to create encIn ByteBuffer");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, macOutBB);
            (*jenv)->DeleteLocalRef(jenv, j_macIn);
            (*jenv)->DeleteLocalRef(jenv, encOutBB);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        /* call Java MAC/encrypt callback */
        retval = (*jenv)->CallIntMethod(jenv, ctxRef, macEncryptMethodId,
                (jobject)(*g_cachedSSLObj), macOutBB, j_macIn, (jlong)macInSz,
                macContent, macVerify, encOutBB, encInBB, (jlong)encSz);

        if ((*jenv)->ExceptionOccurred(jenv) || retval != 0) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->ThrowNew(jenv, excClass,
                "Call to Java callback failed in NativeMacEncryptCb");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, macOutBB);
            (*jenv)->DeleteLocalRef(jenv, j_macIn);
            (*jenv)->DeleteLocalRef(jenv, encOutBB);
            (*jenv)->DeleteLocalRef(jenv, encInBB);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        /* delete local refs */
        (*jenv)->DeleteLocalRef(jenv, macOutBB);
        (*jenv)->DeleteLocalRef(jenv, j_macIn);
        (*jenv)->DeleteLocalRef(jenv, encOutBB);
        (*jenv)->DeleteLocalRef(jenv, encInBB);
    }

    /* detach JNIEnv from thread */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

int  NativeDecryptVerifyCb(WOLFSSL* ssl, unsigned char* decOut,
        const unsigned char* decIn, unsigned int decSz, int content,
        int verify, unsigned int* padSz, void* ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jmethodID  decryptVerifyMethodId;

    jbyteArray j_decIn;
    jlongArray j_padSz;

    jobject    decOutBB = NULL;
    jlong      tmpVal = 0;

    (void)ctx;

    if (!g_vm || !ssl || !decOut || !decIn || !padSz) {
        return -1;
    }

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return -1;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return -1;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeMacEncryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference in "
            "NativeMacEncryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID "
            "in NativeDecryptVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* find getContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID "
            "in NativeDecryptVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext ctx object from Java land */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeDecryptVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext class reference from Java land */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference "
            "in NativeDecryptVerifyCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call internal decrypt/verify callback */
    decryptVerifyMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
            "internalDecryptVerifyCallback",
            "(Lcom/wolfssl/WolfSSLSession;Ljava/nio/ByteBuffer;[BJII[J)I");

    if (!decryptVerifyMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Error getting internalDecryptVerifyCallback method "
                "from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    if (retval == 0)
    {
        /* create ByteBuffer to wrap decOut */
        decOutBB = (*jenv)->NewDirectByteBuffer(jenv, decOut, decSz);
        if (!decOutBB) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "failed to create decOut ByteBuffer");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        /* create jbyteArray to hold decIn */
        j_decIn = (*jenv)->NewByteArray(jenv, decSz);
        if (!j_decIn) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "failed to create decIn ByteArray");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, decOutBB);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        (*jenv)->SetByteArrayRegion(jenv, j_decIn, 0, decSz, (jbyte*)decIn);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, decOutBB);
            (*jenv)->DeleteLocalRef(jenv, j_decIn);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        /* create jlongArray to hold padSz, since we need to use it as
         * an OUTPUT parameter from Java. Only needs to have 1 element */
        j_padSz = (*jenv)->NewLongArray(jenv, 1);
        if (!j_padSz) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "failed to create padSz longArray");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, decOutBB);
            (*jenv)->DeleteLocalRef(jenv, j_decIn);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        /* call Java decrypt/verify callback, java layer handles
         * adding decrypt/verify CTX reference */
        retval = (*jenv)->CallIntMethod(jenv, ctxRef, decryptVerifyMethodId,
                (jobject)(*g_cachedSSLObj), decOutBB, j_decIn, (jlong)decSz,
                content, verify, j_padSz);

        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, decOutBB);
            (*jenv)->DeleteLocalRef(jenv, j_decIn);
            (*jenv)->DeleteLocalRef(jenv, j_padSz);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        if (retval == 0) {
            /* copy j_padSz into padSz */
            (*jenv)->GetLongArrayRegion(jenv, j_padSz, 0, 1, &tmpVal);
            if ((*jenv)->ExceptionOccurred(jenv)) {
                (*jenv)->ExceptionDescribe(jenv);
                (*jenv)->ExceptionClear(jenv);
                (*jenv)->DeleteLocalRef(jenv, ctxRef);
                (*jenv)->DeleteLocalRef(jenv, decOutBB);
                (*jenv)->DeleteLocalRef(jenv, j_decIn);
                (*jenv)->DeleteLocalRef(jenv, j_padSz);
                if (needsDetach)
                    (*g_vm)->DetachCurrentThread(g_vm);
                return -1;
            }
            *padSz = (unsigned int)tmpVal;
        }

        /* delete local refs */
        (*jenv)->DeleteLocalRef(jenv, decOutBB);
        (*jenv)->DeleteLocalRef(jenv, j_decIn);
        (*jenv)->DeleteLocalRef(jenv, j_padSz);
    }

    /* delete local refs, detach JNIEnv from thread */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

int NativeVerifyDecryptCb(WOLFSSL* ssl, unsigned char* decOut,
        const unsigned char* decIn, unsigned int decSz, int content,
        int macVerify, unsigned int* padSz, void* ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */
    int        hmacSize = 0;          /* WOLFSSL HMAC digest size */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jmethodID  verifyDecryptMethodId;

    jbyteArray j_decIn;
    jlongArray j_padSz;

    jobject    decOutBB = NULL;
    jlong      tmpVal = 0;

    (void)ctx;

    if (!g_vm || !ssl || !decOut || !decIn || !padSz) {
        return -1;
    }

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return -1;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return -1;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeVerifyDecryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference in "
            "NativeVerifyDecryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID "
            "in NativeVerifyDecryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* find getContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID "
            "in NativeVerifyDecryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext ctx object from Java land */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeVerifyDecryptCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext class reference from Java land */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference "
            "in NativeVerifyDecryptCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call internal verify/decrypt callback */
    verifyDecryptMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
            "internalVerifyDecryptCallback",
            "(Lcom/wolfssl/WolfSSLSession;Ljava/nio/ByteBuffer;[BJII[J)I");

    if (!verifyDecryptMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Error getting internalVerifyDecryptCallback method "
                "from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    if (retval == 0) {

        /* Get WOLFSSL HMAC digest size, decOut holds decSz + hmacSize */
        hmacSize = wolfSSL_GetHmacSize((WOLFSSL*)ssl);

        /* create ByteBuffer to wrap decOut */
        decOutBB = (*jenv)->NewDirectByteBuffer(jenv, decOut, decSz + hmacSize);
        if (!decOutBB) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "failed to create decOut ByteBuffer");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        /* create jbyteArray to hold decIn */
        j_decIn = (*jenv)->NewByteArray(jenv, decSz);
        if (!j_decIn) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "failed to create decIn ByteArray");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, decOutBB);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        (*jenv)->SetByteArrayRegion(jenv, j_decIn, 0, decSz, (jbyte*)decIn);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, decOutBB);
            (*jenv)->DeleteLocalRef(jenv, j_decIn);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        /* create jlongArray to hold padSz, since we need to use it as
         * an OUTPUT parameter from Java. Only needs to have 1 element */
        j_padSz = (*jenv)->NewLongArray(jenv, 1);
        if (!j_padSz) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "failed to create padSz longArray");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, decOutBB);
            (*jenv)->DeleteLocalRef(jenv, j_decIn);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        /* call Java verify/decrypt callback, java layer handles
         * adding verify/decrypt CTX reference */
        retval = (*jenv)->CallIntMethod(jenv, ctxRef, verifyDecryptMethodId,
                (jobject)(*g_cachedSSLObj), decOutBB, j_decIn, (jlong)decSz,
                content, macVerify, j_padSz);

        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, decOutBB);
            (*jenv)->DeleteLocalRef(jenv, j_decIn);
            (*jenv)->DeleteLocalRef(jenv, j_padSz);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }

        if (retval == 0) {
            /* copy j_padSz into padSz */
            (*jenv)->GetLongArrayRegion(jenv, j_padSz, 0, 1, &tmpVal);
            if ((*jenv)->ExceptionOccurred(jenv)) {
                (*jenv)->ExceptionDescribe(jenv);
                (*jenv)->ExceptionClear(jenv);
                (*jenv)->DeleteLocalRef(jenv, ctxRef);
                (*jenv)->DeleteLocalRef(jenv, decOutBB);
                (*jenv)->DeleteLocalRef(jenv, j_decIn);
                (*jenv)->DeleteLocalRef(jenv, j_padSz);
                if (needsDetach)
                    (*g_vm)->DetachCurrentThread(g_vm);
                return -1;
            }
            *padSz = (unsigned int)tmpVal;
        }

        /* delete local refs */
        (*jenv)->DeleteLocalRef(jenv, decOutBB);
        (*jenv)->DeleteLocalRef(jenv, j_decIn);
        (*jenv)->DeleteLocalRef(jenv, j_padSz);
    }

    /* delete local refs, detach JNIEnv from thread */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

#endif /* ATOMIC_USER */

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setEccSignCb
  (JNIEnv* jenv, jobject jcl, jlong ctx)
{
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

#if defined(HAVE_PK_CALLBACKS) && defined(HAVE_ECC)
    if(ctx) {
        /* set ECC sign callback */
        wolfSSL_CTX_SetEccSignCb((WOLFSSL_CTX*)(uintptr_t)ctx, NativeEccSignCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when setting "
                "EccSignCb");
    }
#else
     (*jenv)->ThrowNew(jenv, excClass,
             "wolfSSL not compiled with PK Callback support "
             "(HAVE_PK_CALLBACKS)");
#endif
}

#if defined(HAVE_PK_CALLBACKS) && defined(HAVE_ECC)

int  NativeEccSignCb(WOLFSSL* ssl, const unsigned char* in, unsigned int inSz,
        unsigned char* out, unsigned int* outSz, const unsigned char* keyDer,
        unsigned int keySz, void* ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI Environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jmethodID  eccSignMethodId;

    jlongArray j_outSz;

    jobject    outBB = NULL;
    jobject    inBB = NULL;
    jobject    keyDerBB = NULL;
    jlong      tmpVal = 0;

    (void)ctx;

    if (!g_vm || !ssl || !in || !out || !outSz || !keyDer)
        return -1;

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return -1;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return -1;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeEccSignCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference in "
            "NativeEccSignCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID "
            "in NativeEccSignCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* find getContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID "
            "in NativeEccSignCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext ctx object from Java land */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeEccSignCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext class reference from Java land */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference "
            "in NativeEccSignCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call internal decrypt/verify callback */
    eccSignMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
            "internalEccSignCallback",
            "(Lcom/wolfssl/WolfSSLSession;Ljava/nio/ByteBuffer;"
            "JLjava/nio/ByteBuffer;[JLjava/nio/ByteBuffer;J)I");

    if (!eccSignMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Error getting internalEccSignCallback method "
                "from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap out */
    outBB = (*jenv)->NewDirectByteBuffer(jenv, out, *outSz);
    if (!outBB) {
        (*jenv)->ThrowNew(jenv, excClass,
                "failed to create eccSign out ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap in */
    inBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)in, inSz);
    if (!inBB) {
        (*jenv)->ThrowNew(jenv, excClass,
                "failed to create eccSign in ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap keyDer */
    keyDerBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)keyDer, keySz);
    if (!keyDerBB) {
        (*jenv)->ThrowNew(jenv, excClass,
                "failed to create eccSign keyDer ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create jlongArray to hold outSz, since we need to use it as
     * an OUTPUT parameter from Java. Only needs to have 1 element */
    j_outSz = (*jenv)->NewLongArray(jenv, 1);
    if (!j_outSz) {
        (*jenv)->ThrowNew(jenv, excClass,
                "failed to create outSz longArray");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        (*jenv)->DeleteLocalRef(jenv, keyDerBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* copy outSz into j_outSz */
    (*jenv)->SetLongArrayRegion(jenv, j_outSz, 0, 1, (jlong*)outSz);
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        (*jenv)->ThrowNew(jenv, excClass,
                "failed to set j_outSz longArray");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        (*jenv)->DeleteLocalRef(jenv, keyDerBB);
        (*jenv)->DeleteLocalRef(jenv, j_outSz);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call Java ECC sign callback, java layer handles
     * adding decrypt/verify CTX reference */
    retval = (*jenv)->CallIntMethod(jenv, ctxRef, eccSignMethodId,
            (jobject)(*g_cachedSSLObj), inBB, (jlong)inSz, outBB, j_outSz,
            keyDerBB, (jlong)keySz);

    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
    }

    if (retval == 0) {
        /* copy j_outSz into outSz */
        (*jenv)->GetLongArrayRegion(jenv, j_outSz, 0, 1, &tmpVal);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, outBB);
            (*jenv)->DeleteLocalRef(jenv, inBB);
            (*jenv)->DeleteLocalRef(jenv, keyDerBB);
            (*jenv)->DeleteLocalRef(jenv, j_outSz);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }
        *outSz = (unsigned int)tmpVal;
    }

    /* delete local refs */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    (*jenv)->DeleteLocalRef(jenv, outBB);
    (*jenv)->DeleteLocalRef(jenv, inBB);
    (*jenv)->DeleteLocalRef(jenv, keyDerBB);
    (*jenv)->DeleteLocalRef(jenv, j_outSz);

    /* detach JNIEnv from thread */
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

#endif /* HAVE_PK_CALLBACKS */

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setEccVerifyCb
  (JNIEnv* jenv, jobject jcl, jlong ctx)
{
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

#if defined(HAVE_PK_CALLBACKS) && defined(HAVE_ECC)
    if(ctx) {
        /* set ECC verify callback */
        wolfSSL_CTX_SetEccVerifyCb((WOLFSSL_CTX*)(uintptr_t)ctx,
                                   NativeEccVerifyCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when setting "
                "EccVerifyCb");
    }
#else
    (*jenv)->ThrowNew(jenv, excClass,
            "wolfSSL not compiled with PK Callback support "
            "(HAVE_PK_CALLBACKS)");
#endif
}

#if defined(HAVE_PK_CALLBACKS) && defined(HAVE_ECC)

int  NativeEccVerifyCb(WOLFSSL* ssl, const unsigned char* sig,
        unsigned int sigSz, const unsigned char* hash, unsigned int hashSz,
        const unsigned char* keyDer, unsigned int keySz, int* result,
        void* ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI Environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jmethodID  eccVerifyMethodId;
    jintArray  j_result;

    jobject    sigBB = NULL;
    jobject    hashBB = NULL;
    jobject    keyDerBB = NULL;
    jint       tmpVal = 0;

    (void)ctx;

    if (!g_vm || !ssl || !sig || !hash || !keyDer || !result)
        return -1;

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return -1;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return -1;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeEccVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference in "
            "NativeEccVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID "
            "in NativeEccVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* find getContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID "
            "in NativeEccVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext ctx object from Java land */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeEccVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext class reference from Java land */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference "
            "in NativeEccVerifyCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call internal ECC verify callback */
    eccVerifyMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
            "internalEccVerifyCallback",
            "(Lcom/wolfssl/WolfSSLSession;Ljava/nio/ByteBuffer;"
            "JLjava/nio/ByteBuffer;JLjava/nio/ByteBuffer;J[I)I");

    if (!eccVerifyMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Error getting internalEccVerifyCallback method from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'sig' */
    sigBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)sig, sigSz);
    if (!sigBB) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Failed to create eccVerify out ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'hash' */
    hashBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)hash, hashSz);
    if (!hashBB) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Failed to create eccVerify hash ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, sigBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'keyDer' */
    keyDerBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)keyDer, keySz);
    if (!keyDerBB) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Failed to create eccVerify keyDer ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, sigBB);
        (*jenv)->DeleteLocalRef(jenv, hashBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create jintArray to hold result, since we need to use it as
     * an OUTPUT parameter from Java. Only needs to have 1 element */
    j_result = (*jenv)->NewIntArray(jenv, 1);
    if (!j_result) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Failed to create result intArray in EccVerifyCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, sigBB);
        (*jenv)->DeleteLocalRef(jenv, hashBB);
        (*jenv)->DeleteLocalRef(jenv, keyDerBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call Java ECC verify callback, java layer handles
     * adding CTX reference */
    retval = (*jenv)->CallIntMethod(jenv, ctxRef, eccVerifyMethodId,
            (jobject)(*g_cachedSSLObj), sigBB, (jlong)sigSz, hashBB,
            (jlong)hashSz, keyDerBB, (jlong)keySz, j_result);

    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, sigBB);
        (*jenv)->DeleteLocalRef(jenv, hashBB);
        (*jenv)->DeleteLocalRef(jenv, keyDerBB);
        (*jenv)->DeleteLocalRef(jenv, j_result);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* copy j_result into result */
    (*jenv)->GetIntArrayRegion(jenv, j_result, 0, 1, &tmpVal);
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, sigBB);
        (*jenv)->DeleteLocalRef(jenv, hashBB);
        (*jenv)->DeleteLocalRef(jenv, keyDerBB);
        (*jenv)->DeleteLocalRef(jenv, j_result);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }
    *result = tmpVal;

    /* delete local refs */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    (*jenv)->DeleteLocalRef(jenv, sigBB);
    (*jenv)->DeleteLocalRef(jenv, hashBB);
    (*jenv)->DeleteLocalRef(jenv, keyDerBB);
    (*jenv)->DeleteLocalRef(jenv, j_result);

    /* detach JNIEnv from thread */
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

#endif /* HAVE_PK_CALLBACKS */

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setEccSharedSecretCb
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr)
{
#if defined(HAVE_PK_CALLBACKS) && defined(HAVE_ECC)
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if(ctx != NULL) {
        /* set ECC shared secret callback */
        wolfSSL_CTX_SetEccSharedSecretCb(ctx, NativeEccSharedSecretCb);

    } else {
        throwWolfSSLJNIExceptionWithMsg(jenv,
                "Input WolfSSLContext object was null when setting "
                "EccSharedSecretCb", 0);
    }
#else
    (void)ctxPtr;
    (void)jcl;
    throwWolfSSLJNIExceptionWithMsg(jenv,
             "wolfSSL not compiled with PK Callback support "
             "(HAVE_PK_CALLBACKS)", 0);
#endif
}

#if defined(HAVE_PK_CALLBACKS) && defined(HAVE_ECC)

/* Get MethodID from provided object, method name, and method signature.
 * Stores MethodID in mid.
 * return 0 on success, negative on error. */
static int GetMethodIDFromObject(JNIEnv* jenv, jobject obj,
               const char* methodName, const char* methodSig,
               jmethodID* mid)
{
    jclass class;

    if (jenv == NULL || methodName == NULL || methodSig == NULL)
        return BAD_FUNC_ARG;

    class = (*jenv)->GetObjectClass(jenv, obj);
    if (!class) {
        printf("GetObjectClass failed in GetMethodIDFromObject");
        return -1;
    }

    *mid = (*jenv)->GetMethodID(jenv, class, methodName, methodSig);
    if (*mid == NULL) {
        printf("GetMethodID failed in GetMethodIDFromObject");
        CheckException(jenv);
        return -1;
    }

    return 0;
}

/* get WolfSSLContext from WolfSSLSession ID, storing context in ctxRef.
 * return 0 on success, negative on error */
static int GetWolfSSLContextFromSessionObj(JNIEnv* jenv, jobject* sessObj,
                                    jobject* ctxRef)
{
    jclass     sessClass;       /* WolfSSLSession class */
    jmethodID  getCtxMethodId;  /* WolfSSLSession->getAssCtxPtr() ID */

    if (jenv == NULL || sessObj == NULL || ctxRef == NULL)
        return BAD_FUNC_ARG;

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*sessObj));
    if (!sessClass) {
        printf("Can't get native WolfSSLSession class reference in "
               "GetWolfSSLContextFromSessionObj");
        return -1;
    }

    /* find getContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
                "getAssociatedContextPtr",
                "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        CheckException(jenv);
        printf("Can't get getAssociatedContextPtr() methodID in "
               "GetWolfSSLContextFromSessionObj");
        return -1;
    }

    /* get WolfSSLContext ctx object from Java land */
    *ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*sessObj),
                                        getCtxMethodId);
    CheckException(jenv);
    if (!(*ctxRef)) {
        printf("Can't get WolfSSLContext object in "
               "GetWolfSSLContextFromSessionObj");
        return -1;
    }

    return 0;
}

/* Java JCE uses ECC public keys that are DER formatted with a header
 * that specifies the key type and curve. wolfSSL requires a raw ECC
 * public key for ECC shared secret callback.  This function strips off
 * the header, giving the callback the raw key.
 *
 * publicKeyDer - IN/OUT ECC DER public key, ECC raw public key
 * pubKeySz     - size of publicKeyDer buffer
 *
 * returns size of new publicKeyDer contents
 */
static int StripEccPublicDerHeaderInline(byte* publicKeyDer, word32 pubKeySz)
{
    int ret = 0;
    word32 idx = 0;
    word32 keySz = 0;
    ecc_key tmpKey;

    if (publicKeyDer == NULL)
        return BAD_FUNC_ARG;

    /* DER encoded header will start with 0x30 sequence */
    if (publicKeyDer[0] == (ASN_SEQUENCE | ASN_CONSTRUCTED)) {

        keySz = pubKeySz;

        ret = wc_ecc_init(&tmpKey);
        if (ret < 0)
            return ret;

        ret = wc_EccPublicKeyDecode(publicKeyDer, &idx,
                                    &tmpKey, pubKeySz);
        if (ret < 0) {
            wc_ecc_free(&tmpKey);
            return ret;
        }

        ret = wc_ecc_export_x963(&tmpKey, publicKeyDer, &keySz);
        if (ret < 0) {
            wc_ecc_free(&tmpKey);
            return ret;
        }

        wc_ecc_free(&tmpKey);
        return keySz;
    }

    return ret;
}

int  NativeEccSharedSecretCb(WOLFSSL* ssl, ecc_key* otherKey,
        unsigned char* publicKeyDer, unsigned int* pubKeySz,
        unsigned char* out, unsigned int* outlen, int side, void* ctx)
{
    int        ret;
    jint       retval = 0;

    JNIEnv*    jenv = NULL;           /* JNI Environment */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */

    jobject    ctxRef;                /* WolfSSLContext object */
    jmethodID  eccSharedSecretMethodId;

    jbyteArray j_pubKeyDerSz;
    jbyteArray j_outSz;

    jclass     eccKeyClass;
    jmethodID  eccKeyMethodId;
    jobject    eccKeyObject;

    ecc_key    tmpKey;                /* tmp key, used in conversion */
    byte*      tmpKeyDer = NULL;      /* tmp der buffer, used in conversion */
    word32     tmpKeyDerSz;           /* stores size of tmpKeyDer */

    jobject    pubKeyDerBB = NULL;
    jobject    outBB = NULL;
    jlong      tmpVal = 0;

    (void)ctx;

    if (!g_vm || !ssl || !otherKey || !publicKeyDer || !pubKeySz ||
        !out || !outlen)
        return -1;

    /* get JavaEnv from JavaVM */
    ret = GetJNIEnvFromVM(g_vm, &jenv, &needsDetach);
    if (ret != 0)
        return ret;

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        throwWolfSSLJNIExceptionWithMsg(jenv,
            "Can't get WolfSSLSession reference in NativeEccSharedSecretCb",
            needsDetach);
        return -1;
    }

    /* get WolfSSLContext ctx object from Java land */
    ret = GetWolfSSLContextFromSessionObj(jenv, g_cachedSSLObj, &ctxRef);
    if (ret != 0) {
        CheckException(jenv);
        throwWolfSSLJNIExceptionWithMsg(jenv,
            "Unable to get WolfSSLContext reference", needsDetach);
        return ret;
    }

    /* find internal ecc shared secret callback */
    ret = GetMethodIDFromObject(jenv, ctxRef,
            "internalEccSharedSecretCallback",
            "(Lcom/wolfssl/WolfSSLSession;Lcom/wolfssl/WolfCryptEccKey;"
            "Ljava/nio/ByteBuffer;[JLjava/nio/ByteBuffer;[JI)I",
            &eccSharedSecretMethodId);

    if (ret != 0) {
        throwWolfSSLJNIExceptionWithMsg(jenv,
            "GetMethodIDFromObject failed in NativeEccSharedSecretCb",
            needsDetach);
        return -1;
    }

    /* SETUP: otherKey - holds server's public key on client end, otherwise
     * holds server's private key on server end. */

    /* find WolfCryptEccKey class */
    eccKeyClass = (*jenv)->FindClass(jenv,
        "com/wolfssl/WolfCryptEccKey");
    if (!eccKeyClass) {
        CheckException(jenv);
        throwWolfSSLJNIExceptionWithMsg(jenv,
            "Error finding WolfCryptEccKey class for ECC "
            "shared secret callback",
            needsDetach);
        return -1;
    }

    /* find WolfCryptEccKey constructor */
    eccKeyMethodId = (*jenv)->GetMethodID(jenv, eccKeyClass,
                                          "<init>", "(J)V");
    if (!eccKeyMethodId) {
        CheckException(jenv);
        throwWolfSSLJNIExceptionWithMsg(jenv,
                "Error getting WolfCryptEccKey constructor method ID in "
                "ECC shared secret callback", needsDetach);
        return -1;
    }

    /* create new WolfCryptEccKey object to return otherKey */
    eccKeyObject = (*jenv)->NewObject(jenv, eccKeyClass, eccKeyMethodId,
                                      (jlong)(uintptr_t)otherKey);
    if (!eccKeyObject) {
        CheckException(jenv);
        throwWolfSSLJNIExceptionWithMsg(jenv,
                "Error creating WolfCryptEccKey object in native ECC "
                "shared secret callback", needsDetach);
        return -1;
    }

    /* SETUP: publicKeyDer - create ByteBuffer to wrap publicKeyDer.
     * publicKeyDer is an output buffer for client's public key to be
     * placed (client side), otherwise it contains the client's public
     * key (server side) */
    if (side == WOLFSSL_SERVER_END) {

        /* publicKeyDer is ECC public key without DER header. Add header
         * back on here before giving to Java callback, since we know
         * curve details now */

        retval = wc_ecc_init(&tmpKey);
        if (retval != 0) {
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, eccKeyObject);
            throwWolfSSLJNIExceptionWithMsg(jenv, "Failed to init "
                "eccSharedSecret tmpKey", needsDetach);
            return -1;
        }

        retval = wc_ecc_import_x963_ex(publicKeyDer, *pubKeySz, &tmpKey,
                                       otherKey->dp->id);
        if (retval != 0) {
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, eccKeyObject);
            wc_ecc_free(&tmpKey);
            throwWolfSSLJNIExceptionWithMsg(jenv, "Failed to import "
                "eccSharedSecret publicKeyDer", needsDetach);
            return -1;
        }

        tmpKeyDer = (byte*)XMALLOC(ECC_BUFSIZE, otherKey->heap,
                                   DYNAMIC_TYPE_TMP_BUFFER);
        if (!tmpKeyDer) {
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, eccKeyObject);
            wc_ecc_free(&tmpKey);
            throwWolfSSLJNIExceptionWithMsg(jenv, "Failed to allocate "
                "eccSharedSecret tmpKeyDer", needsDetach);
            return -1;
        }

        tmpKeyDerSz = wc_EccPublicKeyToDer(&tmpKey, tmpKeyDer, ECC_BUFSIZE, 1);
        if (tmpKeyDerSz <= 0) {
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, eccKeyObject);
            wc_ecc_free(&tmpKey);
            XFREE(tmpKeyDer, otherKey->heap, DYNAMIC_TYPE_TMP_BUFFER);
            throwWolfSSLJNIExceptionWithMsg(jenv, "Failed to export "
                "eccSharedSecret public key to DER", needsDetach);
            return -1;
        }

        wc_ecc_free(&tmpKey);

        pubKeyDerBB = (*jenv)->NewDirectByteBuffer(jenv, tmpKeyDer,
                                                   tmpKeyDerSz);
        if (!pubKeyDerBB) {
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, eccKeyObject);
            XFREE(tmpKeyDer, otherKey->heap, DYNAMIC_TYPE_TMP_BUFFER);
            throwWolfSSLJNIExceptionWithMsg(jenv, "Failed to create "
                "eccSharedSecret publicKeyDer ByteBuffer", needsDetach);
            return -1;
        }

    } else {
        /* CLIENT side */
        pubKeyDerBB = (*jenv)->NewDirectByteBuffer(jenv, publicKeyDer,
                *pubKeySz);
        if (!pubKeyDerBB) {
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, eccKeyObject);
            throwWolfSSLJNIExceptionWithMsg(jenv, "Failed to create "
                "eccSharedSecret publicKeyDer ByteBuffer", needsDetach);
            return -1;
        }
    }

    /* SETUP: publicKeyDerSz
     * create jlongArray to hold publicKeyDerSz, since we need to use it as an
     * OUTPUT parameter from Java. Only needs to have 1 element. */
    j_pubKeyDerSz = (*jenv)->NewLongArray(jenv, 1);
    if (!j_pubKeyDerSz) {
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, eccKeyObject);
        (*jenv)->DeleteLocalRef(jenv, pubKeyDerBB);
        if (side == WOLFSSL_SERVER_END)
            XFREE(tmpKeyDer, otherKey->heap, DYNAMIC_TYPE_TMP_BUFFER);
        throwWolfSSLJNIExceptionWithMsg(jenv, "Failed to create "
            "eccSharedSecret array for publicKeyDerSz", needsDetach);
        return -1;
    }

    /* SETUP: out
     * create ByteBuffer to wrap out */
    outBB = (*jenv)->NewDirectByteBuffer(jenv, out, *outlen);
    if (!outBB) {
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, eccKeyObject);
        (*jenv)->DeleteLocalRef(jenv, pubKeyDerBB);
        (*jenv)->DeleteLocalRef(jenv, j_pubKeyDerSz);
        if (side == WOLFSSL_SERVER_END)
            XFREE(tmpKeyDer, otherKey->heap, DYNAMIC_TYPE_TMP_BUFFER);
        throwWolfSSLJNIExceptionWithMsg(jenv, "Failed to create "
            "eccSharedSecret out ByteBuffer", needsDetach);
        return -1;
    }

    /* SETUP: outSz
     * create jlongArray to hold outSz, since we need to use it as an
     * OUTPUT parameter from Java. Only needs to have 1 element. */
    j_outSz = (*jenv)->NewLongArray(jenv, 1);
    if (!j_outSz) {
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, eccKeyObject);
        (*jenv)->DeleteLocalRef(jenv, pubKeyDerBB);
        (*jenv)->DeleteLocalRef(jenv, j_pubKeyDerSz);
        if (side == WOLFSSL_SERVER_END)
            XFREE(tmpKeyDer, otherKey->heap, DYNAMIC_TYPE_TMP_BUFFER);
        throwWolfSSLJNIExceptionWithMsg(jenv, "Failed to create "
            "eccSharedSecret array for outSz", needsDetach);
        return -1;
    }

    /* call Java ECC shared secret callback, java layer handles
     * adding shared secret CTX reference */
    retval = (*jenv)->CallIntMethod(jenv, ctxRef, eccSharedSecretMethodId,
            (jobject)(*g_cachedSSLObj), eccKeyObject, pubKeyDerBB,
            j_pubKeyDerSz, outBB, j_outSz, (jint)side);

    CheckException(jenv);
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    (*jenv)->DeleteLocalRef(jenv, eccKeyObject);
    (*jenv)->DeleteLocalRef(jenv, pubKeyDerBB);
    if (side == WOLFSSL_SERVER_END) {
        XFREE(tmpKeyDer, otherKey->heap, DYNAMIC_TYPE_TMP_BUFFER);
    }

    if (retval == 0) {
        /* copy j_outSz into outlen, j_pubKeyDerSz into pubKeySz */
        (*jenv)->GetLongArrayRegion(jenv, j_outSz, 0, 1, &tmpVal);
        if (CheckException(jenv)) {
            (*jenv)->DeleteLocalRef(jenv, j_pubKeyDerSz);
            (*jenv)->DeleteLocalRef(jenv, j_outSz);
            (*jenv)->DeleteLocalRef(jenv, outBB);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }
        *outlen = (unsigned int)tmpVal;

        if (side == WOLFSSL_CLIENT_END) {
            (*jenv)->GetLongArrayRegion(jenv, j_pubKeyDerSz, 0, 1, &tmpVal);
            if (CheckException(jenv)) {
                (*jenv)->DeleteLocalRef(jenv, j_pubKeyDerSz);
                (*jenv)->DeleteLocalRef(jenv, j_outSz);
                (*jenv)->DeleteLocalRef(jenv, outBB);
                if (needsDetach)
                    (*g_vm)->DetachCurrentThread(g_vm);
                return -1;
            }
            *pubKeySz = (unsigned int)tmpVal;

            /* attempt to strip off DER header if present, wolfSSL
             * requires raw ECC public key. ret is 0 if header not detected */
            ret = StripEccPublicDerHeaderInline(publicKeyDer, *pubKeySz);
            if (ret < 0) {
                (*jenv)->DeleteLocalRef(jenv, j_pubKeyDerSz);
                (*jenv)->DeleteLocalRef(jenv, j_outSz);
                (*jenv)->DeleteLocalRef(jenv, outBB);
                if (needsDetach)
                    (*g_vm)->DetachCurrentThread(g_vm);
                return -1;
            } else if (ret > 0) {
                *pubKeySz = ret;
            }
        }
    }

    /* delete local refs */
    (*jenv)->DeleteLocalRef(jenv, j_pubKeyDerSz);
    (*jenv)->DeleteLocalRef(jenv, j_outSz);
    (*jenv)->DeleteLocalRef(jenv, outBB);

    /* detach JNIEnv from thread */
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

#endif /* HAVE_PK_CALLBACKS */

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setRsaSignCb
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

#if defined(HAVE_PK_CALLBACKS) && !defined(NO_RSA)
    if(ctx != NULL) {
        /* set RSA sign callback */
        wolfSSL_CTX_SetRsaSignCb(ctx, NativeRsaSignCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when setting "
                "RsaSignCb");
    }
#else
    (void)ctx;
    (*jenv)->ThrowNew(jenv, excClass,
            "wolfSSL not compiled with PK Callback support and/or RSA support");
#endif
}

#if defined(HAVE_PK_CALLBACKS) && !defined(NO_RSA)

int  NativeRsaSignCb(WOLFSSL* ssl, const unsigned char* in, unsigned int inSz,
        unsigned char* out, unsigned int* outSz, const unsigned char* keyDer,
        unsigned int keySz, void* ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI Environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jmethodID  rsaSignMethodId;
    jintArray  j_outSz;

    jobject    inBB = NULL;
    jobject    outBB = NULL;
    jobject    keyDerBB = NULL;
    jint       tmpVal = 0;

    (void)ctx;

    if (!g_vm || !ssl || !in || !out || !outSz || !keyDer)
        return -1;

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return -1;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return -1;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeRsaSignCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference in "
            "NativeRsaSignCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID "
            "in NativeRsaSignCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* find getContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID "
            "in NativeRsaSignCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext ctx object from Java land */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeRsaSignCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext class reference from Java land */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference "
            "in NativeRsaSignCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call internal RSA sign callback */
    rsaSignMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
            "internalRsaSignCallback",
            "(Lcom/wolfssl/WolfSSLSession;Ljava/nio/ByteBuffer;"
            "JLjava/nio/ByteBuffer;[ILjava/nio/ByteBuffer;J)I");

    if (!rsaSignMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Error getting internalRsaSignCallback method from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'in' */
    inBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)in, inSz);
    if (!inBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaSign in ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'out' */
    outBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)out, *outSz);
    if (!outBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaSign out ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'keyDer' */
    keyDerBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)keyDer, keySz);
    if (!keyDerBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaSign keyDer ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create jintArray to hold outSz, since we need to use it as
     * an OUTPUT parameter from Java. Only needs to have 1 element */
    j_outSz = (*jenv)->NewIntArray(jenv, 1);
    if (!j_outSz) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create result intArray in RsaSignCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        (*jenv)->DeleteLocalRef(jenv, keyDerBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* copy outSz into j_outSz */
    (*jenv)->SetIntArrayRegion(jenv, j_outSz, 0, 1, (jint*)outSz);
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to set j_outSz intArray in RsaSignCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        (*jenv)->DeleteLocalRef(jenv, keyDerBB);
        (*jenv)->DeleteLocalRef(jenv, j_outSz);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call Java RSA sign callback, java layer handles
     * adding CTX reference */
    retval = (*jenv)->CallIntMethod(jenv, ctxRef, rsaSignMethodId,
            (jobject)(*g_cachedSSLObj), inBB, (jlong)inSz, outBB, j_outSz,
            keyDerBB, (jlong)keySz);

    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        (*jenv)->DeleteLocalRef(jenv, keyDerBB);
        (*jenv)->DeleteLocalRef(jenv, j_outSz);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
    }

    if (retval == 0) {
        /* copy j_outSz into outSz */
        (*jenv)->GetIntArrayRegion(jenv, j_outSz, 0, 1, &tmpVal);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, inBB);
            (*jenv)->DeleteLocalRef(jenv, outBB);
            (*jenv)->DeleteLocalRef(jenv, keyDerBB);
            (*jenv)->DeleteLocalRef(jenv, j_outSz);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }
        *outSz = tmpVal;
    }

    /* delete local refs */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    (*jenv)->DeleteLocalRef(jenv, inBB);
    (*jenv)->DeleteLocalRef(jenv, outBB);
    (*jenv)->DeleteLocalRef(jenv, keyDerBB);
    (*jenv)->DeleteLocalRef(jenv, j_outSz);

    /* detach JNIEnv from thread */
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

#endif /* HAVE_PK_CALLBACKS */

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setRsaVerifyCb
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

#if defined(HAVE_PK_CALLBACKS) && !defined(NO_RSA)
    if(ctx != NULL) {
        /* set RSA verify callback */
        wolfSSL_CTX_SetRsaVerifyCb(ctx, NativeRsaVerifyCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when setting "
                "RsaVerifyCb");
    }
#else
    (void)ctx;
    (*jenv)->ThrowNew(jenv, excClass,
            "wolfSSL not compiled with PK Callback and/or RSA support");
#endif
}

#if defined(HAVE_PK_CALLBACKS) && !defined(NO_RSA)

int  NativeRsaVerifyCb(WOLFSSL* ssl, unsigned char* sig, unsigned int sigSz,
        unsigned char** out, const unsigned char* keyDer, unsigned int keySz,
        void* ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI Environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jmethodID  rsaVerifyMethodId;

    jobject    sigBB = NULL;
    jobject    outBB = NULL;
    jobject    keyDerBB = NULL;

    (void)ctx;

    if (!g_vm || !ssl || !sig || !out || !keyDer)
        return -1;

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return -1;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return -1;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeRsaVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference in "
            "NativeRsaVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID "
            "in NativeRsaVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* find getContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID "
            "in NativeRsaVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext ctx object from Java land */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeRsaVerifyCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext class reference from Java land */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference "
            "in NativeRsaVerifyCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call internal RSA verify callback */
    rsaVerifyMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
            "internalRsaVerifyCallback",
            "(Lcom/wolfssl/WolfSSLSession;Ljava/nio/ByteBuffer;"
            "JLjava/nio/ByteBuffer;JLjava/nio/ByteBuffer;J)I");

    if (!rsaVerifyMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Error getting internalRsaVerifyCallback method from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'sig' */
    sigBB = (*jenv)->NewDirectByteBuffer(jenv, sig, sigSz);
    if (!sigBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaVerify sig ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'out', since we're actually
     * doing this inline, outBB points to the same address as
     * sigBB */
    outBB = (*jenv)->NewDirectByteBuffer(jenv, sig, sigSz);
    if (!outBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaVerify out ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, sigBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'keyDer' */
    keyDerBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)keyDer, keySz);
    if (!keyDerBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaVerify keyDer ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, sigBB);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call Java RSA verify callback, java layer handles
     * adding CTX reference */
    retval = (*jenv)->CallIntMethod(jenv, ctxRef, rsaVerifyMethodId,
            (jobject)(*g_cachedSSLObj), sigBB, (jlong)sigSz, outBB,
            (jlong)sigSz, keyDerBB, (jlong)keySz);

    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
    }

    /* point out* to the beginning of our decrypted buffer */
    if (retval > 0)
        *out = sig;

    /* delete local refs */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    (*jenv)->DeleteLocalRef(jenv, sigBB);
    (*jenv)->DeleteLocalRef(jenv, outBB);
    (*jenv)->DeleteLocalRef(jenv, keyDerBB);

    /* detach JNIEnv from thread */
    (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

#endif /* HAVE_PK_CALLBACKS */

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setRsaEncCb
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

#if defined(HAVE_PK_CALLBACKS) && !defined(NO_RSA)
    if(ctx != NULL) {
        /* set RSA encrypt callback */
        wolfSSL_CTX_SetRsaEncCb(ctx, NativeRsaEncCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when setting "
                "RsaEncCb");
    }
#else
    (void)ctx;
    (*jenv)->ThrowNew(jenv, excClass,
            "wolfSSL not compiled with PK Callback and/or RSA support");
#endif
}

#if defined(HAVE_PK_CALLBACKS) && !defined(NO_RSA)

int  NativeRsaEncCb(WOLFSSL* ssl, const unsigned char* in, unsigned int inSz,
        unsigned char* out, unsigned int* outSz, const unsigned char* keyDer,
        unsigned int keySz, void* ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI Environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jmethodID  rsaEncMethodId;
    jintArray  j_outSz;

    jobject    inBB = NULL;
    jobject    outBB = NULL;
    jobject    keyDerBB = NULL;
    jint       tmpVal = 0;

    (void)ctx;

    if (!g_vm || !ssl || !in || !out || !outSz || !keyDer)
        return -1;

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return -1;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return -1;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeRsaEncCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference in "
            "NativeRsaEncCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID "
            "in NativeRsaEncCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* find getContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID "
            "in NativeRsaEncCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext ctx object from Java land */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeRsaEncCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext class reference from Java land */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference "
            "in NativeRsaEncCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call internal RSA enc callback */
    rsaEncMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
            "internalRsaEncCallback",
            "(Lcom/wolfssl/WolfSSLSession;Ljava/nio/ByteBuffer;"
            "JLjava/nio/ByteBuffer;[ILjava/nio/ByteBuffer;J)I");

    if (!rsaEncMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Error getting internalRsaEncCallback method from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'in' */
    inBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)in, inSz);
    if (!inBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaEnc in ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'out' */
    outBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)out, *outSz);
    if (!outBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaEnc out ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'keyDer' */
    keyDerBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)keyDer, keySz);
    if (!keyDerBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaEnc keyDer ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create jintArray to hold outSz, since we need to use it as
     * an OUTPUT parameter from Java. Only needs to have 1 element */
    j_outSz = (*jenv)->NewIntArray(jenv, 1);
    if (!j_outSz) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create result intArray in RsaEncCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        (*jenv)->DeleteLocalRef(jenv, keyDerBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* copy outSz into j_outSz */
    (*jenv)->SetIntArrayRegion(jenv, j_outSz, 0, 1, (jint*)outSz);
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to set j_outSz intArray in RsaEncCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        (*jenv)->DeleteLocalRef(jenv, keyDerBB);
        (*jenv)->DeleteLocalRef(jenv, j_outSz);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call Java RSA enc callback, java layer handles
     * adding CTX reference */
    retval = (*jenv)->CallIntMethod(jenv, ctxRef, rsaEncMethodId,
            (jobject)(*g_cachedSSLObj), inBB, (jlong)inSz, outBB, j_outSz,
            keyDerBB, (jlong)keySz);

    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
    }

    if (retval == 0) {
        /* copy j_outSz into outSz */
        (*jenv)->GetIntArrayRegion(jenv, j_outSz, 0, 1, &tmpVal);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, inBB);
            (*jenv)->DeleteLocalRef(jenv, outBB);
            (*jenv)->DeleteLocalRef(jenv, keyDerBB);
            (*jenv)->DeleteLocalRef(jenv, j_outSz);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return -1;
        }
        *outSz = tmpVal;
    }

    /* delete local refs */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    (*jenv)->DeleteLocalRef(jenv, inBB);
    (*jenv)->DeleteLocalRef(jenv, outBB);
    (*jenv)->DeleteLocalRef(jenv, keyDerBB);
    (*jenv)->DeleteLocalRef(jenv, j_outSz);

    /* detach JNIEnv from thread */
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

#endif /* HAVE_PK_CALLBACKS */

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setRsaDecCb
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

#if defined(HAVE_PK_CALLBACKS) && !defined(NO_RSA)
    if(ctx != NULL) {
        /* set RSA encrypt callback */
        wolfSSL_CTX_SetRsaDecCb(ctx, NativeRsaDecCb);

    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when setting "
                "RsaDecCb");
    }
#else
    (void)ctx;
    (*jenv)->ThrowNew(jenv, excClass,
            "wolfSSL not compiled with PK Callback and/or RSA support");
#endif
}

#if defined(HAVE_PK_CALLBACKS) && !defined(NO_RSA)

int  NativeRsaDecCb(WOLFSSL* ssl, unsigned char* in, unsigned int inSz,
        unsigned char** out, const unsigned char* keyDer, unsigned int keySz,
        void* ctx)
{
    jint       retval = 0;
    jint       vmret  = 0;

    JNIEnv*    jenv;                  /* JNI Environment */
    jclass     excClass;              /* WolfSSLJNIException class */
    int        needsDetach = 0;       /* Should we explicitly detach? */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass     sessClass;             /* WolfSSLSession class */
    jfieldID   ctxFid;                /* WolfSSLSession->ctx FieldID */
    jmethodID  getCtxMethodId;        /* WolfSSLSession->getAssCtxPtr() ID */

    jobject    ctxRef;                /* WolfSSLContext object */
    jclass     innerCtxClass;         /* WolfSSLContext class */
    jmethodID  rsaDecMethodId;

    jobject    inBB = NULL;
    jobject    outBB = NULL;
    jobject    keyDerBB = NULL;

    (void)ctx;

    if (!g_vm || !ssl || !in || !out || !keyDer)
        return -1;

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return -1;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return -1;
    }

    /* find exception class in case we need it */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativeRsaDecCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLSession class reference in "
            "NativeRsaDecCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext field ID "
            "in NativeRsaDecCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* find getContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
        "getAssociatedContextPtr",
        "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get getAssociatedContextPtr() method ID "
            "in NativeRsaDecCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext ctx object from Java land */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get WolfSSLContext object in NativeRsaDecCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* get WolfSSLContext class reference from Java land */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Can't get native WolfSSLContext class reference "
            "in NativeRsaDecCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call internal ECC verify callback */
    rsaDecMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
            "internalRsaDecCallback",
            "(Lcom/wolfssl/WolfSSLSession;Ljava/nio/ByteBuffer;"
            "JLjava/nio/ByteBuffer;JLjava/nio/ByteBuffer;J)I");

    if (!rsaDecMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
            "Error getting internalRsaDecCallback method from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'in' */
    inBB = (*jenv)->NewDirectByteBuffer(jenv, in, inSz);
    if (!inBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaDec in ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'out', since we're actually
     * doing this inline, outBB points to the same address as
     * inBB */
    outBB = (*jenv)->NewDirectByteBuffer(jenv, in, inSz);
    if (!outBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaDec out ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* create ByteBuffer to wrap 'keyDer' */
    keyDerBB = (*jenv)->NewDirectByteBuffer(jenv, (void*)keyDer, keySz);
    if (!keyDerBB) {
        (*jenv)->ThrowNew(jenv, excClass,
            "Failed to create rsaDec keyDer ByteBuffer");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return -1;
    }

    /* call Java RSA decrypt callback, java layer handles
     * adding CTX reference */
    retval = (*jenv)->CallIntMethod(jenv, ctxRef, rsaDecMethodId,
            (jobject)(*g_cachedSSLObj), inBB, (jlong)inSz, outBB, (jlong)inSz,
            keyDerBB, (jlong)keySz);

    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, inBB);
        (*jenv)->DeleteLocalRef(jenv, outBB);
        (*jenv)->DeleteLocalRef(jenv, keyDerBB);
        retval = -1;
    }

    /* point out* to the beginning of our decrypted buffer */
    if (retval > 0)
        *out = in;

    /* delete local refs */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    (*jenv)->DeleteLocalRef(jenv, inBB);
    (*jenv)->DeleteLocalRef(jenv, outBB);
    (*jenv)->DeleteLocalRef(jenv, keyDerBB);

    /* detach JNIEnv from thread */
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

#endif /* HAVE_PK_CALLBAKCS */

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setPskClientCb
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

#ifndef NO_PSK
    if (ctx != NULL) {
        /* set PSK client callback */
        wolfSSL_CTX_set_psk_client_callback(ctx, NativePskClientCb);
    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when setting "
                "NativePskClientCb");
    }
#else
    (void)ctx;
    (*jenv)->ThrowNew(jenv, excClass,
            "wolfSSL not compiled with PSK support");
#endif
}

#ifndef NO_PSK

unsigned int NativePskClientCb(WOLFSSL* ssl, const char* hint, char* identity,
        unsigned int id_max_len, unsigned char* key, unsigned int max_key_len)
{
    jint        vmret  = 0;
    jlong       retval = 0;
    int         usingSSLCallback = 0;
    int         needsDetach = 0;      /* Should we explicitly detach? */

    JNIEnv*     jenv;                 /* JNI environment */
    jclass      excClass;             /* class: WolfSSLJNIException */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass      sessClass;            /* WolfSSLSession class */
    jfieldID    ctxFid;               /* WolfSSLSession->ctx fieldID */
    jmethodID   getCtxMethodId;       /* WolfSSLSession->getAssCtxPtr() ID */

    jfieldID    internPskClientCbFid; /* WolfSSLContext->internPskClientCb ID */
    jobject     internPskClientCbObj; /*      "    ->internPskClientCb object */

    jobject     ctxRef;               /* WolfSSLContext object */
    jclass      innerCtxClass;        /* WolfSSLContext class */
    jmethodID   pskClientMethodId;    /* internalPskClientCallback ID */

    jstring     hintString;           /* String, for 'hint' */
    jclass      strBufClass;          /* StringBuffer class for 'identity' */
    jmethodID   strBufMethodId;       /* StringBuffer constructor ID */
    jobject     strBufObj;            /* StringBuffer object */
    jbyteArray  keyArray;             /* byte[] for key in/out */
    jmethodID   toStringId;           /* StringBuffer.toString() ID */
    jstring     bufString;            /* StringBuffer.toString() result */
    const char* tmpString;            /* tmp output char* for String out */

    /* Note: since this is called from C, not the JVM, we need to explicitly
     * free all object refs with DeleteLocalRef() */

    if (!g_vm || !ssl || !hint || !identity || !key) {
        /* we can't throw an exception yet, so just return 0 (failure) */
        return 0;
    }

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return 0;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return 0;
    }

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativePskClientCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession class reference in "
                "NativePskClientCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLContext field ID in "
                "NativePSKClientCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* find WolfSSLSession.getAssociatedContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
            "getAssociatedContextPtr",
            "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get getAssociatedContextPtr() method ID in "
                "NativePSKClientCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* get WolfSSLContext(ctx) object from Java WolfSSLSession object */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get WolfSSLContext object in NativePskClientCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* get WolfSSLContext class reference from object */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLContext class reference in "
                "NativePskClientCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* figure out if we need to call the CTX or SSL level callback */

    /*  1.  Get internPskClientCb FieldID */
    internPskClientCbFid = (*jenv)->GetFieldID(jenv, innerCtxClass,
            "internPskClientCb",
            "Lcom/wolfssl/WolfSSLPskClientCallback;");
    if (!internPskClientCbFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native internPskClientCb field ID in "
                "NativePSKClientCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /*  2.  Get WolfSSLPskClientCallback object (or null) */
    internPskClientCbObj = (*jenv)->GetObjectField(jenv, ctxRef,
            internPskClientCbFid);
    if (!internPskClientCbObj) {
        printf("Using SSL level PSK Client callback!!!\n");
        usingSSLCallback = 1;
    }

    if (usingSSLCallback == 1) {
        /* WolfSSLSession level callback */
        pskClientMethodId = (*jenv)->GetMethodID(jenv, sessClass,
                "internalPskClientCallback",
                "(Lcom/wolfssl/WolfSSLSession;Ljava/lang/String;"
                "Ljava/lang/StringBuffer;J[BJ)J");
    } else {
        /* WolfSSLContext level callback */
        pskClientMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
                "internalPskClientCallback",
                "(Lcom/wolfssl/WolfSSLSession;Ljava/lang/String;"
                "Ljava/lang/StringBuffer;J[BJ)J");
    }

    if (!pskClientMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }

        (*jenv)->ThrowNew(jenv, excClass,
                "Error getting internalPskClientCallback method from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* create String to wrap 'hint' */
    hintString = (*jenv)->NewStringUTF(jenv, hint);
    if (!hintString) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Error creating String for PSK client hint");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* find StringBuffer class to wrap 'identity' */
    strBufClass = (*jenv)->FindClass(jenv, "java/lang/StringBuffer");
    if (!strBufClass) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Error finding StringBuffer class for PSK client identity");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, hintString);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* find StringBuffer Constructor */
    strBufMethodId = (*jenv)->GetMethodID(jenv, strBufClass,
            "<init>", "()V");
    if (!strBufMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get StringBuffer constructor method ID "
                "in NativePskClientCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, hintString);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* create new StringBuffer object */
    strBufObj = (*jenv)->NewObject(jenv, strBufClass, strBufMethodId);
    if (!strBufObj) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get StringBuffer object in NativePskClientCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, hintString);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* create jbyteArray to hold received key */
    keyArray = (*jenv)->NewByteArray(jenv, max_key_len);
    if (!keyArray) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Error creating jbyteArray for PSK client key");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, hintString);
        (*jenv)->DeleteLocalRef(jenv, strBufObj);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* call Java PSK client callback */
    if (usingSSLCallback == 1) {
        /* call WolfSSLSession level callback */
        retval = (*jenv)->CallLongMethod(jenv, (jobject)(*g_cachedSSLObj),
                pskClientMethodId, (jobject)(*g_cachedSSLObj), hintString,
                strBufObj, (jlong)id_max_len, keyArray, (jlong)max_key_len);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, hintString);
            (*jenv)->DeleteLocalRef(jenv, strBufObj);
            (*jenv)->DeleteLocalRef(jenv, keyArray);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return 0;
        }
    } else {
        /* call WolfSSLContext level callback */
        retval = (*jenv)->CallLongMethod(jenv, ctxRef, pskClientMethodId,
                (jobject)(*g_cachedSSLObj), hintString, strBufObj,
                (jlong)id_max_len, keyArray, (jlong)max_key_len);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, hintString);
            (*jenv)->DeleteLocalRef(jenv, strBufObj);
            (*jenv)->DeleteLocalRef(jenv, keyArray);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return 0;
        }
    }

    if (retval > 0) {

        /* copy jbyteArray into char key array */
        (*jenv)->GetByteArrayRegion(jenv, keyArray, 0, retval, (jbyte*)key);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, hintString);
            (*jenv)->DeleteLocalRef(jenv, strBufObj);
            (*jenv)->DeleteLocalRef(jenv, keyArray);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return 0;
        }

        /* get the String from the StringBuffer */
        toStringId = (*jenv)->GetMethodID(jenv, strBufClass,
                "toString", "()Ljava/lang/String;");
        if (!toStringId) {
            if ((*jenv)->ExceptionOccurred(jenv)) {
                (*jenv)->ExceptionDescribe(jenv);
                (*jenv)->ExceptionClear(jenv);
            }
            (*jenv)->ThrowNew(jenv, excClass,
                    "Error getting String ID from StringBuffer in PSK CB");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, hintString);
            (*jenv)->DeleteLocalRef(jenv, strBufObj);
            (*jenv)->DeleteLocalRef(jenv, keyArray);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return 0;
        }

        bufString = (jstring) (*jenv)->CallObjectMethod(jenv,
                strBufObj, toStringId);
        CheckException(jenv);
        if (!bufString) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "Error getting String from StringBuffer in PSK CB");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, hintString);
            (*jenv)->DeleteLocalRef(jenv, strBufObj);
            (*jenv)->DeleteLocalRef(jenv, keyArray);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return 0;
        }

        /* convert string to C string (char*) */
        tmpString = (*jenv)->GetStringUTFChars(jenv, bufString, 0);
        if (!tmpString) {
            (*jenv)->ThrowNew(jenv, excClass,
                    "Error with GetStringUTFChars in PSK Client CB");
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, hintString);
            (*jenv)->DeleteLocalRef(jenv, strBufObj);
            (*jenv)->DeleteLocalRef(jenv, keyArray);
            (*jenv)->DeleteLocalRef(jenv, bufString);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return 0;
        }
        strcpy(identity, tmpString);
        (*jenv)->ReleaseStringUTFChars(jenv, bufString, tmpString);
        (*jenv)->DeleteLocalRef(jenv, bufString);
    }

    /* delete local obj refs, detach JNIEnv from thread */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    (*jenv)->DeleteLocalRef(jenv, hintString);
    (*jenv)->DeleteLocalRef(jenv, strBufObj);
    (*jenv)->DeleteLocalRef(jenv, keyArray);
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

#endif /* NO_PSK */

JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setPskServerCb
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    jclass excClass = NULL;
    (void)jcl;

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        return;
    }

#ifndef NO_PSK
    if (ctx != NULL) {
        /* set PSK server callback */
        wolfSSL_CTX_set_psk_server_callback(ctx, NativePskServerCb);
    } else {
        (*jenv)->ThrowNew(jenv, excClass,
                "Input WolfSSLContext object was null when setting "
                "NativePskServerCb");
    }
#else
    (void)ctx;
    (*jenv)->ThrowNew(jenv, excClass,
            "wolfSSL not compiled with PSK support");
#endif
}

#ifndef NO_PSK
unsigned int NativePskServerCb(WOLFSSL* ssl, const char* identity,
        unsigned char* key, unsigned int max_key_len)
{
    jint        vmret  = 0;
    jlong       retval = 0;
    int         usingSSLCallback = 0;
    int         needsDetach = 0;     /* Should we explicitly detach? */

    JNIEnv*     jenv;                 /* JNI environment */
    jclass      excClass;             /* class: WolfSSLJNIException */

    static jobject* g_cachedSSLObj;   /* WolfSSLSession cached object */
    jclass      sessClass;            /* WolfSSLSession class */
    jfieldID    ctxFid;               /* WolfSSLSession->ctx fieldID */
    jmethodID   getCtxMethodId;       /* WolfSSLSession->getAssCtxPtr() ID */

    jfieldID    internPskServerCbFid; /* WolfSSLContext->internPskServerCb ID */
    jobject     internPskServerCbObj; /*      "   -> internPskServerCb object */

    jobject     ctxRef;               /* WolfSSLContext object */
    jclass      innerCtxClass;        /* WolfSSLContext class */
    jmethodID   pskServerMethodId;    /* internalPskServerCallback ID */

    jstring     identityString;       /* String, for 'hint' */
    jbyteArray  keyArray;             /* byte[] for key in/out */

    /* Note: since this is called from C, not the JVM, we need to explicitly
     * free all object refs with DeleteLocalRef() */

    if (!g_vm || !ssl || !identity || !key) {
        /* we can't throw an exception yet, so just return 0 (failure) */
        return 0;
    }

    /* get JavaEnv from JavaVM */
    vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
    if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
        vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
        vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
        if (vmret) {
            return 0;
        }
        needsDetach = 1;
    } else if (vmret != JNI_OK) {
        return 0;
    }

    /* find exception class */
    excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
    if ((*jenv)->ExceptionOccurred(jenv)) {
        (*jenv)->ExceptionDescribe(jenv);
        (*jenv)->ExceptionClear(jenv);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* get stored WolfSSLSession jobject */
    g_cachedSSLObj = (jobject*) wolfSSL_get_jobject((WOLFSSL*)ssl);
    if (!g_cachedSSLObj) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession object reference in "
                "NativePskServerCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* lookup WolfSSLSession class from object */
    sessClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
    if (!sessClass) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLSession class reference in "
                "NativePskServerCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* lookup WolfSSLContext private member fieldID */
    ctxFid = (*jenv)->GetFieldID(jenv, sessClass, "ctx",
            "Lcom/wolfssl/WolfSSLContext;");
    if (!ctxFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLContext field ID in "
                "NativePSKClientCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* find WolfSSLSession.getAssociatedContextPtr() method */
    getCtxMethodId = (*jenv)->GetMethodID(jenv, sessClass,
            "getAssociatedContextPtr",
            "()Lcom/wolfssl/WolfSSLContext;");
    if (!getCtxMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get getAssociatedContextPtr() method ID in "
                "NativePSKClientCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* get WolfSSLContext(ctx) object from Java WolfSSLSession object */
    ctxRef = (*jenv)->CallObjectMethod(jenv, (jobject)(*g_cachedSSLObj),
            getCtxMethodId);
    CheckException(jenv);
    if (!ctxRef) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get WolfSSLContext object in NativePskServerCb");
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* get WolfSSLContext class reference from object */
    innerCtxClass = (*jenv)->GetObjectClass(jenv, ctxRef);
    if (!innerCtxClass) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native WolfSSLContext class reference in "
                "NativePskServerCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* figure out if we need to call the CTX or SSL level callback */

    /*  1.  Get the internPskServerCb FieldID */
    internPskServerCbFid = (*jenv)->GetFieldID(jenv, innerCtxClass,
            "internPskServerCb",
            "Lcom/wolfssl/WolfSSLPskServerCallback;");
    if (!internPskServerCbFid) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Can't get native internPskServerCb field ID in "
                "NativePskServerCb");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /*  2.  Get WolfSSLPskServerCallback object (or null) */
    internPskServerCbObj = (*jenv)->GetObjectField(jenv, ctxRef,
            internPskServerCbFid);
    if (!internPskServerCbObj) {
        printf("Using SSL level PSK Server callback!!!\n");
        usingSSLCallback = 1;
    }

    if (usingSSLCallback == 1) {
        /* WolfSSLSession level callback */
        pskServerMethodId = (*jenv)->GetMethodID(jenv, sessClass,
                "internalPskServerCallback",
                "(Lcom/wolfssl/WolfSSLSession;Ljava/lang/String;"
                "[BJ)J");
    } else {
        /* WolfSSLContext level callback */
        pskServerMethodId = (*jenv)->GetMethodID(jenv, innerCtxClass,
                "internalPskServerCallback",
                "(Lcom/wolfssl/WolfSSLSession;Ljava/lang/String;"
                "[BJ)J");
    }

    if (!pskServerMethodId) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Error getting internalPskServerCallback method from JNI");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* create String to wrap 'identity' */
    identityString = (*jenv)->NewStringUTF(jenv, identity);
    if (!identityString) {
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
        }
        (*jenv)->ThrowNew(jenv, excClass,
                "Error creating String for PSK client identity");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* create jbyteArray to hold received key */
    keyArray = (*jenv)->NewByteArray(jenv, max_key_len);
    if (!keyArray) {
        (*jenv)->ThrowNew(jenv, excClass,
                "Error creating jbyteArray for PSK server key");
        (*jenv)->DeleteLocalRef(jenv, ctxRef);
        (*jenv)->DeleteLocalRef(jenv, identityString);
        if (needsDetach)
            (*g_vm)->DetachCurrentThread(g_vm);
        return 0;
    }

    /* call Java PSK server callback */
    if (usingSSLCallback == 1) {
        retval = (*jenv)->CallLongMethod(jenv, (jobject)(*g_cachedSSLObj),
                pskServerMethodId, (jobject)(*g_cachedSSLObj), identityString,
                keyArray, (jlong)max_key_len);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, identityString);
            (*jenv)->DeleteLocalRef(jenv, keyArray);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return 0;
        }
    } else {
        retval = (*jenv)->CallLongMethod(jenv, ctxRef, pskServerMethodId,
                (jobject)(*g_cachedSSLObj), identityString,
                keyArray, (jlong)max_key_len);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, identityString);
            (*jenv)->DeleteLocalRef(jenv, keyArray);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return 0;
        }
    }

    if (retval > 0) {

        /* copy jbyteArray into char key array */
        (*jenv)->GetByteArrayRegion(jenv, keyArray, 0, retval, (jbyte*)key);
        if ((*jenv)->ExceptionOccurred(jenv)) {
            (*jenv)->ExceptionDescribe(jenv);
            (*jenv)->ExceptionClear(jenv);
            (*jenv)->DeleteLocalRef(jenv, ctxRef);
            (*jenv)->DeleteLocalRef(jenv, identityString);
            (*jenv)->DeleteLocalRef(jenv, keyArray);
            if (needsDetach)
                (*g_vm)->DetachCurrentThread(g_vm);
            return 0;
        }
    }

    /* delete local obj refs, detach JNIEnv from thread */
    (*jenv)->DeleteLocalRef(jenv, ctxRef);
    (*jenv)->DeleteLocalRef(jenv, identityString);
    (*jenv)->DeleteLocalRef(jenv, keyArray);
    if (needsDetach)
        (*g_vm)->DetachCurrentThread(g_vm);

    return retval;
}

#endif /* NO_PSK */

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_usePskIdentityHint
  (JNIEnv* jenv, jobject obj, jlong ctxPtr, jstring hint)
{
#ifndef NO_PSK
    jint ret;
    const char* nativeHint;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)obj;

    if (jenv == NULL || ctx == NULL || hint == NULL) {
        return (jint)SSL_FAILURE;
    }

    nativeHint = (*jenv)->GetStringUTFChars(jenv, hint, 0);

    ret = (jint)wolfSSL_CTX_use_psk_identity_hint(ctx, nativeHint);

    (*jenv)->ReleaseStringUTFChars(jenv, hint, nativeHint);

    return ret;
#else
    (void)jenv;
    (void)obj;
    (void)ctxPtr;
    (void)hint;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_useSupportedCurve
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jint name)
{
#ifdef HAVE_SUPPORTED_CURVES
    int ret = 0;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL || ctx == NULL) {
        return (jint)SSL_FAILURE;
    }

    ret = wolfSSL_CTX_UseSupportedCurve(ctx, (word16)name);

    return (jint)ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)name;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setGroups
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jintArray groups)
{
#if defined(HAVE_TLS13) && defined(HAVE_SUPPORTED_CURVES)
    int ret = WOLFSSL_FAILURE;
    int groupsSz = 0;
    int* jniGroups = NULL;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    if (jenv == NULL || ctx == NULL || groups == NULL) {
        return (jint)SSL_FAILURE;
    }

    groupsSz = (*jenv)->GetArrayLength(jenv, groups);
    jniGroups = (*jenv)->GetIntArrayElements(jenv, groups, NULL);

    if (groupsSz == 0 || groupsSz > WOLFSSL_MAX_GROUP_COUNT ||
        jniGroups == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    ret = wolfSSL_CTX_set_groups(ctx, jniGroups, groupsSz);

    (*jenv)->ReleaseIntArrayElements(jenv, groups, jniGroups, JNI_ABORT);

    return (jint)ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)groups;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_set1SigAlgsList
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jstring list)
{
#if !defined(WOLFCRYPT_ONLY) && defined(OPENSSL_EXTRA)
    int ret = WOLFSSL_FAILURE;
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    const char* sigAlgList = NULL;
    (void)jcl;

    if (jenv == NULL || ctx == NULL || list == NULL) {
        return (jint)WOLFSSL_FAILURE;
    }

    sigAlgList = (*jenv)->GetStringUTFChars(jenv, list, 0);

    ret = wolfSSL_CTX_set1_sigalgs_list(ctx, sigAlgList);

    (*jenv)->ReleaseStringUTFChars(jenv, list, sigAlgList);

    return (jint)ret;
#else
    (void)jenv;
    (void)jcl;
    (void)ctxPtr;
    (void)list;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_useSecureRenegotiation
  (JNIEnv* jenv, jobject jcl, jlong ctx)
{
    (void)jenv;
    (void)jcl;
#ifdef HAVE_SECURE_RENEGOTIATION
    return (jint)wolfSSL_CTX_UseSecureRenegotiation(
                    (WOLFSSL_CTX*)(uintptr_t)ctx);
#else
    (void)ctx;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setMinDhKeySz
  (JNIEnv* jenv, jobject obj, jlong ctxPtr, jint keySzBits)
{
#ifndef NO_DH
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)obj;

    /* wolfSSL_CTX_SetMinDhKey_Sz() sanitizes ctx and keySzBits */
    if (jenv == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    return (jint)wolfSSL_CTX_SetMinDhKey_Sz(ctx, (word16)keySzBits);
#else
    (void)jenv;
    (void)obj;
    (void)ctxPtr;
    (void)keySzBits;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setMinRsaKeySz
  (JNIEnv* jenv, jobject obj, jlong ctxPtr, jint keySzBits)
{
#ifndef NO_RSA
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)obj;

    /* wolfSSL_CTX_SetMinRsaKey_Sz() sanitizes ctx and keySzBits */
    if (jenv == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    return (jint)wolfSSL_CTX_SetMinRsaKey_Sz(ctx, (short)keySzBits);
#else
    (void)jenv;
    (void)obj;
    (void)ctxPtr;
    (void)keySzBits;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setMinEccKeySz
  (JNIEnv* jenv, jobject obj, jlong ctxPtr, jint keySzBits)
{
#ifdef HAVE_ECC
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)obj;

    /* wolfSSL_CTX_SetMinEccKey_Sz() sanitizes ctx and keySzBits */
    if (jenv == NULL) {
        return (jint)BAD_FUNC_ARG;
    }

    return (jint)wolfSSL_CTX_SetMinEccKey_Sz(ctx, (short)keySzBits);
#else
    (void)jenv;
    (void)obj;
    (void)ctxPtr;
    (void)keySzBits;
    return (jint)NOT_COMPILED_IN;
#endif
}

JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setDevId
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jint devId)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    /* wolfSSL_CTX_SetDevId() checks ssl for NULL */
    if (jenv == NULL) {
        return BAD_FUNC_ARG;
    }

    return (jint)wolfSSL_CTX_SetDevId(ctx, (int)devId);
}

void JNICALL Java_com_wolfssl_WolfSSLContext_flushSessions
  (JNIEnv* jenv, jobject jcl, jlong ctxPtr, jint tm)
{
    WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr;
    (void)jcl;

    wolfSSL_CTX_flush_sessions(ctx, (int)tm);
}

