/* WolfSSLSession.java
 *
 * 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
 */

package com.wolfssl;

import java.util.Arrays;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.lang.StringBuilder;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.security.Security;

/**
 * Wraps a native WolfSSL session object and contains methods directly related
 * to the SSL/TLS session.
 *
 * @author  wolfSSL
 */
public class WolfSSLSession {

    /* Internal pointer to native WOLFSSL object. Access to this pointer
     * should be protected in this class with synchronization on the
     * this.sslLock lock. */
    private long sslPtr;

    private Object ioReadCtx;
    private Object ioWriteCtx;
    private Object genCookieCtx;
    private Object macEncryptCtx;
    private Object decryptVerifyCtx;
    private Object verifyDecryptCtx;
    private Object eccSignCtx;
    private Object eccVerifyCtx;
    private Object eccSharedSecretCtx;
    private Object rsaSignCtx;
    private Object rsaVerifyCtx;
    private Object rsaEncCtx;
    private Object rsaDecCtx;
    private Object alpnSelectArg;
    private Object tls13SecretCtx;
    private Object sessionTicketCtx;

    /* reference to the associated WolfSSLContext */
    private WolfSSLContext ctx = null;

    /* user-registered PSK callbacks, also at WolfSSLContext level */
    private WolfSSLPskClientCallback internPskClientCb = null;
    private WolfSSLPskServerCallback internPskServerCb = null;

    /* User-registerd I/O callbacks:
     *
     * These are called by internal WolfSSLSession I/O callback. This is done
     * in order to pass references to WolfSSLSession object. There are two sets
     * of I/O callbacks here, one set that will use byte[] and one that will
     * use ByteBuffer. Only one send and one recv callback can be set at a time
     * between the two. Native JNI code will give preference to using the
     * ByteBuffer variants for performance if set, since this will avoid an
     * extra native allocation (NewByteArray()). The ByteBuffer variant will
     * wrap the pre-allocated wolfSSL array in a Java direct ByteBuffer
     * to pass back up to Java. */
    private WolfSSLIORecvCallback internRecvSSLCb_array;
    private WolfSSLIOSendCallback internSendSSLCb_array;
    private WolfSSLByteBufferIORecvCallback internRecvSSLCb_BB;
    private WolfSSLByteBufferIOSendCallback internSendSSLCb_BB;

    /* user-registered ALPN select callback, called by internal WolfSSLSession
     * ALPN select callback */
    private WolfSSLALPNSelectCallback internAlpnSelectCb;

    /* user-registered TLS 1.3 secret callback, called by internal
     * WolfSSLSession TLS 1.3 secret callback */
    private WolfSSLTls13SecretCallback internTls13SecretCb;

    /* user-registered session ticket callback, called by internal
     * WolfSSLSession session ticket callback */
    private WolfSSLSessionTicketCallback internSessionTicketCb;

    /* have session tickets been enabled for this session? Default to false. */
    private boolean sessionTicketsEnabled = false;

    /* is this context active, or has it been freed? */
    private boolean active = false;

    /* lock around active state */
    private final Object stateLock = new Object();

    /* lock around native WOLFSSL pointer use */
    private final Object sslLock = new Object();

    /* Is static direct ByteBuffer pool enabled for read/write() calls */
    private boolean byteBufferPoolEnabled = true;

    /* Maximum direct ByteBuffer pool size */
    private static int MAX_POOL_SIZE = 16;

    /* Size of each direct ByteBuffer in the pool. This is set to 17KB, which
     * is slightly larger than the maximum SSL record size (16KB). This
     * allows for some overhead (SSL record header, etc) */
    private static int BUFFER_SIZE = 17 * 1024;

    /* Thread-local direct ByteBuffer pool for optimized JNI direct memory
     * access. Passing byte[] and offset down to JNI, on some systems this
     * will cause unaligned memory access, with pointer addition
     * (buffer + offset). Unaligned memory access can be considerably slower
     * (ex: Aarch64). To avoid this, we use a thread-local pool of ByteBuffers
     * here so native JNI does not do unaligned memory access and to eliminate
     * cross-thread contention. */
    private static final ThreadLocal<ConcurrentLinkedQueue<ByteBuffer>> directBufferPool =
        ThreadLocal.withInitial(() -> new ConcurrentLinkedQueue<>());

    /**
     * Check if static direct ByteBuffer pool has been disabled for
     * use in read/write() methods.
     *
     * The pool is enabled by default, unless explicitly disabled by setting
     * the "wolfssl.readWriteByteBufferPool.disabled" property to "true".
     *
     * @return true if disabled, otherwise false
     */
    private boolean readWritePoolDisabled() {

        String disabled =
            Security.getProperty("wolfssl.readWriteByteBufferPool.disabled");

        if (disabled == null || disabled.isEmpty()) {
            return false;
        }

        if (disabled.equalsIgnoreCase("true")) {
            return true;
        }

        return false;
    }

    /**
     * Check if the maximum size of the static per-thread direct
     * ByteBuffer pool has been adjusted by setting of the
     * "wolfssl.readWriteByteBufferPool.size" Security property.
     *
     * @return the size set, or the current default
     * (MAX_POOL_SIZE) if not.
     */
    private int readWritePoolGetMaxSizeFromProperty() {

        int maxSize = MAX_POOL_SIZE;

        String sizeProp =
            Security.getProperty("wolfssl.readWriteByteBufferPool.size");

        if (sizeProp == null || sizeProp.isEmpty()) {
            return maxSize;
        }

        try {
            int size = Integer.parseInt(sizeProp);
            if (size > 0) {
                maxSize = size;
            }
        } catch (NumberFormatException e) {
            WolfSSLDebug.log(getClass(),
                WolfSSLDebug.Component.JNI, WolfSSLDebug.ERROR, 0,
                () -> "Invalid value for " +
                "wolfssl.readWriteByteBufferPool.size: " + sizeProp);
        }

        return maxSize;
    }

    /**
     * Check if the size of the ByteBuffers in the static per-thread
     * pool has been adjusted by setting of the
     * "wolfssl.readWriteByteBufferPool.bufferSize" Security property.
     *
     * @return the size set, or the current default (BUFFER_SIZE) if not.
     */
    private int readWritePoolGetBufferSizeFromProperty() {

        int bufferSize = BUFFER_SIZE;

        String sizeProp =
            Security.getProperty("wolfssl.readWriteByteBufferPool.bufferSize");

        if (sizeProp == null || sizeProp.isEmpty()) {
            return bufferSize;
        }

        try {
            int size = Integer.parseInt(sizeProp);
            if (size > 0) {
                bufferSize = size;
            }
        } catch (NumberFormatException e) {
            WolfSSLDebug.log(getClass(),
                WolfSSLDebug.Component.JNI, WolfSSLDebug.ERROR, 0,
                () -> "Invalid value for " +
                "wolfssl.readWriteByteBufferPool.bufferSize: " + sizeProp);
        }

        return bufferSize;
    }

    /**
     * Read current values of relevant Security properties and set
     * internal behavior.
     */
    private void detectSecurityPropertySettings() {

        /* Re-use sslLock for synchronization here */
        synchronized (sslLock) {
            /* Check if static direct ByteBuffer pool has been disabled
             * with the "wolfssl.readWriteByteBufferPool.disabled"
             * Security property. */
            if (readWritePoolDisabled()) {
                this.byteBufferPoolEnabled = false;
            }

            /* Check if the maximum size of the static per-thread direct
             * ByteBuffer pool has been adjusted by setting of the
             * "wolfssl.readWriteByteBufferPool.size" Security property. */
            WolfSSLSession.MAX_POOL_SIZE =
                readWritePoolGetMaxSizeFromProperty();

            /* Check if the size of the ByteBuffers in the static per-thread
             * pool has been adjusted by setting of the
             * "wolfssl.readWriteByteBufferPool.bufferSize" Security property. */
            WolfSSLSession.BUFFER_SIZE =
                readWritePoolGetBufferSizeFromProperty();
        }
    }

    /**
     * Get a DirectByteBuffer from the thread-local pool or allocate a new one
     * if the pool is empty.
     *
     * @return a direct ByteBuffer ready to use
     */
    private static synchronized ByteBuffer acquireDirectBuffer() {
        ConcurrentLinkedQueue<ByteBuffer> threadPool = directBufferPool.get();
        ByteBuffer buffer = threadPool.poll();
        if (buffer == null) {
            WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, 0,
                () -> "Thread-local DirectByteBuffer pool empty, " +
                "allocating new buffer");
            buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
        } else {
            WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, 0,
                () -> "Reusing DirectByteBuffer from thread-local pool, " +
                "pool size: " + threadPool.size());
            buffer.clear();
        }
        return buffer;
    }

    /**
     * Return a DirectByteBuffer to the thread-local pool for reuse.
     *
     * If the pool is full, the ByteBuffer will be garbage collected.
     *
     * @param buffer the buffer to return to the pool
     */
    private static synchronized void releaseDirectBuffer(ByteBuffer buffer) {

        if (buffer != null && buffer.isDirect()) {

            buffer.clear();
            ConcurrentLinkedQueue<ByteBuffer> threadPool =
                directBufferPool.get();

            if (threadPool.size() < MAX_POOL_SIZE) {
                WolfSSLDebug.log(WolfSSLSession.class,
                    WolfSSLDebug.Component.JNI, WolfSSLDebug.INFO, 0,
                    () -> "Returning DirectByteBuffer to thread-local pool, " +
                    "pool size: " + threadPool.size());
                threadPool.offer(buffer);
            }
        }
    }

    /* SNI requested by this WolfSSLSession if client side and useSNI()
     * was called successfully. */
    private byte[] clientSNIRequested = null;

    /**
     * Creates a new SSL/TLS session.
     *
     * Native session created also creates JNI SSLAppData for usage
     * internal to wolfSSL JNI. This constructor creates a default
     * pipe() to use for interrupting threads waiting in select()/poll()
     * when close() is called. To skip creation of this pipe() use
     * the WolfSSLSession(WolfSSLContext ctx, boolean setupIOPipe)
     * constructor with 'setupIOPipe' set to false.
     *
     * @param  ctx   WolfSSLContext object used to create SSL session.
     *
     * @throws com.wolfssl.WolfSSLException if session object creation
     *                                      failed.
     */
    public WolfSSLSession(WolfSSLContext ctx) throws WolfSSLException {

        sslPtr = newSSL(ctx.getContextPtr(), true);
        if (sslPtr == 0) {
            throw new WolfSSLException("Failed to create SSL Object");
        }

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, sslPtr,
            () -> "creating new WolfSSLSession (with I/O pipe)");

        detectSecurityPropertySettings();

        synchronized (stateLock) {
            this.active = true;
        }

        /* save context reference for I/O callbacks from JNI */
        this.ctx = ctx;
    }

    /**
     * Creates a new SSL/TLS session.
     *
     * Native session created also creates JNI SSLAppData for usage
     * internal to wolfSSL JNI. A pipe() can be created internally to wolfSSL
     * JNI to use for interrupting threads waiting in select()/poll()
     * when close() is called. To skip creation of this pipe(), set
     * 'setupIOPipe' to false.
     *
     * It is generally recommended to have wolfSSL JNI create the native
     * pipe(), unless you will be operating over non-Socket I/O. For example,
     * when this WolfSSLSession is being created from the JSSE level
     * SSLEngine class.
     *
     * @param ctx         WolfSSLContext object used to create SSL session.
     * @param setupIOPipe true to create internal IO pipe(), otherwise
     *        false
     *
     * @throws com.wolfssl.WolfSSLException if session object creation
     *                                      failed.
     */
    public WolfSSLSession(WolfSSLContext ctx, boolean setupIOPipe)
        throws WolfSSLException {

        sslPtr = newSSL(ctx.getContextPtr(), false);
        if (sslPtr == 0) {
            throw new WolfSSLException("Failed to create SSL Object");
        }

        if (setupIOPipe) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, sslPtr,
                () -> "creating new WolfSSLSession (with I/O pipe)");
        } else {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, sslPtr,
                () -> "creating new WolfSSLSession (without I/O pipe)");
        }

        detectSecurityPropertySettings();

        synchronized (stateLock) {
            this.active = true;
        }

        /* save context reference for I/O callbacks from JNI */
        this.ctx = ctx;
    }

    /* ------------------- private/protected methods -------------------- */

    /* used from JNI code */
    synchronized WolfSSLContext getAssociatedContextPtr() {
        return ctx;
    }

    synchronized Object getGenCookieCtx() {
        return this.genCookieCtx;
    }

    synchronized Object getMacEncryptCtx() {
        return this.macEncryptCtx;
    }

    synchronized Object getDecryptVerifyCtx() {
        return this.decryptVerifyCtx;
    }

    synchronized Object getVerifyDecryptCtx() {
        return this.verifyDecryptCtx;
    }

    synchronized Object getEccSignCtx() {
        return this.eccSignCtx;
    }

    synchronized Object getEccVerifyCtx() {
        return this.eccVerifyCtx;
    }

    synchronized Object getEccSharedSecretCtx() {
        return this.eccSharedSecretCtx;
    }

    synchronized Object getRsaSignCtx() {
        return this.rsaSignCtx;
    }

    synchronized Object getRsaVerifyCtx() {
        return this.rsaVerifyCtx;
    }

    synchronized Object getRsaEncCtx() {
        return this.rsaEncCtx;
    }

    synchronized Object getRsaDecCtx() {
        return this.rsaDecCtx;
    }

    /* Methods to detect which I/O callback variants have been set */
    private boolean isArrayIOSendCallbackSet() {
        return (internSendSSLCb_array != null);
    }

    private boolean isByteBufferIOSendCallbackSet() {
        return (internSendSSLCb_BB != null);
    }

    private boolean isArrayIORecvCallbackSet() {
        return (internRecvSSLCb_array != null);
    }

    private boolean isByteBufferIORecvCallbackSet() {
        return (internRecvSSLCb_BB != null);
    }

    /* These callbacks will be registered with native wolfSSL library */

    /**
     * Internal wolfSSL I/O receive callback, using byte array.
     */
    private int internalIOSSLRecvCallback(WolfSSLSession ssl,
        byte[] buf, int sz)
    {
        /* call user-registered recv method */
        return internRecvSSLCb_array.receiveCallback(ssl, buf, sz,
            ssl.getIOReadCtx());
    }

    /**
     * Internal wolfSSL I/O receive callback, using ByteBuffer.
     */
    private int internalIOSSLRecvCallback(WolfSSLSession ssl,
            ByteBuffer buf, int sz)
    {
        /* call user-registered recv method */
        return internRecvSSLCb_BB.receiveCallback(ssl, buf, sz,
            ssl.getIOReadCtx());
    }

    /**
     * Internal wolfSSL I/O send callback, using byte array.
     */
    private int internalIOSSLSendCallback(WolfSSLSession ssl,
        byte[] buf, int sz)
    {
        /* call user-registered recv method */
        return internSendSSLCb_array.sendCallback(ssl, buf, sz,
            ssl.getIOWriteCtx());
    }

    /**
     * Internal wolfSSL I/O send callback, using ByteBuffer.
     */
    private int internalIOSSLSendCallback(WolfSSLSession ssl,
            ByteBuffer buf, int sz)
    {
        /* call user-registered recv method */
        return internSendSSLCb_BB.sendCallback(ssl, buf, sz,
            ssl.getIOWriteCtx());
    }

    private long internalPskClientCallback(WolfSSLSession ssl, String hint,
            StringBuffer identity, long idMaxLen, byte[] key,
            long keyMaxLen)
    {
        /* call user-registered PSK client callback method */
        return internPskClientCb.pskClientCallback(ssl, hint, identity,
            idMaxLen, key, keyMaxLen);
    }

    private long internalPskServerCallback(WolfSSLSession ssl,
            String identity, byte[] key, long keyMaxLen)
    {
        /* call user-registered PSK server callback method */
        return internPskServerCb.pskServerCallback(ssl, identity,
            key, keyMaxLen);
    }

    private int internalAlpnSelectCallback(WolfSSLSession ssl, String[] out,
        String[] in)
    {
        /* call user-registered ALPN select callback */
        return internAlpnSelectCb.alpnSelectCallback(ssl, out, in,
            this.alpnSelectArg);
    }

    private int internalTls13SecretCallback(WolfSSLSession ssl, int id,
        byte[] secret)
    {
        /* call user-registered TLS 1.3 secret callback */
        return internTls13SecretCb.tls13SecretCallback(ssl, id, secret,
            this.tls13SecretCtx);
    }

    private int internalSessionTicketCallback(WolfSSLSession ssl, byte[] ticket)
    {
        /* call user-registered session ticket callback */
        return internSessionTicketCb.sessionTicketCallback(ssl, ticket,
            this.sessionTicketCtx);
    }

    /**
     * Verifies that the current WolfSSLSession object is active.
     *
     * @throws IllegalStateException if object has been freed
     */
    private synchronized void confirmObjectIsActive()
        throws IllegalStateException {

        synchronized (stateLock) {
            if (this.active == false) {
                throw new IllegalStateException(
                    "WolfSSLSession object has been freed");
            }
        }
    }

    /* ------------------ native method declarations -------------------- */

    private native long newSSL(long ctx, boolean withIOPipe);
    private native int setFd(long ssl, Socket sd, int type);
    private native int setFd(long ssl, DatagramSocket sd, int type);
    private native int useCertificateFile(long ssl, String file, int format);
    private native int usePrivateKeyFile(long ssl, String file, int format);
    private native int useCertificateChainFile(long ssl, String file);
    private native void setUsingNonblock(long ssl, int nonblock);
    private native int getUsingNonblock(long ssl);
    private native int getFd(long ssl);
    private native int connect(long ssl, int timeout);
    private native int write(long ssl, byte[] data, int offset, int length,
        int timeout);
    private native int write(long ssl, ByteBuffer data, final int position,
        final int limit, boolean hasArray, int sz, int timeout)
        throws WolfSSLException;
    private native int read(long ssl, byte[] data, int offset, int sz,
        int timeout);
    private native int read(long ssl, ByteBuffer data, final int position,
        final int limit, boolean hasArray, int sz, int timeout)
        throws WolfSSLException;
    private native int pending(long ssl);
    private native int accept(long ssl, int timeout);
    private native void freeSSL(long ssl);
    private native int shutdownSSL(long ssl, int timeout);
    private native int getError(long ssl, int ret);
    private native int setSession(long ssl, long session);
    private native long getSession(long ssl);
    private native long get1Session(long ssl);
    private static native int wolfsslSessionIsSetup(long ssl);
    private static native int wolfsslSessionIsResumable(long ssl);
    private static native long wolfsslSessionDup(long session);
    private static native String wolfsslSessionCipherGetName(long ssl);
    private static native void freeNativeSession(long session);
    private native byte[] getSessionID(long session);
    private native int setServerID(long ssl, byte[] id, int len, int newSess);
    private native int setTimeout(long ssl, long t);
    private native long getTimeout(long ssl);
    private native int setSessTimeout(long session, long t);
    private native long getSessTimeout(long session);
    private native int setCipherList(long ssl, String list);
    private native int dtlsGetCurrentTimeout(long ssl);
    private native int dtlsGotTimeout(long ssl);
    private native int dtlsRetransmit(long ssl);
    private native int dtls(long ssl);
    private native int dtlsSetPeer(long ssl, InetSocketAddress peer);
    private native int sendHrrCookie(long ssl, byte[] secret);
    private native long getDtlsMacDropCount(long ssl);
    private native long getDtlsReplayDropCount(long ssl);
    private native InetSocketAddress dtlsGetPeer(long ssl);
    private native int sessionReused(long ssl);
    private native long getPeerCertificate(long ssl);
    private native String getPeerX509Issuer(long ssl, long x509);
    private native String getPeerX509Subject(long ssl, long x509);
    private native String getPeerX509AltName(long ssl, long x509);
    private native String getVersion(long ssl);
    private native long getCurrentCipher(long ssl);
    private native int checkDomainName(long ssl, String dn);
    private native int setTmpDH(long ssl, byte[] p, int pSz, byte[] g, int gSz);
    private native int setTmpDHFile(long ssl, String fname, int format);
    private native int useCertificateBuffer(long ssl, byte[] in, long sz,
            int format);
    private native int usePrivateKeyBuffer(long ssl, byte[] in, long sz,
            int format);
    private native int useCertificateChainBuffer(long ssl, byte[] in,
            long sz);
    private native int useCertificateChainBufferFormat(
            long ssl, byte[] in, long sz, int format);
    private native int setGroupMessages(long ssl);
    private native int enableCRL(long ssl, int options);
    private native int disableCRL(long ssl);
    private native int loadCRL(long ssl, String path, int type, int monitor);
    private native int setCRLCb(long ssl, WolfSSLMissingCRLCallback cb);
    private native String cipherGetName(long ssl);
    private native byte[] getMacSecret(long ssl, int verify);
    private native byte[] getClientWriteKey(long ssl);
    private native byte[] getClientWriteIV(long ssl);
    private native byte[] getServerWriteKey(long ssl);
    private native byte[] getServerWriteIV(long ssl);
    private native int getKeySize(long ssl);
    private native int getSide(long ssl);
    private native int isTLSv1_1(long ssl);
    private native int getBulkCipher(long ssl);
    private native int getCipherBlockSize(long ssl);
    private native int getAeadMacSize(long ssl);
    private native int getHmacSize(long ssl);
    private native int getHmacType(long ssl);
    private native int getCipherType(long ssl);
    private native int setTlsHmacInner(long ssl, byte[] inner, long sz,
            int content, int verify);
    private native void setEccSignCtx(long ssl);
    private native void setEccVerifyCtx(long ssl);
    private native void setEccSharedSecretCtx(long ssl);
    private native void setRsaSignCtx(long ssl);
    private native void setRsaVerifyCtx(long ssl);
    private native void setRsaEncCtx(long ssl);
    private native void setRsaDecCtx(long ssl);
    private native void setPskClientCb(long ctx);
    private native void setPskServerCb(long ctx);
    private native String getPskIdentityHint(long ssl);
    private native String getPskIdentity(long ssl);
    private native int usePskIdentityHint(long ssl, String hint);
    private native boolean handshakeDone(long ssl);
    private native void setConnectState(long ssl);
    private native void setAcceptState(long ssl);
    private native void setVerify(long ssl, int mode, WolfSSLVerifyCallback vc);
    private native long setOptions(long ssl, long op);
    private native long getOptions(long ssl);
    private native int getShutdown(long ssl);
    private native void setSSLIORecv(long ssl);
    private native void setSSLIOSend(long ssl);
    private native int useSNI(long ssl, byte type, byte[] data);
    private native byte[] getSNIRequest(long ssl, byte type);
    private native int useSessionTicket(long ssl);
    private native int gotCloseNotify(long ssl);
    private native int sslSetAlpnProtos(long ssl, byte[] alpnProtos);
    private native byte[] sslGet0AlpnSelected(long ssl);
    private native int useALPN(long ssl, String protocols, int options);
    private native int setALPNSelectCb(long ssl);
    private native int setTls13SecretCb(long ssl);
    private native int setSessionTicketCb(long ssl);
    private native void keepArrays(long ssl);
    private native byte[] getClientRandom(long ssl);
    private native int useSecureRenegotiation(long ssl);
    private native int rehandshake(long ssl);
    private native int set1SigAlgsList(long ssl, String list);
    private native int useSupportedCurve(long ssl, int name);
    private native int disableExtendedMasterSecret(long ssl);
    private native int hasTicket(long session);
    private native int interruptBlockedIO(long ssl);
    private native int getThreadsBlockedInPoll(long ssl);
    private native int setMTU(long ssl, int mtu);
    private native String stateStringLong(long ssl);
    private native int getMaxOutputSize(long ssl);

    /* ------------------- session-specific methods --------------------- */

    /**
     * Loads a certificate file into the SSL session object.
     * This file is provided by the <b>file</b> parameter. The <b>format</b>
     * paramenter specifies the format type of the file - either
     * <b>SSL_FILETYPE_ASN1</b> or <b>SSL_FILETYPE_PEM</b>. Please see the
     * wolfSSL examples for proper usage.
     *
     * @param file      a file containing the certificate to be loaded into
     *                  the wolfSSL SSL session object.
     * @param format    format of the certificates pointed to by <code>file
     *                  </code>. Possible options are <b>SSL_FILETYPE_ASN1</b>,
     *                  for DER-encoded certificates, or <b>SSL_FILETYPE_PEM
     *                  </b> for PEM-encoded certificates.
     * @return          <code>SSL_SUCCESS</code> upon success,
     *                  <code>SSL_BAD_FILE</code> upon bad input file,
     *                  otherwise <code>SSL_FAILURE</code>. Possible failure
     *                  causes may be that the file is in the wrong format, the
     *                  format argument was given incorrectly, the file
     *                  doesn't exist, can't be read, or is corrupted,
     *                  an out of memory condition occurs, or the Base16
     *                  decoding fails on the file.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    WolfSSLContext#useCertificateFile(String, int)
     */
    public int useCertificateFile(String file, int format)
        throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered useCertificateFile(" + file + ", " +
                format + ")");

            return useCertificateFile(this.sslPtr, file, format);
        }
    }

    /**
     * Loads a private key file into the SSL session object.
     * This file is provided by the <b>file</b> parameter. The <b>format</b>
     * paramenter specifies the format type of the file - either
     * <b>SSL_FILETYPE_ASN1</b> or <b>SSL_FILETYPE_PEM</b>. Please see the
     * wolfSSL examples for proper usage.
     *
     * @param file      a file containing the private key to be loaded into
     *                  the wolfSSL SSL session.
     * @param format    format of the private key pointed to by <code>file
     *                  </code>. Possible options are <b>SSL_FILETYPE_ASN1</b>,
     *                  for a DER-encoded key, or <b>SSL_FILETYPE_PEM
     *                  </b> for a PEM-encoded key.
     * @return          <code>SSL_SUCCESS</code> upon success,
     *                  <code>SSL_BAD_FILE</code> upon bad input file, otherwise
     *                  <code>SSL_FAILURE</code>. Possible failure causes
     *                  may be that the file is in the wrong format, the
     *                  format argument was given incorrectly, the file
     *                  doesn't exist, can't be read, or is corrupted,
     *                  an out of memory condition occurs, the Base16
     *                  decoding fails on the file, or the key file is
     *                  encrypted but no password is provided.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    WolfSSLContext#usePrivateKeyFile(String, int)
     */
    public int usePrivateKeyFile(String file, int format)
        throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered usePrivateKeyFile(" + file + ", " +
                format + ")");

            return usePrivateKeyFile(this.sslPtr, file, format);
        }
    }

    /**
     * Loads a chain of certificates into the SSL session object.
     * The file containing the certificate chain is provided by the <b>file</b>
     * parameter and must contain PEM-formatted certificates. This function
     * will process up to <code>MAX_CHAIN_DEPTH</code> (default = 9, defined
     * in internal.h) certificates, plus the subject cert.
     *
     * @param file  path to the file containing the chain of certificates
     *              to be loaded into the wolfSSL SSL session. Certificates
     *              must be in PEM format.
     * @return      <code>SSL_SUCCESS</code> on success,
     *              <code>SSL_BAD_FILE</code> upon bad input file, otherwise
     *              <code>SSL_FAILURE</code>. If the function call fails,
     *              possible causes might include: the file is in the wrong
     *              format, the file doesn't exist, can't be read, or is
     *              corrupted, or an out of memory condition occurs.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    WolfSSLContext#useCertificateFile(String, int)
     * @see    #useCertificateFile(String, int)
     */
    public int useCertificateChainFile(String file)
        throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered useCertificateChainFile(" + file + ")");

            return useCertificateChainFile(this.sslPtr, file);
        }
    }


    /**
     * Assigns a Socket file descriptor as the input/output facility for the
     * SSL connection.
     *
     * @param sd Socket to be used as input/output facility.
     * @return   <code>SSL_SUCCESS</code> on success, otherwise
     *           <code>SSL_FAILURE</code>.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getFd()
     */
    public int setFd(Socket sd) throws IllegalStateException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setFd(" + sd + ")");

            ret = setFd(this.sslPtr, sd, 1);

            if (ret == WolfSSL.SSL_SUCCESS) {
                WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                    WolfSSLDebug.INFO, this.sslPtr,
                    () -> "native fd set to: " + getFd(this.sslPtr));
            }

            return ret;
        }
    }

    /**
     * Assigns a DatagramSocket file descriptor as the input/output facility
     * for the SSL connection.
     * This can be used when using DatagramSocket objects with DTLS.
     *
     * @param sd Socket to be used as input/output facility.
     * @return   <code>SSL_SUCCESS</code> on success, otherwise
     *           <code>SSL_FAILURE</code>.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getFd()
     */
    public int setFd(DatagramSocket sd) throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setFd(" + sd + ")");

            return setFd(this.sslPtr, sd, 2);
        }
    }

    /**
     * Informs wolfSSL session that the underlying I/O is non-blocking.
     * After an application creates a SSL session (native WOLFSSL object),
     * if it will be used with a non-blocking socket, this method should
     * be called. This lets the SSL session know that receiving EWOULDBLOCK
     * means that the recvfrom call would block rather than that it timed out.
     *
     * @param nonblock  value used to set non-blocking flag on the SSL
     *                  session. Use <b>1</b> to specify non-blocking,
     *                  otherwise <b>0</b>.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #getUsingNonblock()
     * @see    #dtlsGotTimeout()
     * @see    #dtlsGetCurrentTimeout()
     */
    public void setUsingNonblock(int nonblock)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setUsingNonblock(" + nonblock + ")");

            setUsingNonblock(this.sslPtr, nonblock);
        }
    }

    /**
     * Allows the application to determine if wolfSSL is using non-blocking
     * I/O.
     * After an application created an SSL session object, if it will be used
     * with a non-blocking socket, call <code>setUsingNonblock()</code> on it.
     * This lets the SSL session object know that receiving EWOULDBLOCK means
     * that the recvfrom call would block rather than that it timed out.
     *
     * @return <b>1</b> if the underlying I/O is non-blocking, otherwise
     *         <b>0</b> if blocking.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #setUsingNonblock(int)
     * @see    #setSession(long)
     */
    public int getUsingNonblock()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getUsingNonblock()");

            return getUsingNonblock(this.sslPtr);
        }
    }

    /**
     * Returns the file descriptor used as the input/output facility for the
     * SSL connection.
     * Typically this will be a socket file descriptor.
     *
     * @return SSL session file descriptor
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #setFd(Socket)
     */
    public int getFd()
        throws IllegalStateException, WolfSSLJNIException {

        final int fd;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered getFd()");

            fd = getFd(this.sslPtr);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "returning fd: " + fd);
        }

        return fd;
    }

    /**
     * Helper method to throw appropriate exception based on native
     * result of poll()/select() from API that does I/O.
     */
    private static void throwExceptionFromIOReturnValue(
        int ret, String nativeFunc)
        throws SocketTimeoutException, SocketException {

        if (ret == WolfSSL.WOLFJNI_IO_EVENT_TIMEOUT) {
            throw new SocketTimeoutException(
                "Native socket timed out during " + nativeFunc);
        }
        else if (ret == WolfSSL.WOLFJNI_IO_EVENT_FD_CLOSED) {
            throw new SocketException("Socket fd closed during poll(), " +
                "errno = " + WolfSSL.getErrno());
        }
        else if (ret == WolfSSL.WOLFJNI_IO_EVENT_ERROR) {
            throw new SocketException("Socket fd poll() exceptional error, " +
                "errno = " + WolfSSL.getErrno());
        }
        else if (ret == WolfSSL.WOLFJNI_IO_EVENT_POLLHUP) {
            throw new SocketException("Socket disconnected during poll(), " +
                "errno = " + WolfSSL.getErrno());
        }
        else if (ret == WolfSSL.WOLFJNI_IO_EVENT_FAIL) {
            throw new SocketException("Socket select/poll() failed, " +
                "errno = " + WolfSSL.getErrno());
        }
    }

    /**
     * Initializes an SSL/TLS handshake with a server.
     * This function is called on the client side. When called, the underlying
     * communication channel should already be set up.
     * <p>
     * <code>connect()</code> works with both blocking and non-blocking I/O.
     * When the underlying I/O is non-blocking, <code>connect()</code> will
     * return when the underlying I/O could not satisfy the needs of
     * <code>connect()</code> to continue the handshake. In this case, a call
     * to <code>getError</code> will yield either <b>SSL_ERROR_WANT_READ</b> or
     * <b>SSL_ERROR_WANT_WRITE</b>. The calling process must then repeat the
     * call to <code>connect()</code> when the underlying I/O is ready and
     * wolfSSL will pick up where it left off.
     * </p><p>
     * If the underlying I/O is blocking, <code>connect()</code> will only
     * return once the handshake has been finished or an error occurred.
     * </p><p>
     * wolfSSL takes a different approach to certificate verification than
     * OpenSSL does. The default policy for clients is to verify the server,
     * meaning that if the application doesn't load CA certificates to verify
     * the server, it will get a connect error, "unable to verify" (-155). If
     * the application wants to mimic OpenSSL behavior of having
     * <code>connect()</code> succeed even if verifying the server fails (and
     * reducing security), the application can do this by calling:
     * </p><p>
     * <code>WolfSSLContext#setVerify(ctx, SSL_VERIFY_NONE, 0);</code>
     * </p><p>
     * before calling <code>newSSL()</code>, though it's not recommended.
     * </p>
     *
     * @return <code>SSL_SUCCESS</code> if successful, otherwise
     *         <code>SSL_FAILURE</code> if an error occurred. To get
     *         a more detailed error code, call <code>getError()</code>.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if underlying socket timed out
     * @throws SocketException Native socket select() or poll() failed
     */
    public int connect()
        throws IllegalStateException, SocketTimeoutException, SocketException {

        return connect(0);
    }

    /**
     * Initializes an SSL/TLS handshake with a server, using socket timeout
     * value in milliseconds.
     * This function is called on the client side. When called, the underlying
     * communication channel should already be set up.
     * <p>
     * <code>connect()</code> works with both blocking and non-blocking I/O.
     * When the underlying I/O is non-blocking, <code>connect()</code> will
     * return when the underlying I/O could not satisfy the needs of
     * <code>connect()</code> to continue the handshake. In this case, a call
     * to <code>getError</code> will yield either <b>SSL_ERROR_WANT_READ</b> or
     * <b>SSL_ERROR_WANT_WRITE</b>. The calling process must then repeat the
     * call to <code>connect()</code> when the underlying I/O is ready and
     * wolfSSL will pick up where it left off.
     * <p>
     * If the underlying I/O is blocking, <code>connect()</code> will only
     * return once the handshake has been finished or an error occurred.
     * <p>
     * wolfSSL takes a different approach to certificate verification than
     * OpenSSL does. The default policy for clients is to verify the server,
     * meaning that if the application doesn't load CA certificates to verify
     * the server, it will get a connect error, "unable to verify" (-155). If
     * the application wants to mimic OpenSSL behavior of having
     * <code>connect()</code> succeed even if verifying the server fails (and
     * reducing security), the application can do this by calling:
     * <p>
     * <code>WolfSSLContext#setVerify(ctx, SSL_VERIFY_NONE, 0);</code>
     * <p>
     * before calling <code>newSSL()</code>, though it's not recommended.
     *
     * @param timeout read timeout, milliseconds. Specify 0 to use infinite
     *                timeout
     *
     * @return <code>SSL_SUCCESS</code> if successful, otherwise
     *         <code>SSL_FAILURE</code> if an error occurred. To get
     *         a more detailed error code, call <code>getError()</code>.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if socket timeout occurs
     * @throws SocketException Native socket select() or poll() failed
     */
    public int connect(int timeout)
        throws IllegalStateException, SocketTimeoutException, SocketException {

        final int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered connect(timeout: " + timeout +")");

            ret = connect(this.sslPtr, timeout);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "connect() ret: " + ret + ", err: " + getError(ret));
        }

        throwExceptionFromIOReturnValue(ret, "wolfSSL_connect()");

        return ret;
    }

    /**
     * Write bytes from a byte array to the SSL connection.
     * If necessary, <code>write()</code> will negotiate an SSL/TLS session
     * if the handshake has not already been performed yet by <code>connect
     * </code> or <code>accept</code>.
     * <p>
     * <code>write()</code> works with both blocking and non-blocking I/O.
     * When the underlying I/O is non-blocking, <code>write()</code> will
     * return when the underlying I/O could not satisfy the needs of <code>
     * write()</code> to continue. In this case, a call to <code>getError
     * </code> will yield either <b>SSL_ERROR_WANT_READ</b> or
     * <b>SSL_ERROR_WANT_WRITE</b>. The calling process must then repeat the
     * call to <code>write()</code> when the underlying I/O is ready.
     * <p>
     * If the underlying I/O is blocking, <code>write()</code> will only
     * return once the buffer <b>data</b> of size <b>length</b> has been
     * completely written or an error occurred.
     *
     * @param data   data buffer which will be sent to peer
     * @param length size, in bytes, of data to send to the peer
     * @return       the number of bytes written upon success. <code>0
     *               </code>or a negative value will be returned upon failure.
     *               <code>SSL_FAILURE</code>return upon failure when either
     *               an error occurred or, when using non-blocking sockets,
     *               the <b>SSL_ERROR_WANT_READ</b> or
     *               <b>SSL_ERROR_WANT_WRITE</b> error was received and the
     *               application needs to call <code>write()</code> again.
     *               <code>BAD_FUNC_ARC</code> when bad arguments are used.
     *               Use <code>getError</code> to get a specific error code.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if socket timeout occurs
     * @throws SocketException Native socket select() or poll() failed
     */
    public int write(byte[] data, int length)
        throws IllegalStateException, SocketTimeoutException, SocketException {

        return write(data, 0, length, 0);
    }

    /**
     * Write bytes from a byte array to the SSL connection, using socket
     * timeout value in milliseconds.
     * If necessary, <code>write()</code> will negotiate an SSL/TLS session
     * if the handshake has not already been performed yet by <code>connect
     * </code> or <code>accept</code>.
     * <p>
     * <code>write()</code> works with both blocking and non-blocking I/O.
     * When the underlying I/O is non-blocking, <code>write()</code> will
     * return when the underlying I/O could not satisfy the needs of <code>
     * write()</code> to continue. In this case, a call to <code>getError
     * </code> will yield either <b>SSL_ERROR_WANT_READ</b> or
     * <b>SSL_ERROR_WANT_WRITE</b>. The calling process must then repeat the
     * call to <code>write()</code> when the underlying I/O is ready.
     * <p>
     * If the underlying I/O is blocking, <code>write()</code> will only
     * return once the buffer <b>data</b> of size <b>length</b> has been
     * completely written or an error occurred.
     *
     * @param data   data buffer which will be sent to peer
     * @param length size, in bytes, of data to send to the peer
     * @param timeout read timeout, milliseconds.
     * @return       the number of bytes written upon success. <code>0
     *               </code>will be returned upon failure. <code>
     *               SSL_FATAL_ERROR</code>upon failure when either an
     *               error occurred or, when using non-blocking sockets,
     *               the <b>SSL_ERROR_WANT_READ</b> or
     *               <b>SSL_ERROR_WANT_WRITE</b> error was received and the
     *               application needs to call <code>write()</code> again.
     *               <code>BAD_FUNC_ARC</code> when bad arguments are used.
     *               Use <code>getError</code> to get a specific error code.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if socket timeout occurs
     * @throws SocketException Native socket select/poll() failed
     */
    public int write(byte[] data, int length, int timeout)
        throws IllegalStateException, SocketTimeoutException, SocketException {

        return write(data, 0, length, timeout);
    }

    /**
     * Write bytes from a byte array to the SSL connection, using socket
     * timeout value in milliseconds.
     * If necessary, <code>write()</code> will negotiate an SSL/TLS session
     * if the handshake has not already been performed yet by <code>connect
     * </code> or <code>accept</code>.
     * <p>
     * <code>write()</code> works with both blocking and non-blocking I/O.
     * When the underlying I/O is non-blocking, <code>write()</code> will
     * return when the underlying I/O could not satisfy the needs of <code>
     * write()</code> to continue. In this case, a call to <code>getError
     * </code> will yield either <b>SSL_ERROR_WANT_READ</b> or
     * <b>SSL_ERROR_WANT_WRITE</b>. The calling process must then repeat the
     * call to <code>write()</code> when the underlying I/O is ready.
     * <p>
     * If the underlying I/O is blocking, <code>write()</code> will only
     * return once the buffer <b>data</b> of size <b>length</b> has been
     * completely written or an error occurred.
     *
     * @param data   data buffer which will be sent to peer
     * @param offset offset into data buffer to start writing from
     * @param length size, in bytes, of data to send to the peer
     * @param timeout read timeout, milliseconds.
     * @return       the number of bytes written upon success. <code>0
     *               </code>will be returned upon failure. <code>
     *               SSL_FATAL_ERROR</code>upon failure when either an
     *               error occurred or, when using non-blocking sockets,
     *               the <b>SSL_ERROR_WANT_READ</b> or
     *               <b>SSL_ERROR_WANT_WRITE</b> error was received and the
     *               application needs to call <code>write()</code> again.
     *               <code>BAD_FUNC_ARC</code> when bad arguments are used.
     *               Use <code>getError</code> to get a specific error code.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if socket timeout occurs
     * @throws SocketException Native socket select/poll() failed
     */
    public int write(byte[] data, int offset, int length, int timeout)
        throws IllegalStateException, SocketTimeoutException, SocketException {

        int ret = 0;
        int err = 0;
        int totalWritten = 0;
        int remaining = length;
        long localPtr;
        ByteBuffer directBuffer = null;

        confirmObjectIsActive();

        /* Fix for Infer scan, since not synchronizing on sslLock for
         * access to this.sslPtr, see note below */
        synchronized (sslLock) {
            localPtr = this.sslPtr;
        }

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, localPtr,
            () -> "entered write(offset: " + offset + ", length: " +
            length + ", timeout: " + timeout + ")");

        /* Use a direct ByteBuffer from the pool to avoid unaligned
         * memory access. Otherwise our native JNI code may need to do
         * "buffer + offset" and end up with unaligned memory which
         * can be slow on some targets (ex: ARM/Aarch64) */
        try {
            if (!this.byteBufferPoolEnabled) {
                /* Throw exception so we fall back to using byte[] in the
                 * catch block below */
                throw new Exception("ByteBuffer pool not enabled");
            }

            /* Get a buffer from the pool */
            directBuffer = acquireDirectBuffer();

            WolfSSLDebug.log(getClass(),
                WolfSSLDebug.Component.JNI, WolfSSLDebug.INFO, localPtr,
                () -> "write() using thread-local ByteBuffer pool: pool size: " +
                directBufferPool.get().size());

            /* Write in chunks until all data is written or an error occurs.
             * The DirectByteBuffer size might be smaller than the data length,
             * so we need to loop to handle all the data */
            while (remaining > 0) {
                /* Calculate size for current chunk */
                int writeSize = Math.min(remaining, directBuffer.capacity());

                /* Copy data from user array to direct buffer */
                directBuffer.clear();
                directBuffer.put(data, offset + totalWritten, writeSize);
                directBuffer.flip();

                /* Call native write with DirectByteBuffer */
                ret = write(localPtr, directBuffer, directBuffer.position(),
                    directBuffer.limit(), false, writeSize, timeout);

                if (ret <= 0) {
                    /* Error occurred, break out of loop */
                    err = getError(ret);
                    break;
                }

                /* Update tracking variables */
                totalWritten += ret;
                remaining -= ret;
            }

        } catch (Exception e) {

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.ERROR, localPtr,
                () -> "write() falling back to use byte[]");

            /* Fall back to original implementation on exception (write not
             * done yet at this point in JNI call above) */
            totalWritten = write(localPtr, data, offset, length, timeout);

        } finally {
            /* Return buffer to pool */
            if (directBuffer != null) {
                releaseDirectBuffer(directBuffer);
            }
        }

        /* Return total bytes written, or last error code */
        final int finalRet = (totalWritten > 0) ? totalWritten : ret;
        final int finalErr = err;
        final int finalTotal = totalWritten;

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, localPtr,
            () -> "write() ret: " + finalRet + ", err: " + finalErr +
                  ", totalWritten: " + finalTotal);

        throwExceptionFromIOReturnValue(finalRet, "wolfSSL_write()");

        return finalRet;
    }

    /**
     * Reads bytes from the SSL session and returns the read bytes as a byte
     * array.
     * The bytes read are removed from the internal receive buffer.
     * <p>
     * If necessary, <code>read()</code> will negotiate an SSL/TLS session
     * if the handshake has not already been performed yet by <code>connect()
     * </code> or <code>accept()</code>.
     * <p>
     * The SSL/TLS protocol uses SSL records which have a maximum size of
     * 16kB. As such, wolfSSL needs to read an entire SSL record internally
     * before it is able to process and decrypt the record. Because of this,
     * a call to <code>read()</code> will only be able to return the
     * maximum buffer size which has been decrypted at the time of calling.
     * There may be additional not-yet-decrypted data waiting in the internal
     * wolfSSL receive buffer which will be retrieved and decrypted with the
     * next call to <code>read()</code>.
     *
     * @param data  buffer where the data read from the SSL connection
     *              will be placed.
     * @param sz    number of bytes to read into <b><code>data</code></b>
     * @return      the number of bytes read upon success. <code>SSL_FAILURE
     *              </code> will be returned upon failure which may be caused
     *              by either a clean (close notify alert) shutdown or just
     *              that the peer closed the connection. <code>
     *              SSL_FATAL_ERROR</code> upon failure when either an error
     *              occurred or, when using non-blocking sockets, the
     *              <b>SSL_ERROR_WANT_READ</b> or <b>SSL_ERROR_WANT_WRITE</b>
     *              error was received and the application needs to call
     *              <code>read()</code> again. Use <code>getError</code> to
     *              get a specific error code.
     *              <code>BAD_FUNC_ARC</code> when bad arguments are used.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if socket timeout occurs, should not
     *         occur since infinite timeout is used for this call.
     * @throws SocketException Native socket select/poll() failed
     */
    public int read(byte[] data, int sz)
        throws IllegalStateException, SocketTimeoutException, SocketException {

        return read(data, 0, sz, 0);
    }

    /**
     * Reads bytes from the SSL session and returns the read bytes as a byte
     * array, using socket timeout value in milliseconds.
     * The bytes read are removed from the internal receive buffer.
     * <p>
     * If necessary, <code>read()</code> will negotiate an SSL/TLS session
     * if the handshake has not already been performed yet by <code>connect()
     * </code> or <code>accept()</code>.
     * <p>
     * The SSL/TLS protocol uses SSL records which have a maximum size of
     * 16kB. As such, wolfSSL needs to read an entire SSL record internally
     * before it is able to process and decrypt the record. Because of this,
     * a call to <code>read()</code> will only be able to return the
     * maximum buffer size which has been decrypted at the time of calling.
     * There may be additional not-yet-decrypted data waiting in the internal
     * wolfSSL receive buffer which will be retrieved and decrypted with the
     * next call to <code>read()</code>.
     *
     * @param data  buffer where the data read from the SSL connection
     *              will be placed.
     * @param sz    number of bytes to read into <b><code>data</code></b>
     * @param timeout read timeout, milliseconds.
     * @return      the number of bytes read upon success. <code>SSL_FAILURE
     *              </code> will be returned upon failure which may be caused
     *              by either a clean (close notify alert) shutdown or just
     *              that the peer closed the connection. <code>
     *              SSL_FATAL_ERROR</code> upon failure when either an error
     *              occurred or, when using non-blocking sockets, the
     *              <b>SSL_ERROR_WANT_READ</b> or <b>SSL_ERROR_WANT_WRITE</b>
     *              error was received and the application needs to call
     *              <code>read()</code> again. Use <code>getError</code> to
     *              get a specific error code.
     *              <code>BAD_FUNC_ARC</code> when bad arguments are used.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if socket timeout occurs
     * @throws SocketException Native socket select/poll() failed
     */
    public int read(byte[] data, int sz, int timeout)
        throws IllegalStateException, SocketTimeoutException, SocketException {

        return read(data, 0, sz, timeout);
    }

    /**
     * Reads bytes from the SSL session and returns the read bytes as a byte
     * array, using socket timeout value in milliseconds.
     * The bytes read are removed from the internal receive buffer.
     * <p>
     * If necessary, <code>read()</code> will negotiate an SSL/TLS session
     * if the handshake has not already been performed yet by <code>connect()
     * </code> or <code>accept()</code>.
     * <p>
     * The SSL/TLS protocol uses SSL records which have a maximum size of
     * 16kB. As such, wolfSSL needs to read an entire SSL record internally
     * before it is able to process and decrypt the record. Because of this,
     * a call to <code>read()</code> will only be able to return the
     * maximum buffer size which has been decrypted at the time of calling.
     * There may be additional not-yet-decrypted data waiting in the internal
     * wolfSSL receive buffer which will be retrieved and decrypted with the
     * next call to <code>read()</code>.
     *
     * @param data  buffer where the data read from the SSL connection
     *              will be placed.
     * @param offset offset into data buffer for data to be placed.
     * @param sz    number of bytes to read into <b><code>data</code></b>
     * @param timeout read timeout, milliseconds.
     * @return      the number of bytes read upon success. <code>SSL_FAILURE
     *              </code> will be returned upon failure which may be caused
     *              by either a clean (close notify alert) shutdown or just
     *              that the peer closed the connection. <code>
     *              SSL_FATAL_ERROR</code> upon failure when either an error
     *              occurred or, when using non-blocking sockets, the
     *              <b>SSL_ERROR_WANT_READ</b> or <b>SSL_ERROR_WANT_WRITE</b>
     *              error was received and the application needs to call
     *              <code>read()</code> again. Use <code>getError</code> to
     *              get a specific error code.
     *              <code>BAD_FUNC_ARC</code> when bad arguments are used.
     * @throws IllegalStateException WolfSSLSession has been freed
     * @throws SocketTimeoutException if socket timeout occurs
     * @throws SocketException Native socket select/poll() failed
     */
    public int read(byte[] data, int offset, int sz, int timeout)
        throws IllegalStateException, SocketTimeoutException, SocketException {

        int ret;
        int err;
        int readSz = sz;
        final int readOff = offset;
        final int tmpReadSz = sz;
        final int readTimeout = timeout;
        long localPtr;
        ByteBuffer directBuffer = null;

        confirmObjectIsActive();

        /* Fix for Infer scan, since not synchronizing on sslLock for
         * access to this.sslPtr, see note below */
        synchronized (sslLock) {
            localPtr = this.sslPtr;
        }

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, localPtr,
            () -> "entered read(offset: " + readOff + ", sz: " + tmpReadSz +
            ", timeout: " + readTimeout + ")");

        /* Use a DirectByteBuffer from the pool to avoid unaligned
         * memory access. Otherwise our native JNI code may need to
         * do "buffer + offset" and end up with unaligned memory which
         * can be slow on some targets (ex: ARM/Aarch64) */
        try {
            if (!this.byteBufferPoolEnabled) {
                /* Throw exception so we fall back to using byte[] in the
                 * catch block below */
                throw new Exception("ByteBuffer pool not enabled");
            }

            /* Get a buffer from the pool */
            directBuffer = acquireDirectBuffer();

            WolfSSLDebug.log(getClass(),
                WolfSSLDebug.Component.JNI, WolfSSLDebug.INFO, localPtr,
                () -> "read() using thread-local ByteBuffer pool: pool size: " +
                directBufferPool.get().size());

            /* Only read up to the size of the buffer or readSz,
             * whichever is smaller. */
            readSz = Math.min(readSz, directBuffer.capacity());

            /* Use direct buffer for JNI call */
            directBuffer.limit(readSz);

            /* Call native read with DirectByteBuffer */
            ret = read(localPtr, directBuffer, 0, readSz, false,
                readSz, readTimeout);

            if (ret > 0) {
                /* Copy data from direct buffer to user array */
                directBuffer.flip();
                directBuffer.get(data, offset, ret);
            }

            err = getError(ret);

        } catch (Exception e) {

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, localPtr,
                () -> "read() falling back to use byte[]");

            /* Fall back to original implementation on errors */
            ret = read(localPtr, data, readOff, readSz, readTimeout);
            err = getError(ret);

        } finally {

            /* Return buffer to pool */
            if (directBuffer != null) {
                releaseDirectBuffer(directBuffer);
            }
        }

        final int finalRet = ret;
        final int finalErr = err;
        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, localPtr,
            () -> "read() ret: " + finalRet + ", err: " + finalErr);

        throwExceptionFromIOReturnValue(ret, "wolfSSL_read()");

        return ret;
    }

    /**
     * Reads bytes from the SSL session and returns the read bytes into
     * the provided ByteBuffer, using socket timeout value in milliseconds.
     *
     * The bytes read are removed from the internal receive buffer.
     * <p>
     * If necessary, <code>read()</code> will negotiate an SSL/TLS session
     * if the handshake has not already been performed yet by <code>connect()
     * </code> or <code>accept()</code>.
     * <p>
     * The SSL/TLS protocol uses SSL records which have a maximum size of
     * 16kB. As such, wolfSSL needs to read an entire SSL record internally
     * before it is able to process and decrypt the record. Because of this,
     * a call to <code>read()</code> will only be able to return the
     * maximum buffer size which has been decrypted at the time of calling.
     * There may be additional not-yet-decrypted data waiting in the internal
     * wolfSSL receive buffer which will be retrieved and decrypted with the
     * next call to <code>read()</code>.
     *
     * @param data  ByteBuffer where the data read from the SSL connection
     *              will be placed. position() will be updated after this
     *              method writes data to the ByteBuffer.
     * @param sz    number of bytes to read into <b><code>data</code></b>,
     *              may be adjusted to the maximum space in data if that is
     *              smaller than this size.
     * @param timeout read timeout, milliseconds.
     * @return      the number of bytes read upon success. <code>SSL_FAILURE
     *              </code> will be returned upon failure which may be caused
     *              by either a clean (close notify alert) shutdown or just
     *              that the peer closed the connection. <code>
     *              SSL_FATAL_ERROR</code> upon failure when either an error
     *              occurred or, when using non-blocking sockets, the
     *              <b>SSL_ERROR_WANT_READ</b> or <b>SSL_ERROR_WANT_WRITE</b>
     *              error was received and the application needs to call
     *              <code>read()</code> again. Use <code>getError</code> to
     *              get a specific error code.
     *              <code>BAD_FUNC_ARC</code> when bad arguments are used.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if socket timeout occurs
     * @throws SocketException Native socket select/poll() failed
     */
    public int read(ByteBuffer data, int sz, int timeout)
        throws IllegalStateException, SocketTimeoutException, SocketException {

        final int ret;
        final int err;
        final int readSz = sz;
        final int readTimeout = timeout;
        final int bbPosition;
        final int bbLimit;
        boolean hasArray;
        long localPtr;

        confirmObjectIsActive();

        /* Fix for Infer scan, since not synchronizing on sslLock for
         * access to this.sslPtr, see note below */
        synchronized (sslLock) {
            localPtr = this.sslPtr;
        }

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, localPtr,
            () -> "entered read(ByteBuffer, sz: " + readSz +
            ", timeout: " + readTimeout + ")");

        /* not synchronizing on sslLock here since JNI read() locks
         * session mutex around native wolfSSL_read() call. If sslLock
         * is locked here, since we call select() inside native JNI we
         * could timeout waiting for corresponding write() operation to
         * occur if needed */
        try {
            bbPosition = data.position();
            bbLimit = data.limit();
            hasArray = data.hasArray();

            ret = read(localPtr, data, bbPosition, bbLimit, hasArray,
                readSz, readTimeout);
            err = getError(ret);
        } catch (WolfSSLException e) {
            /* JNI code may throw WolfSSLException on JNI specific errors */
            throw new SocketException(e.getMessage());
        }

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, localPtr,
            () -> "read() ret: " + ret + ", err: " + err);

        throwExceptionFromIOReturnValue(ret, "wolfSSL_read()");

        return ret;
    }

    /**
     * Return number of bytes available on the internal output buffer that
     * have already been decrypted and available immediately via a call to
     * read().
     *
     * @return number of bytes available on success, WolfSSL.SSL_FAILURE
     *         on error.
     *
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public int pending() throws IllegalStateException {

        int ret = 0;

        confirmObjectIsActive();

        synchronized (sslLock) {
            ret = pending(this.sslPtr);
        }

        return ret;
    }

    /**
     * Waits for an SSL client to initiate the SSL/TLS handshake.
     * This method is called on the server side. When it is called, the
     * underlying communication channel has already been set up.
     * <p>
     * <code>accept()</code> works with both blocking and non-blocking I/O.
     * When the underlying I/O is non-blocking, <code>accept()</code> will
     * return when the underlying I/O could not satisfy the needs of
     * <code>accept()</code> to continue the handshake. In this case, a call to
     * <code>getError()</code> will yield either <b>SSL_ERROR_WANT_READ</b> or
     * <b>SSL_ERROR_WANT_WRITE</b>. The calling process must then repeat the
     * call to <code>accept()</code> when data is available to be read and
     * wolfSSL will pick up where it left off. When using a non-blocking
     * socket, nothing needs to be done, but <code>select()</code> can be used
     * to check for the required condition.
     * <p>
     * If the underlying I/O is blocking, <code>accept()</code> will only
     * return once the handshake has been finished or an error occurred.
     *
     * @return <code>SSL_SUCCESS</code> on success. <code>SSL_FATAL_ERROR
     *         </code> if an error occurred. To get a more detailed
     *         error code, call <code>getError()</code>.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if underlying socket timed out
     * @throws SocketException Native socket select/accept() failed
     * @see    #getError(int)
     * @see    #connect()
     */
    public int accept()
        throws IllegalStateException, SocketTimeoutException, SocketException {

        return accept(0);
    }

    /**
     * Waits for an SSL client to initiate the SSL/TLS handshake, using socket
     * timeout value in milliseconds.
     * This method is called on the server side. When it is called, the
     * underlying communication channel has already been set up.
     * <p>
     * <code>accept()</code> works with both blocking and non-blocking I/O.
     * When the underlying I/O is non-blocking, <code>accept()</code> will
     * return when the underlying I/O could not satisfy the needs of
     * <code>accept()</code> to continue the handshake. In this case, a call to
     * <code>getError()</code> will yield either <b>SSL_ERROR_WANT_READ</b> or
     * <b>SSL_ERROR_WANT_WRITE</b>. The calling process must then repeat the
     * call to <code>accept()</code> when data is available to be read and
     * wolfSSL will pick up where it left off. When using a non-blocking
     * socket, nothing needs to be done, but <code>select()</code> can be used
     * to check for the required condition.
     * <p>
     * If the underlying I/O is blocking, <code>accept()</code> will only
     * return once the handshake has been finished or an error occurred.
     *
     * @param timeout read timeout, milliseconds.
     *
     * @return <code>SSL_SUCCESS</code> on success. <code>SSL_FATAL_ERROR
     *         </code> if an error occurred. To get a more detailed
     *         error code, call <code>getError()</code>.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if underlying socket timed out
     * @throws SocketException Native socket select() failed
     * @see    #getError(int)
     * @see    #connect()
     */
    public int accept(int timeout)
        throws IllegalStateException, SocketTimeoutException, SocketException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered accept(timeout: " + timeout + ")");

            ret = accept(this.sslPtr, timeout);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "accept() ret: " + ret + ", err: " + getError(ret));
        }

        throwExceptionFromIOReturnValue(ret, "wolfSSL_accept()");

        return ret;
    }

    /**
     * Frees an allocated SSL session.
     *
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#newContext(long)
     * @see    WolfSSLContext#free()
     */
    public void freeSSL()
        throws IllegalStateException, WolfSSLJNIException {

        synchronized (sslLock) {
            synchronized (stateLock) {
                if (this.active == false) {
                    WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                        WolfSSLDebug.INFO,
                        () -> "entered freeSSL(), already freed");
                    /* already freed, just return */
                    return;
                }

                WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                    WolfSSLDebug.INFO, this.sslPtr,
                    () -> "entered freeSSL()");

                /* free native resources */
                freeSSL(this.sslPtr);

                /* free Java resources */
                this.active = false;
                this.sslPtr = 0;
                this.clientSNIRequested = null;
                this.ctx = null;
            }
        }
    }

    /**
     * Shuts down the active SSL/TLS connection using the SSL session.
     * This function will try to send a "close notify" alert to the peer,
     * with read timeout disabled (set to infinite).
     * <p>
     * The calling application can choose to wait for the peer to send its
     * "close notify" alert in response or just go ahead and shut down the
     * underlying connection after directly calling <code>shutdownSSL</code>
     * (to save resources). Either option is allowed by the TLS specification.
     * If the underlying connection will be used again in the future, the
     * complete two-directional shutdown procedure must be performed to keep
     * synchronization intact between the peers.
     * <p>
     * <code>shutdownSSL()</code> works with both blocking and non-blocking
     * I/O. When the underlying I/O is non-blocking, <code>shutdownSSL()
     * </code> will return an error if the underlying I/O could not satisfy the
     * needs of <code>shutdownSSL()</code> to continue. In this case, a call
     * to <code>getError()</code> will yield either <b>SSL_ERROR_WANT_READ</b>
     * or <b>SSL_ERROR_WANT_WRITE</b>. The calling process must then repeat
     * the call to <code>shutdownSSL()</code> when the underlying I/O is ready.
     *
     * @return <code>SSL_SUCCESS</code> on success,
     *         <code>SSL_FATAL_ERROR</code> upon failure. Call <code>
     *         getError()</code> for a more specific error code.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if socket timeout occurs, should not
     *         since infinite timeout is used for this call.
     * @throws SocketException Native socket select/poll() failed
     * @see    #shutdownSSL(int)
     * @see    #freeSSL(long)
     * @see    WolfSSLContext#free()
     */
    public int shutdownSSL()
        throws IllegalStateException, SocketTimeoutException, SocketException {

        return shutdownSSL(0);
    }

    /**
     * Shuts down the active SSL/TLS connection using the SSL session
     * and provided read timeout value in milliseconds.
     * This function will try to send a "close notify" alert to the peer.
     * <p>
     * The calling application can choose to wait for the peer to send its
     * "close notify" alert in response or just go ahead and shut down the
     * underlying connection after directly calling <code>shutdownSSL</code>
     * (to save resources). Either option is allowed by the TLS specification.
     * If the underlying connection will be used again in the future, the
     * complete two-directional shutdown procedure must be performed to keep
     * synchronization intact between the peers.
     * <p>
     * <code>shutdownSSL()</code> works with both blocking and non-blocking
     * I/O. When the underlying I/O is non-blocking, <code>shutdownSSL()
     * </code> will return an error if the underlying I/O could not satisfy the
     * needs of <code>shutdownSSL()</code> to continue. In this case, a call
     * to <code>getError()</code> will yield either <b>SSL_ERROR_WANT_READ</b>
     * or <b>SSL_ERROR_WANT_WRITE</b>. The calling process must then repeat
     * the call to <code>shutdownSSL()</code> when the underlying I/O is ready.
     *
     * @param timeout read timeout, milliseconds.
     *
     * @return <code>SSL_SUCCESS</code> on success,
     *         <code>SSL_FATAL_ERROR</code> upon failure. Call <code>
     *         getError()</code> for a more specific error code.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws SocketTimeoutException if socket timeout occurs.
     * @throws SocketException Native socket select/poll() failed
     * @see    #freeSSL(long)
     * @see    WolfSSLContext#free()
     */
    public int shutdownSSL(int timeout)
        throws IllegalStateException, SocketTimeoutException, SocketException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered shutdownSSL(timeout: " + timeout + ")");

            ret = shutdownSSL(this.sslPtr, timeout);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "shutdownSSL() ret: " + ret + ", err: " + getError(ret));
        }

        throwExceptionFromIOReturnValue(ret, "wolfSSL_shutdown()");

        return ret;
    }

    /**
     * Returns a unique error code describing why the previous API function
     * call resulted in an error return code.
     * The return value of the previous function is passed to <code>getError()
     * </code>through <code>ret</code>.
     * <p>
     * After <code>getError()</code> is called and returns the unique error
     * code, <code>getErrorString()</code> may be called to get a human-
     * readable error string.
     *
     * @param ret  return value of the previous function which resulted
     *             in an error return code.
     * @return     the unique error code describing why the previous API
     *             function failed. SSL_ERROR_NONE will be returned if
     *             <code>ret</code> is less than 0.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    WolfSSL#getErrorString(long)
     */
    public int getError(int ret) throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            return getError(this.sslPtr, ret);
        }
    }

    /**
     * Sets the session (native WOLFSSL_SESSION) to be used with this object
     * for session resumption.
     *
     * The native WOLFSSL_SESSION pointed to contains all the necessary
     * information required to perform a session resumption and reestablishment
     * of the connection without a new handshake.
     * <p>
     * To do session resumption, before calling <code>shutdownSSL()</code>
     * with your WolfSSLSession object, save the internal session state by
     * calling <code>getSession()</code>, which returns a pointer to the
     * native WOLFSSL_SESSION session structure. Later, when the application
     * is ready to resume a session, it should create a new WolfSSLSession
     * object and assign the previously-saved session pointer by passing it
     * to the <code>setSession(long session)</code> method. This should be
     * done before the handshake is started for the second/resumed time. After
     * calling <code>setSession(long session)</code>, the application may call
     * <code>connect()</code> and wolfSSL will try to resume the session. If
     * the session cannot be resumed, a new fresh handshake will be
     * established.
     *
     * @param session  pointer to the native WOLFSSL_SESSION structure used
     *                 to set the session for the SSL session object.
     * @return         <code>SSL_SUCCESS</code> upon successfully setting
     *                 the session. <code>SSL_FAILURE</code> will be
     *                 returned on failure. This could be caused by the
     *                 session cache being disabled, or if the session has
     *                 timed out.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getSession()
     */
    public int setSession(long session) throws IllegalStateException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setSession(ptr: " + session + ")");

            if ((session != 0) && (wolfsslSessionIsSetup(session) == 1)) {
                WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                    WolfSSLDebug.INFO, this.sslPtr,
                    () -> "session pointer (" + session + ") is setup");
            }
            else {
                WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                    WolfSSLDebug.INFO, this.sslPtr,
                    () -> "session pointer is null or not set up");
            }

            ret = setSession(this.sslPtr, session);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "setSession(session: " + session + ") ret: " +
                ret + ", err: " + getError(ret));

            return ret;
        }
    }

    /**
     * Returns a pointer to the current session (native WOLFSSL_SESSION)
     * associated with this object, or null if not available.
     *
     * The native WOLFSSL_SESSION pointed to contains all the necessary
     * information required to perform a session resumption and reestablishment
     * of the connection without a new handshake.
     * <p>
     * To do session resumption, before calling <code>shutdownSSL()</code>
     * with your WolfSSLSession object, save the internal session state by
     * calling <code>getSession()</code>, which returns a pointer to the
     * native WOLFSSL_SESSION session structure. Later, when the application
     * is ready to resume a session, it should create a new WolfSSLSession
     * object and assign the previously-saved session pointer by passing it
     * to the <code>setSession(long session)</code> method. This should be
     * done before the handshake is started for the second/resumed time. After
     * calling <code>setSession(long session)</code>, the application may call
     * <code>connect()</code> and wolfSSL will try to resume the session. If
     * the session cannot be resumed, a new fresh handshake will be
     * established.
     * <p>
     * <b>IMPORTANT:</b>
     * <p>
     * The pointer (WOLFSSL_SESSION) returned by this method needs to be freed
     * when the application is finished with it by calling
     * <code>freeSession(long session)</code>. This will release the underlying
     * native memory associated with this WOLFSSL_SESSION. Failing to free
     * the session will result in a memory leak.
     *
     * @throws IllegalStateException this WolfSSLSession has been freed
     * @return      a pointer to the current SSL session object on success.
     *              <code>null</code> if <b>ssl</b> is <code>null</code>,
     *              the SSL session cache is disabled, wolfSSL doesn't have
     *              the session ID available, or mutex functions fail.
     * @see         #setSession(long)
     */
    public long getSession() throws IllegalStateException {

        final long sessPtr;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered getSession()");

            /* Calling get1Session() here as an indication that the native
             * JNI level should always return a session pointer that needs
             * to be freed by the application. This behavior can change in
             * native wolfSSL depending on build options
             * (ex: NO_SESSION_CACHE_REF), so JNI layer here will make that
             * behavior consistent to the JNI/JSSE callers. */
            sessPtr = get1Session(this.sslPtr);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "get1Session(), ret ptr: " + sessPtr);

            if ((sessPtr != 0) && (wolfsslSessionIsSetup(sessPtr) == 1)) {
                WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                    WolfSSLDebug.INFO, this.sslPtr,
                    () -> "session pointer (" + sessPtr + ") is setup");
            }
            else {
                WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                    WolfSSLDebug.INFO, this.sslPtr,
                    () -> "session pointer is null or not set up");
            }

            return sessPtr;
        }
    }

    /**
     * Check if native WOLFSSL_SESSION has been set up or not.
     *
     * This method is static and does not check active state since this
     * takes a native pointer and has no interaction with the rest of this
     * object.
     *
     * @param session pointer to native WOLFSSL_SESSION structure. May be
     *        obtained from getSession().
     *
     * @return 1 if session has been set up, otherwise 0 if not set up. May
     *         return WolfSSL.NOT_COMPILED_IN if native wolfSSL does not have
     *         wolfSSL_SessionIsSetup() compiled in. This API was added
     *         after the wolfSSL 5.7.0 release.
     */
    public static int sessionIsSetup(long session) {

        int ret;

        WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, () -> "entered sessionIsSetup(" + session + ")");

        if (session == 0) {
            WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO,
                () -> "sessionIsSetup(), ptr null, returning 0");
            return 0;
        }

        ret = wolfsslSessionIsSetup(session);

        WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO,
            () -> "sessionIsSetup(" + session + ") ret: " + ret);

        return ret;
    }

    /**
     * Check if native WOLFSSL_SESSION is resumable, calling native
     * wolfSSL_SESSION_is_resumable().
     *
     * This method is static and does not check active state since this
     * takes a native pointer and has no interaction with the rest of this
     * object.
     *
     * @param session pointer to native WOLFSSL_SESSION structure. May be
     *        obtained from getSession().
     *
     * @return 1 if session is resumable, otherwise 0. Returns
     * WolfSSL.NOT_COMPILED_IN if native wolfSSL does not have
     * wolfSSL_SESSION_is_resumable() compiled in.
     */
    public static int sessionIsResumable(long session) {

        int ret;

        WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, () -> "entered sessionIsResumable()");

        if (session == 0) {
            return 0;
        }

        ret = wolfsslSessionIsResumable(session);

        WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, () -> "session resumable: " + ret);

        return ret;
    }

    /**
     * Deep copy the contents of the WOLFSSL_SESSION, calling native
     * wolfSSL_SESSION_dup().
     *
     * This session will create a new WOLFSSL_SESSION and deep copy it
     * from the WOLFSSL_SESSION pointer provided. Note that if a non-zero
     * value is returned the application is responsible for freeing this
     * WOLFSSL_SESSION memory when finished by calling freeSession().
     *
     * @param session pointer to native WOLFSSL_SESSION structure. May have
     *        been obtained from getSession().
     *
     * @return long representing a native pointer to a new WOLFSSL_SESSION
     *         structure, or zero on error (equivalent to a NULL pointer).
     */
    public static long duplicateSession(long session) {

        final long sessPtr;

        WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO,
            () -> "entered duplicateSession(ptr: " + session + ")");

        if (session == 0) {
            WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO,
                () -> "session pointer is null, not duplicating");
            return 0;
        }

        if (wolfsslSessionIsSetup(session) == 1) {
            WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO,
                () -> "session ptr prior to dup (" + session + ") is setup");
        }
        else {
            WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO,
                () -> "session ptr prior to dup is NOT set up");
        }

        sessPtr = wolfsslSessionDup(session);

        WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, () -> "duplicated session ptr: " + sessPtr);

        if ((sessPtr != 0) && (wolfsslSessionIsSetup(sessPtr) == 1)) {
            WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO,
                () -> "session pointer after dup (" + sessPtr + ") is setup");
        }
        else {
            WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO,
                () -> "session pointer after dup is NOT set up");
        }

        return sessPtr;
    }

    /**
     * Get cipher suite name from WOLFSSL_SESSION, calling native
     * wolfSSL_SESSION_CIPHER_get_name().
     *
     * This method is static and does not check active state since this
     * takes a native pointer and has no interaction with the rest of this
     * object.
     *
     * @param session pointer to native WOLFSSL_SESSION structure. May have
     *        been obtained from getSession().
     * @return String representation of the cipher suite used in native
     *         WOLFSSL_SESSION structure, or NULL if not able to find the
     *         session.
     */
    public static String sessionGetCipherName(long session) {

        WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO,
            () -> "entered sessionGetCipherName(ptr: " + session + ")");

        if (session == 0) {
            return null;
        }

        return wolfsslSessionCipherGetName(session);
    }

    /**
     * Free the native WOLFSSL_SESSION structure pointed to be session.
     *
     * @param session native WOLFSSL_SESSION pointer to free
     */
    public static synchronized void freeSession(long session) {
        /* No need to call confirmObjectIsActive() because the
         * WOLFSSL_SESSION pointer being passed in here is not associated
         * with this WOLFSSL object or WolfSSLSession. */

        WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO,
            () -> "entered freeSession(ptr: " + session + ")");

        if (session != 0) {
            freeNativeSession(session);
        }

        WolfSSLDebug.log(WolfSSLSession.class, WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, () -> "session freed (ptr: " + session + ")");

    }

    /**
     * Returns the session ID.
     *
     * @throws IllegalStateException WolfSSLContext has been freed
     * @return      the session ID, or a empty array if unable to get valid
     *              session ID
     * @see         #setSession(long)
     */
    public byte[] getSessionID() throws IllegalStateException {

        byte[] sessId = null;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered getSessionID()");

            long sess = getSession(this.sslPtr);
            if (sess != 0) {
                /* returns new byte[] independent of sess ptr */
                 sessId = getSessionID(sess);
            } else {
                sessId = new byte[0];
            }

            WolfSSLDebug.logHex(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "session ID", sessId,
                sessId.length);

            return sessId;
        }
    }

    /**
     * Check if there is a session ticket associated with this
     * WolfSSLSession (WOLFSSL_SESSION).
     *
     * @return true if internal session has session ticket, otherwise false
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public boolean hasSessionTicket() throws IllegalStateException {

        final boolean hasTicket;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered hasSessionTicket()");

            long sess = getSession(this.sslPtr);
            if ((sess != 0) && (hasTicket(sess) == WolfSSL.SSL_SUCCESS)) {
                hasTicket = true;
            } else {
                hasTicket = false;
            }

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "session has ticket: " + hasTicket);

            return hasTicket;
        }
    }

    /**
     * Gets the cache size is set at compile time.
     * This function returns the current cache size which has been set at compile
     * time.
     *
     * @return size of compile time cache.
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public long getCacheSize() throws IllegalStateException {

        long ret;

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, () -> "entered getCacheSize()");

        ret = this.getAssociatedContextPtr().getCacheSize();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, () -> "cache size: " + ret);

        return ret;
    }

    /**
     * Associate client session with serverID, find existing or store
     * for saving. If newSess flag is on, don't reuse existing session.
     *
     * @param id server ID to associate client session with
     * @param newSess if 1, don't reuse existing session, otherwise 0
     *
     * @return WolfSSL.SSL_SUCCESS on success, WolfSSL.SSL_FAILURE on
     *         error. Or WolfSSL.NOT_COMPILED_IN if native API is not
     *         compiled into library.
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public int setServerID(byte[] id, int newSess)
        throws IllegalStateException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setServerID(byte[], newSess: " + newSess + ")");

            ret = setServerID(this.sslPtr, id, id.length, newSess);

            if (ret == WolfSSL.SSL_SUCCESS) {
                if (id != null) {
                    WolfSSLDebug.logHex(getClass(), WolfSSLDebug.Component.JNI,
                        WolfSSLDebug.INFO, this.sslPtr,
                        () -> "set server ID", id, id.length);
                }
                else {
                    WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                        WolfSSLDebug.INFO, this.sslPtr,
                        () -> "server ID byte[] null, not set");
                }
            }
            else {
                WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                    WolfSSLDebug.INFO, this.sslPtr,
                    () -> "failed to set server ID, ret: " + ret);
            }

            return ret;
        }
    }

    /**
     * Sets the timeout in seconds in the given WOLFSSL_SESSION.
     *
     * @param t time in seconds to set
     * @throws IllegalStateException WolfSSLSession has been freed
     * @return WolfSSL.SSL_SUCCESS on success, WolfSSL.JNI_SESSION_UNAVAILABLE
     *         if underlying session is unavailable, or negative values
     *         on failure.
     * @see         #setSession(long)
     * @see         #getSession(long)
     */
    public int setSessTimeout(long t) throws IllegalStateException {

        final int ret;
        long session;

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO,
            () -> "entered setSessTimeout(timeout (sec): " + t + ")");

        session = this.getSession();
        if (session == 0) {
            /* session may be null if session cache disabled, wolfSSL
             * doesn't have session ID available, mutex function fails, etc */
            ret = WolfSSL.JNI_SESSION_UNAVAILABLE;
        } else {
            ret = setSessTimeout(session, t);
        }

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, () -> "set session timeout, ret: " + ret);

        return ret;
    }

    /**
     * Gets the timeout in seconds in the given WOLFSSL_SESSION.
     *
     * @throws IllegalStateException WolfSSLSession has been freed
     * @return current timeout in seconds
     * @see         #setSession(long)
     * @see         #getSession(long)
     */
    public long getSessTimeout() throws IllegalStateException {

        long ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getSessTimeout()");

            ret = getSessTimeout(this.getSession(this.sslPtr));

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "session timeout (sec): " + ret);

            return ret;
        }
    }

    /**
     * Sets the timeout in seconds in the given SSL object.
     *
     * @param t time in seconds to set
     * @throws IllegalStateException WolfSSLSession has been freed
     * @return WOLFSSL_SUCCESS on success, negative values on failure.
     * @see         #setSession(long)
     * @see         #getSession(long)
     */
    public int setTimeout(long t) throws IllegalStateException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setTimeout(timeout: " + t + ")");

            ret = setTimeout(this.sslPtr, t);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "set timeout, ret: " + ret);

            return ret;
        }
    }

    /**
     * Gets the timeout in seconds in the given SSL object.
     *
     * @throws IllegalStateException WolfSSLContext has been freed
     * @return current timeout in seconds
     * @see         #setSession(long)
     * @see         #getSession(long)
     */
    public long getTimeout() throws IllegalStateException {

        long ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered getTimeout()");

            ret = getTimeout(this.sslPtr);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "timeout: " + ret);

            return ret;
        }
    }

    /**
     * Sets the cipher suite list for a given SSL session.
     * The ciphers in the list should be sorted in order of preference from
     * highest to lowest. Each call to <code>setCipherList()</code> resets
     * the cipher suite list for the specific SSL session to the provided list
     * each time time the method is called.
     * <p>
     * The cipher suite list, <b>list</b>, is a null-terminated text String,
     * and colon-delimited list. For example, one possible list may be:
     * <p>
     * <code>"DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:AES256-SHA256"</code>
     * <p>
     * Valid cipher values are the full name values from the cipher_names[]
     * array in the native wolfSSL src/internal.c:
     *
     * @param list  null-terminated text string and colon-delimited list
     *              of cipher suites to use with the specified SSL
     *              session.
     * @return      <code>SSL_SUCCESS</code> upon success. <code>
     *              SSL_FAILURE</code> upon failure.
     * @throws IllegalStateException WolfSSLSession has been freed
     * @see    WolfSSLContext#setCipherList(String)
     */
    public int setCipherList(String list) throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setCipherList(" + list + ")");

            return setCipherList(this.sslPtr, list);
        }
    }

    /**
     * Sets the supported signature algorithms for the given SSL session.
     * By default, without calling this method, native wolfSSL will add the
     * signature-hash algorithms automatically to the ClientHello message
     * based on which algorithms and modes are compiled into the native library.
     *
     * Calling this function will override the defualt list with the specified
     * list.
     *
     * The signature algorithm list, <b>list</b>, is a null-terminated text
     * String, and colon delimited list. Each list item is a combination of
     * public key algorithm and MAC algorithm, concatenated with a plus
     * sign (+).
     *
     * Possible public key algorithms include the following, but are dependent
     * on which algorithms are compiled into the native library:
     *
     *    "RSA"     - available if NO_RSA is not defined
     *    "RSA-PSS" - available if !NO_RSA and WC_RSA_PSS
     *    "PSS"     - available if !NO_RSA and WC_RSA_PSS
     *    "ECDSA"   - available if HAVE_ECC
     *    "ED25519" - available if HAVE_ED25519
     *    "ED448"   - available if HAVE_ED448
     *    "DSA"     - available if !NO_DSA
     *
     * Possible MAC/hash algorithms include the following, but are also
     * dependent on which algorithms are compiled into the native library:
     *
     *    "SHA1"    - available if !NO_SHA and (!NO_OLD_TLS or WOLFSSL_ALLOW_TLS_SHA1)
     *    "SHA224"  - available if WOLFSSL_SHA224
     *    "SHA256"  - available if WOLFSSL_SHA256
     *    "SHA384"  - available if WOLFSSL_SHA384
     *    "SHA512"  - available if WOLFSSL_SHA512
     *
     * When put together as list items these would look similar to:
     *
     *    "RSA+SHA256:ECDSA+SHA256"
     *
     * @param list  null-terminated text string and colon-delimited list
     *              of signature algorithms to use with the specified SSL
     *              session.
     * @return      <code>SSL_SUCCESS</code> upon success. <code>
     *              SSL_FAILURE</code> upon failure.
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public int setSignatureAlgorithms(String list)
        throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setSignatureAlgorithms(" + list + ")");

            return set1SigAlgsList(this.sslPtr, list);
        }
    }

    /**
     * Sets the TLS Supported Curves to be used in the ClientHello
     * extension if enabled in native wolfSSL.
     *
     * @param curveNames String array of ECC curve names to set into the
     *        Supported Curve extension. String values should match names from
     *        the following list:
     *            "sect163k1", "sect163r1", "sect163r2", "sect193r1",
     *            "sect193r2", "sect233k1", "sect233r1", "sect239k1",
     *            "sect283k1", "sect283r1", "sect409k1", "sect409r1",
     *            "sect571k1", "sect571r1", "secp160k1", "secp160r1",
     *            "secp160r2", "secp192k1", "secp192r1", "secp224k1",
     *            "secp224r1", "secp256k1", "secp256r1", "secp384r1",
     *            "secp521r1", "brainpoolP256r1", "brainpoolP384r1",
     *            "brainpoolP512r1", "x25519", "x448", "sm2P256v1",
     *            "ffdhe2048", "ffdhe3072", "ffdhe4096", "ffdhe6144",
     *            "ffdhe8192"
     *
     * @return <code>WolfSSL.SSL_SUCCESS</code> on success, otherwise
     *         negative on error.
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public int useSupportedCurves(String[] curveNames)
        throws IllegalStateException  {

        int ret = 0;
        int curveEnum = 0;

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered useSupportedCurves(" +
                Arrays.asList(curveNames) + ")");
        }

        for (String curve : curveNames) {
            curveEnum = WolfSSL.getNamedGroupFromString(curve);
            synchronized (sslLock) {
                ret = useSupportedCurve(this.sslPtr, curveEnum);
            }
        }

        return ret;
    }

    /**
     * Disable TLS Extended Master Secret usage.
     *
     * @return <code>WolfSSL.SSL_SUCCESS</code> on success, otherwise
     *         negative on error.
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public int disableExtendedMasterSecret()
        throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            return disableExtendedMasterSecret(this.sslPtr);
        }
    }

    /* ---------------- Nonblocking DTLS helper functions  -------------- */

    /**
     * Returns the current timeout value in seconds for the SSL session.
     * When using non-blocking sockets, something in the user code needs
     * to decide when to check for available recv data and how long it has
     * been waiting. The value returned by this method indicates how long the
     * application should wait.
     *
     * @return the current DTLS timeout value in seconds,
     *         <code>NOT_COMPILED_IN</code> if wolfSSL was not built
     *         with DTLS support.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #dtls()
     * @see    #dtlsGetPeer()
     * @see    #dtlsGotTimeout()
     * @see    #dtlsSetPeer(InetSocketAddress)
     */
    public int dtlsGetCurrentTimeout()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered dtlsGetCurrentTimeout()");

            return dtlsGetCurrentTimeout(this.sslPtr);
        }
    }

    /**
     * Performs the actions needed to retry the last retransmit, including
     * adjusting the timeout value.
     * When using non-blocking sockets with DTLS, this method should be
     * called on the SSL session when the controlling code thinks the
     * transmission has timed out.
     *
     * @return <code>SSL_SUCCESS</code> upon success. <code>
     *         SSL_FATAL_ERROR</code> if there have been too many
     *         retransmissions/timeouts without getting a response from
     *         the peer. <code>NOT_COMPILED_IN</code> if wolfSSL was
     *         not compiled with DTLS support.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #dtlsGetCurrentTimeout()
     * @see    #dtlsGetPeer()
     * @see    #dtlsSetPeer(InetSocketAddress)
     * @see    #dtls()
     */
    public int dtlsGotTimeout() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered dtlsGotTimeout()");

            return dtlsGotTimeout(this.sslPtr);
        }
    }

    /**
     * When using non-blocking sockets with DTLS, this function retransmits
     * the last handshake flight ignoring the expected timeout value and
     * retransmit count.
     *
     * It is useful for applications that are using DTLS and need to manage
     * even the timeout and retry count.
     *
     * @return <code>SSL_SUCCESS</code> upon success. <code>
     *         SSL_FATAL_ERROR</code> if there have been too many
     *         retransmissions/timeouts without getting a response from
     *         the peer. <code>NOT_COMPILED_IN</code> if wolfSSL was
     *         not compiled with DTLS support.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #dtlsGetCurrentTimeout()
     * @see    #dtlsGetPeer()
     * @see    #dtlsSetPeer(InetSocketAddress)
     * @see    #dtlsGotTimeout()
     * @see    #dtls()
     */
    public int dtlsRetransmit() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            return dtlsRetransmit(this.sslPtr);
        }
    }

    /**
     * Used to determine if the SSL session has been configured to use DTLS.
     *
     * @return <code>1</code> if the SSL has been configured to use DTLS,
     *         otherwise, <code>0</code>.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #dtlsGetCurrentTimeout()
     * @see    #dtlsGetPeer()
     * @see    #dtlsGotTimeout()
     * @see    #dtlsSetPeer(InetSocketAddress)
     */
    public int dtls() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered dtls()");

            return dtls(this.sslPtr);
        }
    }

    /**
     * Sets the DTLS peer.
     *
     * @param peer  DTLS peer's InetSocketAddress
     * @return      <code>SSL_SUCCESS</code> upon success, <code>
     *              SSL_FAILURE</code> upon failure, <code>
     *              SSL_NOT_IMPLEMENTED</code> if wolfSSL was not compiled
     *              with DTLS support.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #dtlsGetCurrentTimeout()
     * @see    #dtlsGetPeer()
     * @see    #dtlsGotTimeout()
     * @see    #dtls()
     */
    public int dtlsSetPeer(InetSocketAddress peer)
        throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered dtlsSetPeer(" + peer + ")");

            return dtlsSetPeer(this.sslPtr, peer);
        }
    }

    /**
     * Send a cookie with the HelloRetryRequest to avoid storing state.
     *
     * @param secret Secret to use when generating integrity check for cookie.
     * A value of null indicates to generate a new random secret.
     *
     * @return <code>WolfSSL.SSL_SUCCESS</code> on success,
     *         <code>WolfSSL.BAD_FUNC_ARG</code> when not using (D)TLS 1.3,
     *         <code>WolfSSL.SIDE_ERROR</code> when called on a client.
     *
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public int sendHrrCookie(byte[] secret)
        throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            return sendHrrCookie(this.sslPtr, secret);
        }
    }

    /**
     * Get the number of DTLS packets that have been dropped due
     * to verify MAC decrypt errors.
     *
     * Native wolfSSL must be compiled with WOLFSSL_DTLS_DROP_STATS
     * defined for this functionality to be available, otherwise this
     * method will return 0.
     *
     * @return number of dropped packets
     *
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public long getDtlsMacDropCount() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            return getDtlsMacDropCount(this.sslPtr);
        }
    }

    /**
     * Get the number of DTLS packets that have been dropped due to
     * receiving a replayed / duplicated packet.
     *
     * Native wolfSSL must be compiled with WOLFSSL_DTLS_DROP_STATS
     * defined for this functionality to be available, otherwise this
     * method will return 0.
     *
     * @return number of dropped packets
     *
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public long getDtlsReplayDropCount() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            return getDtlsReplayDropCount(this.sslPtr);
        }
    }

    /**
     * Gets the InetSocketAddress of the DTLS peer.
     *
     * @return      DTLS peer's InetSocketAddress upon success, <code>
     *              null</code> upon failure.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #dtlsGetCurrentTimeout()
     * @see    #dtlsGotTimeout()
     * @see    #dtlsSetPeer(InetSocketAddress)
     * @see    #dtls()
     */
    public InetSocketAddress dtlsGetPeer() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered dtlsGetPeer()");

            return dtlsGetPeer(this.sslPtr);
        }
    }

    /**
     * Set the DTLS MTU to use.
     *
     * Native wolfSSL must be compiled with "--enable-dtls-mtu" in addition
     * to "--enable-dtls".
     *
     * @param mtu DTLS MTU value, must be lower than MAX_RECORD_SIZE (16k)
     *
     * @return <code>SSL_SUCCESS</code> on success,
     *         <code>SSL_FAILURE</code> on error, or
     *         <code>NOT_COMPILED_IN</code> if native support has not
     *         been compiled into native wolfSSL.
     *
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public int dtlsSetMTU(int mtu) throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            return setMTU(this.sslPtr, mtu);
        }
    }

    /**
     * Return the current handshake state of the native WOLFSSL object as
     * a String.
     *
     * @return String representing current state of handshake or empty
     *         String if not able to retrieve one.
     *
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public String getStateStringLong() throws IllegalStateException {

        String state = null;

        confirmObjectIsActive();

        synchronized (sslLock) {
            state = stateStringLong(this.sslPtr);
            if (state == null) {
                /* Return empty string instead of null, may prevent some
                 * NullPoinerExceptions from callers */
                state = "";
            }
        }

        return state;
    }

    /**
     * Return max record layer size plaintext input size
     *
     * @return max record layer size plaintext input size in bytes, or
     *         negative value on error.
     *
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public int getMaxOutputSize() throws IllegalStateException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getOutputSize()");

            ret = getMaxOutputSize(this.sslPtr);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "max output size: " + ret);
        }

        return ret;
    }

    /**
     * Determine if a reused session was negotiated during the SSL
     * handshake.
     * If session resumption is being used, and the client has proposed to
     * reuse a given session, this method will notify the application if the
     * requested session has been negotiated after the handshake has completed.
     *
     * @return <b>1</b> if the session was reused, <b>0</b> if a new
     *         session needed to be negotiated.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #setSession(long)
     * @see    #getSession()
     */
    public int sessionReused()
        throws IllegalStateException, WolfSSLJNIException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered sessionReused()");

            ret = sessionReused(this.sslPtr);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "session reused: " + ret);
        }

        return ret;
    }

    /**
     * Gets the native (long) WOLFSSL_X509 pointer to the peer's certificate.
     * This can be used to retrieve further information about the peer's
     * certificate (issuer, subject, alt name, etc.)
     *
     * wolfSSL versions 5.3.0 or later return a newly-allocated
     * WOLFSSL_X509 structure poiner from the native
     * wolfSSL_get_peer_certificate() API called by this wrapper. If using
     * wolfSSL greater than or equal to 5.3.0, the pointer (long) returned
     * from this method must be freed by the caller. Versions of wolfSSL
     * less than 5.3.0 should not free the pointer returned since it points
     * to internal memory that is freed by native wolfSSL.
     *
     * Pointer should be freed by calling:
     *     WolfSSLCertificate.freeX509(long x509);
     *
     * @return (long) WOLFSSL_X509 pointer to the peer's certificate.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLSession#getPeerX509Issuer(long)
     * @see    WolfSSLSession#getPeerX509Subject(long)
     * @see    WolfSSLSession#getVersion()
     * @see    WolfSSLSession#getCurrentCipher()
     */
    public long getPeerCertificate()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getPeerCertificate()");

            return getPeerCertificate(this.sslPtr);
        }
    }

    /**
     * Gets the peer X509 certificate's issuer information.
     *
     * @param x509  pointer (long) to native WOLFSSL_X509 structure, obtained
     *              from getPeerCertificate().
     * @return      String representation of the peer's issuer information
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLSession#getPeerCertificate()
     * @see    WolfSSLSession#getPeerX509Subject(long)
     * @see    WolfSSLSession#getVersion()
     * @see    WolfSSLSession#getCurrentCipher()
     */
    public String getPeerX509Issuer(long x509)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getPeerX509Issuer()");

            return getPeerX509Issuer(this.sslPtr, x509);
        }
    }

    /**
     * Gets the peer X509 certificate's subject information.
     *
     * @param x509  pointer (long) to native WOLFSSL_X509 structure, obtained
     *              from getPeerCertificate().
     * @return      String representation of the peer's subject information
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLSession#getPeerCertificate()
     * @see    WolfSSLSession#getPeerX509Issuer(long)
     * @see    WolfSSLSession#getVersion()
     * @see    WolfSSLSession#getCurrentCipher()
     */
    public String getPeerX509Subject(long x509)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getPeerX509Subject()");

            return getPeerX509Subject(this.sslPtr, x509);
        }
    }

    /**
     * Gets the peer X509 certificate's altname information.
     * This method may be repeatedly called to get the next altname, if any,
     * from the peer cert. If no more altnames are available, <b>null</b>
     * will be returned.
     *
     * @param x509  pointer (long) to native WOLFSSL_X509 structure, obtained
     *              from getPeerCertificate().
     * @return      String representation of the peer's subject information
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLSession#getPeerCertificate()
     * @see    WolfSSLSession#getPeerX509Issuer(long)
     * @see    WolfSSLSession#getPeerX509Subject(long)
     * @see    WolfSSLSession#getVersion()
     * @see    WolfSSLSession#getCurrentCipher()
     */
    public String getPeerX509AltName(long x509)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getPeerX509AltName()");

            return getPeerX509AltName(this.sslPtr, x509);
        }
    }

    /**
     * Returns the SSL/TLS version being used with this session object in
     * String format.
     * Examples include "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2", "DTLS",
     * "DTLS 1.2", and "DTLS 1.3.
     *
     * @return      SSL/TLS protocol version being used in String format,
     *              or "unknown".
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     */
    public String getVersion()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered getVersion()");

            return getVersion(this.sslPtr);
        }
    }

    /**
     * Returns a pointer to the native WOLFSSL_CIPHER object being used
     * in with the SSL session.
     * This pointer can be used with the <code>getCipherName()</code> function
     * to get the name of the current cipher suite being used.
     *
     * @return      pointer (long) to the native WOLFSSL_CIPHER object
     *              currently used with the SSL session.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLSession#cipherGetName()
     */
    public long getCurrentCipher()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getCurrentCipher()");

            return getCurrentCipher(this.sslPtr);
        }
    }

    /**
     * Adds a domain check to the list of checks performed during the peer
     * verification.
     * wolfSSL by default check the peer certificate for a valid date range
     * and a verified signature. Calling this function before <code>connect()
     * </code> or <code>accept()</code> will add a domain name check to the
     * list of checks to perform.
     *
     * @param dn    domain name to check against the peer certificate
     *              when received.
     * @return      <code>SSL_SUCCESS</code> on success, <code>SSL_FAILURE
     *              </code> if a memory error was encountered.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     */
    public int checkDomainName(String dn)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered checkDomainName(" + dn + ")");

            return checkDomainName(this.sslPtr, dn);
        }
    }

    /**
     * Sets up the group parameters to be used if the server negotiates
     * a cipher suite that uses DHE.
     *
     * @param p     Diffie-Hellman prime number parameter
     * @param pSz   size of <code>p</code>
     * @param g     Diffie-Hellman "generator" parameter
     * @param gSz   size of <code>g</code>
     * @return      <code>SSL_SUCCESS</code> on success. <code>MEMORY_E
     *              </code> if a memory error was encountered. <code>
     *              SIDE_ERROR</code> if this function is called on an
     *              SSL client instead of an SSL server.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #accept()
     */
    public int setTmpDH(byte[] p, int pSz, byte[] g, int gSz)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setTmpDH(pSz: " + pSz + ", gSz: " + gSz + ")");

            return setTmpDH(this.sslPtr, p, pSz, g, gSz);
        }
    }

    /**
     * Sets up the group parameters from the specified file to be used if the
     * server negotiates a cipher suite that uses DHE.
     *
     * @param fname     path to Diffie-Hellman parameter file
     * @param format    format of DH parameter file, either
     *                  <code>SSL_FILETYPE_ASN1</code> or <code>
     *                  SSL_FILETYPE_PEM</code>.
     * @return          <code>SSL_SUCCESS</code> on success. <code>MEMORY_E
     *                  </code> if a memory error was encountered. <code>
     *                  SIDE_ERROR</code> if this function is called on an
     *                  SSL client instead of an SSL server, <code>
     *                  SSL_BAD_FILETYPE</code> if the specified format is
     *                  incorrect, <code>SSL_BAD_FILE</code> if there is a
     *                  problem with the input file.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #setTmpDH(byte[], int, byte[], int)
     */
    public int setTmpDHFile(String fname, int format)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getTmpDHFile(" + fname + ", format:" +
                format + ")");

            return setTmpDHFile(this.sslPtr, fname, format);
        }
    }

    /**
     * Loads a certificate buffer into the SSL object.
     * This method behaves like the non-buffered version, only differing
     * in its ability to be called with a buffer as input instead of a file.
     *
     * @param in        input buffer containing the certificate to load
     * @param sz        size of the input buffer, <b>in</b>
     * @param format    format of the certificate buffer being loaded - either
     *                  <b>SSL_FILETYPE_PEM</b> or <b>SSL_FILETYPE_ASN1</b>
     * @return          <b><code>SSL_SUCCESS</code></b> upon success,
     *                  <b><code>SSL_BAD_FILETYPE</code></b> if the file is
     *                  in the wrong format, <b><code>SSL_BAD_FILE</code></b>
     *                  if the file doesn't exist, can't be read, or is
     *                  corrupted. <b><code>MEMORY_E</code></b> if an out of
     *                  memory condition occurs, <b><code>ASN_INPUT_E</code></b>
     *                  if Base16 decoding fails on the file, and <b><code>
     *                  BAD_FUNC_ARG</code></b> if invalid input parameters
     *                  are given.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#loadVerifyBuffer(byte[], long, int)
     * @see    WolfSSLContext#useCertificateBuffer(byte[], long, int)
     * @see    WolfSSLContext#usePrivateKeyBuffer(byte[], long, int)
     * @see    WolfSSLContext#useCertificateChainBuffer(byte[], long)
     * @see    #usePrivateKeyBuffer(byte[], long, int)
     * @see    #useCertificateChainBuffer(byte[], long)
     */
    public int useCertificateBuffer(byte[] in, long sz, int format)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered useCertificateBuffer(sz: " + sz + ", format: " +
                format + ")");

            return useCertificateBuffer(this.sslPtr, in, sz, format);
        }
    }

    /**
     * Loads a private key buffer into the SSL object.
     * This method behaves like the non-buffered version, only differing
     * in its ability to be called with a buffer as input rather than a file.
     *
     * @param in        the input buffer containing the private key to be
     *                  loaded
     * @param sz        the size of the input buffer, <b>in</b>
     * @param format    format of the certificate buffer being loaded - either
     *                  <b>SSL_FILETYPE_PEM</b> or <b>SSL_FILETYPE_ASN1</b>
     * @return          <b><code>SSL_SUCCESS</code></b> upon success,
     *                  <b><code>SSL_BAD_FILETYPE</code></b> if the file is
     *                  in the wrong format, <b><code>SSL_BAD_FILE</code></b>
     *                  if the file doesn't exist, can't be read, or is
     *                  corrupted. <b><code>MEMORY_E</code></b> if an out of
     *                  memory condition occurs, <b><code>ASN_INPUT_E</code></b>
     *                  if Base16 decoding fails on the file,
     *                  <b><code>NO_PASSWORD</code></b> if the key file is
     *                  encrypted but no password is provided, and <b><code>
     *                  BAD_FUNC_ARG</code></b> if invalid input parameters
     *                  are given.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#loadVerifyBuffer(byte[], long, int)
     * @see    WolfSSLContext#useCertificateBuffer(byte[], long, int)
     * @see    WolfSSLContext#usePrivateKeyBuffer(byte[], long, int)
     * @see    WolfSSLContext#useCertificateChainBuffer(byte[], long)
     * @see    #useCertificateBuffer(byte[], long, int)
     * @see    #useCertificateChainBuffer(byte[], long)
     */
    public int usePrivateKeyBuffer(byte[] in, long sz, int format)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered usePrivateKeyBuffer(sz: " + sz + ", format: " +
                format + ")");

            return usePrivateKeyBuffer(this.sslPtr, in, sz, format);
        }
    }

    /**
     * Loads a certificate chain buffer into the SSL object.
     * This method behaves like the non-buffered version, only differing
     * in its ability to be called with a buffer as input instead of a file.
     * The buffer must be in PEM format and start with the subject's
     * certificate, ending with the root certificate.
     *
     * @param in        the input buffer containing the PEM-formatted
     *                  certificate chain to be loaded.
     * @param sz        the size of the input buffer, <b>in</b>
     * @return          <b><code>SSL_SUCCESS</code></b> upon success,
     *                  <b><code>SSL_BAD_FILETYPE</code></b> if the file is
     *                  in the wrong format, <b><code>SSL_BAD_FILE</code></b>
     *                  if the file doesn't exist, can't be read, or is
     *                  corrupted. <b><code>MEMORY_E</code></b> if an out of
     *                  memory condition occurs, <b><code>ASN_INPUT_E</code></b>
     *                  if Base16 decoding fails on the file,
     *                  <b><code>BUFFER_E</code></b> if a chain buffer is
     *                  bigger than the receiving buffer, and <b><code>
     *                  BAD_FUNC_ARG</code></b> if invalid input parameters
     *                  are given.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#loadVerifyBuffer(byte[], long, int)
     * @see    WolfSSLContext#useCertificateBuffer(byte[], long, int)
     * @see    WolfSSLContext#usePrivateKeyBuffer(byte[], long, int)
     * @see    WolfSSLContext#useCertificateChainBuffer(byte[], long)
     * @see    #useCertificateBuffer(byte[], long, int)
     * @see    #usePrivateKeyBuffer(byte[], long, int)
     */
    public int useCertificateChainBuffer(byte[] in, long sz)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered useCertificateChainBuffer(sz: " + sz + ")");

            return useCertificateChainBuffer(this.sslPtr, in, sz);
        }
    }

    /**
     * Loads a certificate chain buffer into the SSL object in specific format.
     * This method behaves like the non-buffered version, only differing
     * in its ability to be called with a buffer as input instead of a file.
     * This function is similar to useCertificateChainBuffer(), but allows
     * the input format to be specified. The format must be either DER or PEM,
     * and start with the subject's certificate, ending with the root
     * certificate.
     *
     * @param in        the input buffer containing the PEM or DER formatted
     *                  certificate chain to be loaded.
     * @param sz        the size of the input buffer, <b>in</b>
     * @param format    format of the certificate buffer being loaded - either
     *                  <b>SSL_FILETYPE_PEM</b> or <b>SSL_FILETYPE_ASN1</b>
     * @return          <b><code>SSL_SUCCESS</code></b> upon success,
     *                  <b><code>SSL_FAILURE</code></b> upon general failure,
     *                  <b><code>SSL_BAD_FILETYPE</code></b> if the file is
     *                  in the wrong format, <b><code>SSL_BAD_FILE</code></b>
     *                  if the file doesn't exist, can't be read, or is
     *                  corrupted. <b><code>MEMORY_E</code></b> if an out of
     *                  memory condition occurs, <b><code>ASN_INPUT_E</code></b>
     *                  if Base16 decoding fails on the file,
     *                  <b><code>BUFFER_E</code></b> if a chain buffer is
     *                  bigger than the receiving buffer, and <b><code>
     *                  BAD_FUNC_ARG</code></b> if invalid input arguments
     *                  are provided.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #useCertificateBuffer(byte[], long, int)
     * @see    #usePrivateKeyBuffer(byte[], long, int)
     * @see    WolfSSLSession#useCertificateBuffer(byte[], long, int)
     * @see    WolfSSLSession#usePrivateKeyBuffer(byte[], long, int)
     * @see    WolfSSLSession#useCertificateChainBuffer(byte[], long)
     */
    public int useCertificateChainBufferFormat(byte[] in, long sz, int format)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered useCertificateChainBufferFormat(sz: " + sz +
                ", format: " + format + ")");

            return useCertificateChainBufferFormat(this.sslPtr, in, sz, format);
        }
    }


    /**
     * Turns on grouping of the handshake messages where possible using the
     * SSL session.
     *
     * @return      <code>SSL_SUCCESS</code> upon success. <code>
     *              BAD_FUNC_ARG</code> if the input session is null.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setGroupMessages()
     */
    public int setGroupMessages()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setGroupMessages()");

            return setGroupMessages(this.sslPtr);
        }
    }

    /**
     * Registers a context for the SSL session's receive callback method.
     * By default, wolfSSL sets the file descriptor passed to setFd() as
     * the context when wolfSSL is using the system's TCP library. If you've
     * registered your own receive callback you may want to set a specific
     * context for the session.
     * <p>
     * For example, if you're using memory buffers, the context may be a
     * pointer to an object describing where and how to access the memory
     * buffers.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              receive callback method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #setIOWriteCtx(Object)
     * @see    WolfSSLContext#setIORecv(WolfSSLIORecvCallback)
     * @see    WolfSSLContext#setIOSend(WolfSSLIOSendCallback)
     */
    public void setIOReadCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered setIOReadCtx()");

            ioReadCtx = ctx;
        }
    }

    /**
     * Return the SSL session's receive callback context, if set.
     *
     * @return Object that was set with setIOReadCtx().
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public Object getIOReadCtx()
        throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            return this.ioReadCtx;
        }
    }

    /**
     * Registers a context for the SSL session's send callback method.
     * By default, wolfSSL sets the file descriptor passed to setFd() as
     * the context when wolfSSL is using the system's TCP library. If
     * you've registered your own send callback, you may want to set a
     * specific context for the session.
     * <p>
     * For example, if you're using memory buffers the context may be a
     * pointer to an object describing where and how to access the memory
     * buffers.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              send callback method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #setIOReadCtx(Object)
     * @see    WolfSSLContext#setIOSend(WolfSSLIOSendCallback)
     * @see    WolfSSLContext#setIORecv(WolfSSLIORecvCallback)
     */
    public void setIOWriteCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setIOWriteCtx()");

            ioWriteCtx = ctx;
        }
    }

    /**
     * Return the SSL session's write callback context, if set.
     *
     * @return Object that was set with setIOWriteCtx().
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public Object getIOWriteCtx()
        throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            return this.ioWriteCtx;
        }
    }

    /**
     * Registers a context for the SSL session's DTLS cookie generation
     * callback method.
     * By default, wolfSSL sets the file descriptor passed to setFd() as
     * the context when wolfSSL is using the system's TCP library. If
     * the application has registered its own DTLS gen cookie callback, it may
     * need to set a specific context for the cookie generation method.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              cookie generation method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setGenCookie(WolfSSLGenCookieCallback)
     */
    public void setGenCookieCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setGenCookieCtx()");

            genCookieCtx = ctx;
        }
    }

    /**
     * Turns on Certificate Revocation List (CRL) checking when
     * verifying certificates.
     * By default, CRL checking is off. <b>options</b> include
     * WOLFSSL_CRL_CHECKALL which performs CRL checking on each certificate
     * in the chain versus the leaf certificate only (which is default).
     *
     * @param options   options to use when enabling CRL
     * @return          <code>SSL_SUCCESS</code> upon success. <code>
     *                  NOT_COMPILED_IN</code> if wolfSSL was not compiled
     *                  with CRL enabled. <code>MEMORY_E</code> if an out
     *                  of memory condition occurs. <code>BAD_FUNC_ARG</code>
     *                  if a pointer is not provided, and <code>
     *                  SSL_FAILURE</code> if the CRL context cannot be
     *                  initialized properly.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #disableCRL()
     * @see    #loadCRL(String, int, int)
     * @see    #setCRLCb(WolfSSLMissingCRLCallback)
     * @see    WolfSSLContext#enableCRL(int)
     * @see    WolfSSLContext#disableCRL()
     * @see    WolfSSLContext#setCRLCb(WolfSSLMissingCRLCallback)
     */
    public int enableCRL(int options)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered enableCRL(" + options + ")");

            return enableCRL(this.sslPtr, options);
        }
    }

    /**
     * Turns off Certificate Revocation List (CRL) checking.
     * By default, CRL checking is off. This function can be used to
     * temporarily or permanently disable CRL checking for a given SSL
     * session object that previously had CRL checking enabled.
     *
     * @return      <code>SSL_SUCCESS</code> on success, <code>
     *              BAD_FUNC_ARG</code> if pointer is not provided.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #enableCRL(int)
     * @see    #loadCRL(String, int, int)
     * @see    #setCRLCb(WolfSSLMissingCRLCallback)
     * @see    WolfSSLContext#enableCRL(int)
     * @see    WolfSSLContext#disableCRL()
     * @see    WolfSSLContext#setCRLCb(WolfSSLMissingCRLCallback)
     */
    public int disableCRL()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

       synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered disableCRL()");

             return disableCRL(this.sslPtr);
        }
    }

    /**
     * Loads CRL files into wolfSSL from the specified path.
     * This method loads a list of CRL files into wolfSSL. The files can be
     * in either PEM or DER format, as specified by the <b>type</b>
     * parameter.
     *
     * @param path      path to directory containing CRL files
     * @param type      type of files in <b>path</b>, either <code>
     *                  SSL_FILETYPE_PEM</code> or <code>SSL_FILETYPE_ASN1
     *                  </code>.
     * @param monitor   OR'd list of flags to indicate if wolfSSL should
     *                  monitor the provided CRL directory for changes.
     *                  Flag values include <code>WOLFSSL_CRL_MONITOR</code>
     *                  to indicate that the directory should be monitored
     *                  and <code>WOLFSSL_CRL_START_MON</code> to start the
     *                  monitor.
     * @return          <b><code>SSL_SUCCESS</code></b> upon success<br>
     *                  <b><code>SSL_FATAL_ERROR</code></b> if enabling the
     *                  internal CertManager fails<br>
     *                  <b><code>BAD_FUNC_ARG</code></b> if the SSL pointer
     *                  is null<br>
     *                  <b><code>BAD_PATH_ERROR</code></b> if there is an
     *                  error opening the provided directory<br>
     *                  <b><code>MEMORY_E</code></b> if a memory error
     *                  occurred<br>
     *                  <b><code>MONITOR_RUNNING_E</code></b> if the CRL
     *                  monitor is already running<br>
     *                  <b><code>THREAD_CREATE_E</code></b> if there was an
     *                  error when creating the CRL monitoring thread.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #enableCRL(int)
     * @see    #disableCRL()
     * @see    #setCRLCb(WolfSSLMissingCRLCallback)
     * @see    WolfSSLContext#enableCRL(int)
     * @see    WolfSSLContext#disableCRL()
     * @see    WolfSSLContext#setCRLCb(WolfSSLMissingCRLCallback)
     */
    public int loadCRL(String path, int type, int monitor)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered loadCRL(" + path + ", type: " + type +
                ", monitor: " + monitor + ")");

            return loadCRL(this.sslPtr, path, type, monitor);
        }
    }

    /**
     * Registers CRL callback to be called when CRL lookup fails.
     *
     * @param cb callback to be registered with SSL session, called
     *           when CRL lookup fails.
     * @return   <b><code>SSL_SUCCESS</code></b> upon success,
     *           <b><code>BAD_FUNC_ARG</code></b> if SSL pointer is null.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #enableCRL(int)
     * @see    #disableCRL()
     * @see    #loadCRL(String, int, int)
     * @see    WolfSSLContext#enableCRL(int)
     * @see    WolfSSLContext#disableCRL()
     * @see    WolfSSLContext#setCRLCb(WolfSSLMissingCRLCallback)
     */
    public int setCRLCb(WolfSSLMissingCRLCallback cb)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setCRLCb(" + cb + ")");

            return setCRLCb(this.sslPtr, cb);
        }
    }

    /**
     * Returns the cipher suite name associated with the WolfSSL session
     * in String format.
     *
     * @return String representation of the cipher suite associated
     *         with the corresponding WolfSSL session.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLSession#getCurrentCipher()
     */
    public String cipherGetName()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered cipherGetName()");

            return cipherGetName(this.sslPtr);
        }
    }

    /**
     * Allows retrieval of the Hmac/Mac secret from the handshake process.
     * The <b>verify</b> parameter specifies whether this is for verification
     * of a peer message.
     *
     * @param verify  specifies whether this if for verification of a peer
     *                message.
     * @return        a valid secret upon success, or <b>null</b> for an
     *                error state. The size of the secret can be obtained
     *                from getHmacSize().
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #getHmacSize()
     */
    public byte[] getMacSecret(int verify)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getMacSecret()");

            return getMacSecret(this.sslPtr, verify);
        }
    }

    /**
     * Allows retrieval of the client write key from the handshake process.
     *
     * @return  a valid key buffer upon success, or <b>null</b> for an error
     *          state. The size of the key can be obtained from getKeySize().
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #getKeySize()
     * @see    #getClientWriteIV()
     */
    public byte[] getClientWriteKey()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getClientWriteKey()");

            return getClientWriteKey(this.sslPtr);
        }
    }

    /**
     * Allows retrieval of the client write IV (initialization vector) from
     * the handshake process.
     *
     * @return  a valid IV buffer upon success, or <b>null</b> for an error
     *          state. The size of the IV can be obtained from
     *          getCipherBlockSize().
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #getCipherBlockSize()
     * @see    #getClientWriteKey()
     */
    public byte[] getClientWriteIV()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getClientWriteIV()");

            return getClientWriteIV(this.sslPtr);
        }
    }

    /**
     * Allows retrieval of the server write key from the handshake process.
     *
     * @return  a valid key buffer upon success, or <b>null</b> for an error
     *          state. The size of the key can be obtained from getKeySize().
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #getKeySize()
     * @see    #getServerWriteIV()
     */
    public byte[] getServerWriteKey()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getServerWriteKey()");

            return getServerWriteKey(this.sslPtr);
        }
    }

    /**
     * Allows retrieval of the server write IV (initialization vector) from
     * the handshake process.
     *
     * @return  a valid IV buffer upon success, or <b>null</b> for an error
     *          state. The size of the IV can be obtained from
     *          getCipherBlockSize().
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #getCipherBlockSize()
     * @see    #getServerWriteKey()
     */
    public byte[] getServerWriteIV()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getServerWriteIV()");

            return getServerWriteIV(this.sslPtr);
        }
    }

    /**
     * Allows retrieval of the key size from the handshake process.
     *
     * @return  the key size in bytes upon success.
     *          <b><code>BAD_FUNC_ARG</code></b> for an error state.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getClientWriteKey()
     * @see    #getServerWriteKey()
     */
    public int getKeySize() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getKeySize()");

            return getKeySize(this.sslPtr);
        }
    }

    /**
     * Allows retrieval of the side of this wolfSSL connection.
     *
     * @return  <b><code>WOLFSSL_SERVER_END</code></b> or
     *          <b><code>WOLFSSL_CLIENT_END</code></b> depending on the side
     *          of the wolfSSL session object.
     *          <b><code>BAD_FUNC_ARG</code></b> for an error state.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getClientWriteKey()
     * @see    #getServerWriteKey()
     */
    public int getSide() throws IllegalStateException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered getSide()");

            ret = getSide(this.sslPtr);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "side: " + ret);
        }

        return ret;
    }

    /**
     * Allows callers to determine if the negotiated protocol version is at
     * least TLS version 1.1 or greater.
     *
     * @return  <b><code>1</code></b> for true, <b><code>0</code></b> for
     *          false.
     *          <b><code>BAD_FUNC_ARG</code></b> for an error state.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getSide()
     */
    public int isTLSv1_1() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered isTLSv1_1()");

            return isTLSv1_1(this.sslPtr);
        }
    }

    /**
     * Allows caller to determine the negotiated bulk cipher algorithm from
     * the handshake.
     *
     * @return  If successful, the call will return one of the following:<br>
     *          WolfSSL.wolfssl_cipher_null<br>
     *          WolfSSL.wolfssl_des<br>
     *          WolfSSL.wolfssl_triple_des<br>
     *          WolfSSL.wolfssl_aes<br>
     *          WolfSSL.wolfssl_aes_gcm<br>
     *          WolfSSL.wolfssl_aes_ccm<br>
     *          WolfSSL.wolfssl_camellia<br>
     *          <b><code>BAD_FUNC_ARG</code></b> for an error state.<br>
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getCipherBlockSize()
     * @see    #getKeySize()
     */
    public int getBulkCipher() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getBulkCipher()");

            return getBulkCipher(this.sslPtr);
        }
    }

    /**
     * Allows callers to determine the negotiated cipher block size from the
     * handshake.
     *
     * @return  the size in bytes of the cipher block size upon success,
     *          <b><code>BAD_FUNC_ARG</code></b> for an error state.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getBulkCipher()
     * @see    #getKeySize()
     */
    public int getCipherBlockSize() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getCipherBlockSize()");

            return getCipherBlockSize(this.sslPtr);
        }
    }

    /**
     * Allows caller to determine the negotiated aead mac size from the
     * handshake.
     * For cipher type <b>WOLFSSL_AEAD_TYPE</b>.
     *
     * @return  the size in bytes of the aead mac size upon success,
     *          <b><code>BAD_FUNC_ARG</code></b> for an error state.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getBulkCipher()
     * @see    #getKeySize()
     */
    public int getAeadMacSize() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getAeadMacSize()");

            return getAeadMacSize(this.sslPtr);
        }
    }

    /**
     * Allows the caller to determine the negotiated (h)mac size from the
     * handshake.
     * For cipher types except <b>WOLFSSL_AEAD_TYPE</b>.
     *
     * @return  the size in bytes of the (h)mac size upon success,
     *          <b><code>BAD_FUNC_ARG</code></b> for an error state.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getBulkCipher()
     * @see    #getHmacType()
     */
    public int getHmacSize() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getHmacSize()");

            return getHmacSize(this.sslPtr);
        }
    }

    /**
     * Allows caller to determine the negotiated (h)mac type from the
     * handshake.
     * For cipher types except <b>WOLFSSL_AEAD_TYPE</b>.
     *
     * @return  If successful, the call will return one of the following:<p>
     *          WolfSSL.MD5<br>
     *          WolfSSL.SHA<br>
     *          WolfSSL.SHA256<br>
     *          WolfSSL.SHA394<br><br>
     *          <b><code>BAD_FUNC_ARG</code></b> or
     *          <b><code>SSL_FATAL_ERROR</code></b> will be returned for an
     *          error state.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getBulkCipher()
     * @see    #getHmacSize()
     *
     */
    public int getHmacType() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr, () -> "entered getHmacType()");

            return getHmacType(this.sslPtr);
        }
    }

    /**
     * Allows caller to determine the negotiated cipher type from the
     * handshake.
     *
     * @return  If successful, the call will return one of the following:<p>
     *          WolfSSL.WOLFSSL_BLOCK_TYPE<br>
     *          WolfSSL.WOLFSSL_STREAM_TYPE<br>
     *          WolfSSL.WOLFSSL_AEAD_TYPE<br><br>
     *          <b><code>BAD_FUNC_ARG</code></b> will be returned for an
     *          error state.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getBulkCipher()
     * @see    #getHmacType()
     */
    public int getCipherType() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getCipherType()");

            return getCipherType(this.sslPtr);
        }
    }

    /**
     * Allows caller to set the Hmac Inner vector for message sending/receiving.
     * The result is written to <b>inner</b> which should be at least
     * getHmacSize() bytes. The size of the message is specified by <b>sz</b>,
     * <b>content</b> is the type of message, and <b>verify</b> specifies
     * whether this is a verification of a peer message. Valid for cipher
     * types excluding <b>WOLFSSL_AEAD_TYPE</b>.
     *
     * @param   inner    inner HMAC vector to set
     * @param   sz       size of the message, in bytes
     * @param   content  type of the message
     * @param   verify   specifies if this is a verification of a peer message.
     *
     * @return  <b><code>1</code></b> upon success,
     *          <b><code>BAD_FUNC_ARG</code></b> for an error state.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @see    #getBulkCipher()
     * @see    #getHmacType()
     */
    public int setTlsHmacInner(byte[] inner, long sz, int content,
            int verify) throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setTlsHmacInner(sz: " + sz + ", content: " +
                content + ", verify: " + verify + ")");

            return setTlsHmacInner(this.sslPtr, inner, sz, content, verify);
        }
    }

    /**
     * Allows caller to set the Atomic Record Processing Mac/Encrypt
     * Callback Context.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              MAC/Encrypt method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setMacEncryptCb(WolfSSLMacEncryptCallback)
     */
    public void setMacEncryptCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setMacEncryptCtx(" + ctx + ")");

            macEncryptCtx = ctx;
        }
    }

    /**
     * Allows caller to set the Atomic User Record Processing Decrypt/Verify
     * Callback Context.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              decrypt/verify method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setDecryptVerifyCb(WolfSSLDecryptVerifyCallback)
     */
    public void setDecryptVerifyCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setDecryptVerifyCtx(" + ctx + ")");

            decryptVerifyCtx = ctx;
        }
    }

    /**
     * Allows caller to set the Atomic User Record Processing Verify/Decrypt
     * Callback Context.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              verify/decrypt method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setVerifyDecryptCb(WolfSSLVerifyDecryptCallback)
     */
    public void setVerifyDecryptCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setVerifyDecryptCtx(" + ctx + ")");

            verifyDecryptCtx = ctx;
        }
    }

    /**
     * Allows caller to set the Public Key ECC Signing Callback Context.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              ECC signing method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setEccSignCb(WolfSSLEccSignCallback)
     */
    public void setEccSignCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setEccSignCtx(" + ctx + ")");

            eccSignCtx = ctx;
            setEccSignCtx(this.sslPtr);
        }
    }

    /**
     * Allows caller to set the Public Key ECC Verification Callback Context.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              ECC verification method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setEccVerifyCb(WolfSSLEccVerifyCallback)
     */
    public void setEccVerifyCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setEccVerifyCtx(" + ctx + ")");

            eccVerifyCtx = ctx;
            setEccVerifyCtx(this.sslPtr);
        }
    }

    /**
     * Allows caller to set the Public Key ECC Shared Secret Callback Context.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              ECC shared secret method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setEccSignCb(WolfSSLEccSignCallback)
     * @see    WolfSSLContext#setEccVerifyCb(WolfSSLEccVerifyCallback)
     */
    public void setEccSharedSecretCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setEccSharedSecretCtx(" + ctx + ")");

            eccSharedSecretCtx = ctx;
            setEccSharedSecretCtx(this.sslPtr);
        }
    }

    /**
     * Allows caller to set the Public Key RSA Signing Callback Context.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              RSA signing method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setRsaSignCb(WolfSSLRsaSignCallback)
     */
    public void setRsaSignCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setRsaSignCtx(" + ctx + ")");

            rsaSignCtx = ctx;
            setRsaSignCtx(this.sslPtr);
        }
    }

    /**
     * Allows caller to set the Public Key RSA Verification Callback
     * Context.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              RSA verification method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setRsaVerifyCb(WolfSSLRsaVerifyCallback)
     */
    public void setRsaVerifyCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setRsaVerifyCtx(" + ctx + ")");

            rsaVerifyCtx = ctx;
            setRsaVerifyCtx(this.sslPtr);
        }
    }

    /**
     * Allows caller to set the Public Key RSA Public Encrypt Callback
     * Context.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              RSA public encrypt method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setRsaEncCb(WolfSSLRsaEncCallback)
     */
    public void setRsaEncCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setRsaEncCtx(" + ctx + ")");

            rsaEncCtx = ctx;
            setRsaEncCtx(this.sslPtr);
        }
    }

    /**
     * Allows caller to set the Public Key RSA Private Decrypt Callback
     * Context.
     *
     * @param ctx   context object to be registered with the SSL session's
     *              RSA private decrypt method.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setRsaDecCb(WolfSSLRsaDecCallback)
     */
    public void setRsaDecCtx(Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setRsaDecCtx(" + ctx + ")");

            rsaDecCtx = ctx;
            setRsaDecCtx(this.sslPtr);
        }
    }

    /**
     * Allows caller to set the PSK client callback at the WolfSSLSession level.
     * This provides a method for the user to set the identity, hint, and key
     * the WolfSSLSession level. The PSK client callback can also be set at the
     * WolfSSLContext level, allowing the user to set it once for all
     * SSL/TLS sessions that are created from the WolfSSLContext.
     * The callback should return the length of the key in octets or
     * 0 for error. The <b>ssl</b> parameter is available for the user's
     * convenience. <b>hint</b> is the client PSK hint. <b>identity</b>
     * is the client identity, with a maximum size in characters of
     * <b>idMaxLen</b>. <b>key</b> is the client key, with a maximum size
     * in bytes of <b>keyMaxLen</b>. An example callback can be found
     * in examples/MyPskClientCallback.java.
     *
     * If the user sets the PSK client callback at both WolfSSLSession and
     * WolfSSLContext levels, the context-level one will be used.
     *
     * @param callback object to be registered as the PSK client callback
     *                 for the WolfSSLSession. The signature of this object
     *                 and corresponding method must match that as shown in
     *                 WolfSSLPskClientCallback.java, inside
     *                 pskClientCallback().
     * @throws IllegalStateException WolfSSLSession has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setPskClientCb(WolfSSLPskClientCallback)
     * @see    WolfSSLContext#setPskServerCb(WolfSSLPskServerCallback)
     * @see    WolfSSLContext#usePskIdentityHint(String)
     * @see    WolfSSLSession#setPskServerCb(WolfSSLPskServerCallback)
     * @see    WolfSSLSession#getPskIdentity()
     * @see    WolfSSLSession#getPskIdentityHint()
     */
    public void setPskClientCb(WolfSSLPskClientCallback callback)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setPskClientCb(" + callback + ")");

            /* set PSK client callback */
            internPskClientCb = callback;

            /* register internal callback with native library */
            setPskClientCb(this.sslPtr);
        }
    }

    /**
     * Allows caller to set the PSK server identity and key at the
     * WolfSSLSession level.
     * The PSK server callback can also be set at the WolfSSLContext level,
     * allowing the user to set it once for all SSL/TLS sessions that are
     * created from the WolfSSLContext.
     * The callback should return the length of the key in octets or
     * 0 for error. The <b>ssl</b> parameter is available for the user's
     * convenience. <b>identity</b> is the client identity,
     * <b>key</b> is the server key, with a maximum size
     * in bytes of <b>keyMaxLen</b>. An example callback can be found
     * in examples/MyPskServerCallback.java.
     *
     * @param callback object to be registered as the PSK server callback
     *                 for the WolfSSLSession. The signature of this object
     *                 and corresponding method must match that as shown in
     *                 WolfSSLPskServerCallback.java, inside
     *                 pskServerCallback().
     * @throws IllegalStateException WolfSSLSession has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    WolfSSLContext#setPskClientCb(WolfSSLPskClientCallback)
     * @see    WolfSSLContext#setPskServerCb(WolfSSLPskServerCallback)
     * @see    WolfSSLContext#usePskIdentityHint(String)
     * @see    WolfSSLSession#setPskClientCb(WolfSSLPskClientCallback)
     * @see    WolfSSLSession#getPskIdentity()
     * @see    WolfSSLSession#getPskIdentityHint()
     */
    public void setPskServerCb(WolfSSLPskServerCallback callback)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setPskServerCb(" + callback + ")");

            /* set PSK server callback */
            internPskServerCb = callback;

            /* register internal callback with native library */
            setPskServerCb(this.sslPtr);
        }
    }

    /**
     * Returns the PSK identity hint.
     *
     * @throws IllegalStateException WolfSSLContext has been freed
     * @return PSK identity hint String
     * @see    WolfSSLContext#setPskClientCb(WolfSSLPskClientCallback)
     * @see    WolfSSLContext#setPskServerCb(WolfSSLPskServerCallback)
     * @see    WolfSSLContext#usePskIdentityHint(String)
     * @see    WolfSSLSession#setPskClientCb(WolfSSLPskClientCallback)
     * @see    WolfSSLSession#setPskServerCb(WolfSSLPskServerCallback)
     * @see    WolfSSLSession#getPskIdentity()
     * @see    WolfSSLSession#usePskIdentityHint(String)
     */
    public String getPskIdentityHint() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getPskIdentityHint()");

            return getPskIdentityHint(this.sslPtr);
        }
    }

    /**
     * Returns the PSK identity.
     *
     * @throws IllegalStateException WolfSSLContext has been freed
     * @return PSK identity hint String
     * @see    WolfSSLContext#setPskClientCb(WolfSSLPskClientCallback)
     * @see    WolfSSLContext#setPskServerCb(WolfSSLPskServerCallback)
     * @see    WolfSSLContext#usePskIdentityHint(String)
     * @see    WolfSSLSession#setPskClientCb(WolfSSLPskClientCallback)
     * @see    WolfSSLSession#setPskServerCb(WolfSSLPskServerCallback)
     * @see    WolfSSLSession#getPskIdentityHint()
     * @see    WolfSSLSession#usePskIdentityHint(String)
     */
    public String getPskIdentity() {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getPskIdentity()");

            return getPskIdentity(this.sslPtr);
        }
    }

    /**
     * Sets the identity hint for this session.
     *
     * @param  hint  identity hint to be used for session.
     * @throws IllegalStateException WolfSSLContext has been freed
     * @return <code>SSL_SUCCESS</code> upon success,
     *         <code>SSL_FAILURE</code> upon error.
     * @see    WolfSSLContext#setPskClientCb(WolfSSLPskClientCallback)
     * @see    WolfSSLContext#setPskServerCb(WolfSSLPskServerCallback)
     * @see    WolfSSLContext#usePskIdentityHint(String)
     * @see    WolfSSLSession#setPskClientCb(WolfSSLPskClientCallback)
     * @see    WolfSSLSession#setPskServerCb(WolfSSLPskServerCallback)
     * @see    WolfSSLSession#getPskIdentityHint()
     * @see    WolfSSLSession#getPskIdentity()
     */
    public int usePskIdentityHint(String hint) {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered usePskIdentityHint(" + hint + ")");

            return usePskIdentityHint(this.sslPtr, hint);
        }
    }

    /**
     * Used to determine if the handshake has been completed.
     *
     * @throws IllegalStateException WolfSSLContext has been freed
     * @return true if the handshake is completed -- false if not.
     */
    public boolean handshakeDone() {

        final boolean done;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered handshakeDone()");

            done = handshakeDone(this.sslPtr);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "handshake done: " + done);
        }

        return done;
    }

    /**
     * Sets the WOLFSSL to be a client
     *
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public void setConnectState() {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setConnectState()");

            setConnectState(this.sslPtr);
        }
    }

    /**
     * Sets the WOLFSSL to be a server
     *
     * @throws IllegalStateException WolfSSLContext has been freed\
     */
    public void setAcceptState() {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setAcceptState()");

            setAcceptState(this.sslPtr);
        }
    }

    /**
     * Sets the verification method for remote peers and also allows a
     * verify callback to be registered with the SSL session.
     * If no verify callback is desired, null can be used for <code>
     * callback</code>.
     * <p>
     * The verification <b>mode</b> of peer certificates is a logically
     * OR'd list of flags. The possible flag values include:
     * <p>
     * <code>SSL_VERIFY_NONE</code><br>
     * <b>Client mode:</b> the client will not verify the certificate
     * received from teh server and the handshake will continue as normal.<br>
     * <b>Server mode:</b> the server will not send a certificate request to
     * the client. As such, client verification will not be enabled.
     * <p>
     * <code>SSL_VERIFY_PEER</code><br>
     * <b>Client mode:</b> the client will verify the certificate received
     * from the server during the handshake. This is turned on by default in
     * wolfSSL, therefore, using this option has no effect.<br>
     * <b>Server mode:</b> the server will send a certificate request to the
     * client and verify the client certificate received.
     * <p>
     * <code>SSL_VERIFY_FAIL_IF_NO_PEER_CERT</code><br>
     * <b>Client mode:</b> no effect when used on the client side.<br>
     * <b>Server mode:</b> the verification will fail on the server side if
     * the client fails to send a certificate when requested to do so (when
     * using SSL_VERIFY_PEER on the SSL server).
     *
     * @param mode      verification type
     * @param callback  custom verification callback to register with the SSL
     *                  session. If no callback is desired, <code>null</code>
     *                  may be used.
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public void setVerify(int mode, WolfSSLVerifyCallback callback)
        throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setVerify(mode: " + mode + ", callback: " +
                callback + ")");

            setVerify(this.sslPtr, mode, callback);
        }
    }

    /**
     * Sets the options to use for the WOLFSSL structure.
     * Example options are WolfSSL.SSL_OP_NO_SSLv3
     *
     *
     * @param op      bit mask of options to set
     * @return returns the revised options bit mask on success
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public long setOptions(long op)
            throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setOptions(" + op + ")");

            return setOptions(this.sslPtr, op);
        }
    }


    /**
     * Gets the options to use for the WOLFSSL structure.
     * Example options are WolfSSL.SSL_OP_NO_SSLv3
     *
     *
     * @return returns the revised options bit mask on success
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public long getOptions()
            throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getOptions()");

            return getOptions(this.sslPtr);
        }
    }

    /**
     * Returns true if the last alert received by this session was a
     * close_notify alert from the peer.
     *
     * @return true if close_notify has been received, otherwise false
     */
    public boolean gotCloseNotify() {

        final boolean gotNotify;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered gotCloseNotify()");

            int ret = gotCloseNotify(this.sslPtr);
            if (ret == 1) {
                gotNotify = true;
            } else {
                gotNotify = false;
            }

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "got close notify: " + gotNotify);

            return gotNotify;
        }
    }

    /**
     * Registers a receive callback for wolfSSL to get input data.
     *
     * This receive callback uses WolfSSLIORecvCallback which uses
     * byte arrays (byte[]). To use direct ByteBuffers, and avoid an extra
     * JNI array allocaiton, use
     * setIORecvByteBuffer(WolfSSLByteBufferIORecvCallback).
     *
     * By default, wolfSSL uses EmbedReceive() in src/io.c as the callback
     * which uses the system TCP recv() function. The user can register a
     * method here to get receive TLS-encoded data from memory, some other
     * network module, or anywhere else. Please see the EmbedReceive() function
     * in src/io.c as a guide for how the function should work and for error
     * codes.
     * <p>
     * In particular, <b>IO_ERR_WANT_READ</b> should be returned for
     * non-blocking receive when no data is ready.
     *
     * @param callback  method to be registered as the receive callback for
     *                  the wolfSSL context. The signature of this function
     *                  must follow that as shown in
     *                  WolfSSLIORecvCallback#receiveCallback(WolfSSLSession,
     *                  byte[], int, long).
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #setIOSend(WolfSSLIOSendCallback)
     */
    public void setIORecv(WolfSSLIORecvCallback callback)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setIORecv(" + callback + ") - byte array");

            /* Only allow one recv callback registered at a time, if ByteBuffer
             * version has been set, set to null before setting byte variant. */
            if (internRecvSSLCb_BB != null) {
                internRecvSSLCb_BB = null;
            }

            /* set user I/O recv */
            internRecvSSLCb_array = callback;

            if (callback != null) {
                /* register internal callback with native library */
                setSSLIORecv(this.sslPtr);
            }
        }
    }

    /**
     * Registers a receive callback for wolfSSL to get input data.
     *
     * This receive callback uses WolfSSLByteBufferIORecvCallback which uses
     * a direct ByteBuffer. To use a byte array instead use
     * setIORecv(WolfSSLIORecvCallback), but this will incur one additional
     * native JNI array allocation.
     *
     * By default, wolfSSL uses EmbedReceive() in src/io.c as the callback
     * which uses the system TCP recv() function. The user can register a
     * method here to get receive TLS-encoded data from memory, some other
     * network module, or anywhere else. Please see the EmbedReceive() function
     * in src/io.c as a guide for how the function should work and for error
     * codes.
     * <p>
     * In particular, <b>IO_ERR_WANT_READ</b> should be returned for
     * non-blocking receive when no data is ready.
     *
     * @param callback  method to be registered as the receive callback for
     *                  the wolfSSL context. The signature of this function
     *                  must follow that as shown in
     *                  WolfSSLByteBufferIORecvCallback#receiveCallback(
     *                  WolfSSLSession, ByteBuffer, int, long).
     * @throws IllegalStateException WolfSSLContext has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #setIOSend(WolfSSLIOSendCallback)
     */
    public void setIORecvByteBuffer(WolfSSLByteBufferIORecvCallback callback)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setIORecv(" + callback + ") - ByteBuffer");

            /* Only allow one recv callback registered at a time, if array
             * version has been set, set to null before setting ByteBuffer. */
            if (internRecvSSLCb_array != null) {
                internRecvSSLCb_array = null;
            }

            /* set user I/O recv */
            internRecvSSLCb_BB = callback;

            if (callback != null) {
                /* Register internal callback with native library, registers
                 * JNI NativeSSLIORecvCb() with native wolfSSL.
                 * NativeSSLIORecvCb() will then call back into
                 * internRecvSSLCb_array/BB(). */
                setSSLIORecv(this.sslPtr);
            }
        }
    }

    /**
     * Registers a send callback for wolfSSL to write TLS encoded output data.
     *
     * This send callback uses WolfSSLIOSendCallback which uses byte arrays
     * (byte[]). To use direct ByteBuffers, and avoid an extra JNI array
     * allocation, use setIOSendByteBuffer(WolfSSLByteBufferIOSendCallback).
     *
     * By default, wolfSSL uses EmbedSend() in src/io.c as the callback,
     * which uses the system TCP send() function. The user can register
     * a method here to send TLS-encoded output data to memory, some other
     * network module, or to anywhere else. Please see the EmbedSend() function
     * in src/io.c as a guide for how the function should work and for error
     * codes.
     * <p>
     * In particular, <b>IO_ERR_WANT_WRITE</b> should be returned for
     * non-blocking send when the action cannot be taken yet.
     *
     * @param callback  method to be registered as the send callback for
     *                  the wolfSSL context. The signature of this function
     *                  must follow that as shown in
     *                  WolfSSLIOSendCallback#sendCallback(WolfSSLSession,
     *                  byte[], int, Object).
     * @throws IllegalStateException WolfSSLSession has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #setIORecv(WolfSSLIORecvCallback)
     */
    public void setIOSend(WolfSSLIOSendCallback callback)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setIOSend(" + callback + ") - byte array");

            /* Only allow one send callback registered at a time, if ByteBuffer
             * version has been set, set to null before setting byte variant. */
            if (internSendSSLCb_BB != null) {
                internSendSSLCb_BB = null;
            }

            /* set user I/O send */
            internSendSSLCb_array = callback;

            if (callback != null) {
                /* Register internal callback with native library, registers
                 * JNI NativeSSLIOSendCb() with native wolfSSL.
                 * NativeSSLIOSendCb() will then call back into
                 * internSendSSLCb_array/BB(). */
                setSSLIOSend(this.sslPtr);
            }
        }
    }

    /**
     * Registers a send callback for wolfSSL to write TLS encoded output data.
     *
     * This send callback uses WolfSSLByteBufferIOSendCallback which uses a
     * ByteBuffer. To use a byte array instead, use
     * setIOSend(WolfSSLIOSendCallback), but this will incur one
     * additional native JNI array allocation.
     *
     * By default, wolfSSL uses EmbedSend() in src/io.c as the callback,
     * which uses the system TCP send() function. The user can register
     * a method here to send TLS-encoded output data to memory, some other
     * network module, or to anywhere else. Please see the EmbedSend() function
     * in src/io.c as a guide for how the function should work and for error
     * codes.
     * <p>
     * In particular, <b>IO_ERR_WANT_WRITE</b> should be returned for
     * non-blocking send when the action cannot be taken yet.
     *
     * @param callback  method to be registered as the send callback for
     *                  the wolfSSL context. The signature of this function
     *                  must follow that as shown in
     *                  WolfSSLByteBufferIOSendCallback#sendCallback(
     *                  WolfSSLSession, ByteBuffer, int, Object).
     * @throws IllegalStateException WolfSSLSession has been freed
     * @throws WolfSSLJNIException Internal JNI error
     * @see    #setIORecv(WolfSSLIORecvCallback)
     */
    public void setIOSendByteBuffer(WolfSSLByteBufferIOSendCallback callback)
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setIOSend(" + callback + ") - ByteBuffer");

            /* Only allow one recv callback registered at a time, if array
             * version has been set, set to null before setting ByteBuffer. */
            if (internSendSSLCb_array != null) {
                internSendSSLCb_array = null;
            }

            /* set user I/O send */
            internSendSSLCb_BB = callback;

            if (callback != null) {
                /* Register internal callback with native library, registers
                 * JNI NativeSSLIOSendCb() with native wolfSSL.
                 * NativeSSLIOSendCb() will then call back into
                 * internSendSSLCb_array/BB(). */
                setSSLIOSend(this.sslPtr);
            }
        }
    }

    /**
     * Interrupt native I/O operations blocked inside select()/poll().
     *
     * This is used by wolfJSSE when SSLSocket.close() is called, to wake up
     * threads that are blocked in select()/poll().
     *
     * @return WolfSSL.SSL_SUCCESS on success, negative on error.
     *
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public synchronized int interruptBlockedIO()
        throws IllegalStateException {

        confirmObjectIsActive();

        /* Not synchronizing on sslLock, since we want to interrupt threads
         * blocked on I/O operations, which will already hold sslLock */

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.sslPtr,
            () -> "entered interruptBlockedIO()");

        return interruptBlockedIO(this.sslPtr);
    }

    /**
     * Get count of threads currently blocked in select() or poll()
     * at the native JNI level.
     *
     * @return count of threads waiting in select() or poll()
     *
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public synchronized int getThreadsBlockedInPoll()
        throws IllegalStateException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.sslPtr,
            () -> "entered getThreadsBlockedInPoll()");

        return getThreadsBlockedInPoll(this.sslPtr);
    }

    /**
     * Use SNI name with this session.
     *
     * @param type SNI type. Currently supported type is
     *        WolfSSL.WOLFSSL_SNI_HOST_NAME.
     * @param data encoded data for SNI extension value
     *
     * @return WolfSSL.SSL_SUCCESS on success, negative on error
     *
     * @throws IllegalStateException if called when WolfSSLSession is not
     *         active
     */
    public int useSNI(byte type, byte[] data)
        throws IllegalStateException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered useSNI(type: " + type + ")");

            ret = useSNI(this.sslPtr, type, data);

            if (ret == WolfSSL.SSL_SUCCESS) {
                /* Save SNI requested by client for use later if needed */
                this.clientSNIRequested = Arrays.copyOf(data, data.length);
            }
        }

        return ret;
    }

    /**
     * Return copy of SNI name that this client set/requested. Used at JSSE
     * level by Endpoint Identification hostname matching on client-side.
     *
     * @return client-requested SNI name as byte array, or null if not set
     * @throws IllegalStateException if called when WolfSSLSession is not
     *         active
     */
    public byte[] getClientSNIRequest()
        throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getClientSNIRequest()");

            if (this.clientSNIRequested == null) {
                return null;
            }

            return Arrays.copyOf(this.clientSNIRequested,
                this.clientSNIRequested.length);
        }
    }

    /**
     * Get SNI request used for this session object as bytes.
     *
     * @param type SNI type. Currently supported type is
     *             WolfSSL.WOLFSSL_SNI_HOST_NAME.
     * @return SNI name requested in this session as a byte array, or
     *         null if not available.
     * @throws IllegalStateException if called when WolfSSLSession is not
     *         active
     */
    public byte[] getSNIRequestBytes(byte type) throws IllegalStateException {

        byte[] reqBytes = null;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getSNIRequest(type: " + type + ")");

            /* Returns a byte array representing SNI host name */
            reqBytes = getSNIRequest(this.sslPtr, type);
        }

        if (reqBytes != null) {
            return reqBytes;
        }

        return null;
    }

    /**
     * Get SNI request used for this session object as String.
     *
     * @param type SNI type. Currently supported type is
     *             WolfSSL.WOLFSSL_SNI_HOST_NAME.
     * @return String representing SNI name requested in this session, or
     *         null if not available.
     * @throws IllegalStateException if called when WolfSSLSession is not
     *         active
     */
    public String getSNIRequest(byte type) throws IllegalStateException {
        byte[] request;

        confirmObjectIsActive();
        request = getSNIRequestBytes(type);
        if (request != null){
            return new String(request, StandardCharsets.UTF_8);
        }

        return null;
    }

    /**
     * Enable session tickets for this session.
     *
     * @return WolfSSL.SSL_SUCCESS on success, otherwise negative.
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public int useSessionTicket()
        throws IllegalStateException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered useSessionTicket()");

            ret = useSessionTicket(this.sslPtr);

            if (ret == WolfSSL.SSL_SUCCESS) {
                this.sessionTicketsEnabled = true;
            }

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "enabled session tickets for session, ret: " + ret);
        }

        return ret;
    }

    /**
     * Determine if session tickets have been enabled for this session.
     * Session tickets can be enabled for this session by calling
     * WolfSSLSession.useSessionTicket().
     *
     * @return true if enabled, otherwise false.
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public synchronized boolean sessionTicketsEnabled()
        throws IllegalStateException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.sslPtr,
            () -> "entered sessionTicketsEnabled(): " +
            this.sessionTicketsEnabled);

        return this.sessionTicketsEnabled;
    }

    /**
     * Set ALPN extension protocol for this session from encoded byte array.
     * Calls SSL_set_alpn_protos() at native level. Format starts with
     * length, where length does not include length byte itself. Example format:
     *
     * byte[] p = "http/1.1".getBytes();
     *
     * Unless this input format is explicitly needed, useALPN(String[], int)
     * will likely be easier to use.
     *
     * @param alpnProtos ALPN protocols, encoded as byte array vector
     * @return WolfSSL.SSL_SUCCESS on success, otherwise negative on error.
     */
    public int useALPN(byte[] alpnProtos) throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered useALPN(byte[])");

            return sslSetAlpnProtos(this.sslPtr, alpnProtos);
        }
    }

    /**
     * Set ALPN extension protocol for this session from String array.
     * Calls native wolfSSL_useALPN(), where protocols should be a String
     * array of ALPN protocols. At the native JNI level, this is converted to
     * a comma-delimited list of prototocls and passed to native wolfSSL.
     *
     * This method is similar to useALPN(byte[]), but accepts a String array
     * and calls a different native wolfSSL API for ALPN use.
     *
     * @param protocols Array of ALPN protocol Strings
     * @param options Options to control behavior of ALPN failure mode.
     *                Possible options include:
     *                    WolfSSL.WOLFSSL_ALPN_CONTINUE_ON_MISMATCH
     *                    WolfSSL.WOLFSSL_ALPN_FAILED_ON_MISMATCH
     * @return WolfSSL.SSL_SUCCESS on success, otherwise negative on error.
     *
     */
    public int useALPN(String[] protocols, int options) {

        /* all protocols, comma delimited */
        StringBuilder allProtocols = new StringBuilder();

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered useALPN(String[], int)");
        }

        if (protocols == null) {
            return WolfSSL.BAD_FUNC_ARG;
        }

        for (int i = 0; i < protocols.length; i++) {
            if (i != 0) {
                allProtocols.append(",");
            }
            allProtocols.append(protocols[i]);
        }

        synchronized (sslLock) {
            return useALPN(this.sslPtr, allProtocols.toString(), options);
        }
    }

    /**
     * Get the ALPN protocol selected by the client/server for this session.
     *
     * @return byte array representation of selected protocol.
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public byte[] getAlpnSelected() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getAlpnSelected()");

            return sslGet0AlpnSelected(this.sslPtr);
        }
    }

    /**
     * Get the ALPN protocol selected by the client/server for this session.
     *
     * Same behavior as getAlpnSelected(), but returns a String instead of a
     * byte array.
     *
     * @return String of the selected ALPN protocol
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public String getAlpnSelectedString() throws IllegalStateException {

        byte[] alpnSelectedBytes = null;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getAlpnSelectedString()");
        }

        alpnSelectedBytes = getAlpnSelected();

        if (alpnSelectedBytes != null) {
            return new String(alpnSelectedBytes, StandardCharsets.UTF_8);
        } else {
            return null;
        }
    }

    /**
     * Registers ALPN select callback.
     *
     * This callback is called by native wolfSSL during the handshake
     * on the server side after receiving the ALPN protocols by the client
     * in the ClientHello message.
     *
     * @param cb callback to be registered with SSL session
     * @param arg Object that will be passed back to user inside callback
     *
     * @return    <code>SSL_SUCCESS</code> upon success. <code>
     *            NOT_COMPILED_IN</code> if wolfSSL was not compiled with
     *            ALPN support, and other negative value representing other
     *            error scenarios.
     *
     * @throws IllegalStateException WolfSSLSession has been freed
     * @throws WolfSSLJNIException Internal JNI error
     */
    public int setAlpnSelectCb(WolfSSLALPNSelectCallback cb, Object arg)
        throws IllegalStateException, WolfSSLJNIException {

        int ret = 0;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setAlpnSelectCb(" + cb + ", Object: " +
                arg + ")");

            ret = setALPNSelectCb(this.sslPtr);
            if (ret == WolfSSL.SSL_SUCCESS) {
                /* set ALPN select callback */
                internAlpnSelectCb = cb;

                /* set ALPN select arg Object, returned to user in callback */
                this.alpnSelectArg = arg;
            }
        }

        return ret;
    }

    /**
     * Register TLS 1.3 secret callback.
     *
     * The callback registered by this method is called by native wolfSSL
     * during TLS 1.3 connection to retrieve the secrets used in those
     * connections. These can be printed to a log file for consumption by
     * Wireshark.
     *
     * @param cb callback to be registered with this SSL session
     * @param ctx Object that will be passed back to user inside callback
     *
     * @return <code>SSL_SUCCESS</code> on success. <code>
     *         NOT_COMPILED_IN</code> if wolfSSL was not compiled with
     *         TLS 1.3 and HAVE_SECERT_CALLBACK defined, and other
     *         negative value on error.
     *
     * @throws IllegalStateException WolfSSLSession has been freed
     * @throws WolfSSLJNIException Internal JNI error
     */
    public int setTls13SecretCb(WolfSSLTls13SecretCallback cb, Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        int ret = 0;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setTls13SecretCb(" + cb + ", ctx: " + ctx + ")");

            ret = setTls13SecretCb(this.sslPtr);
            if (ret == WolfSSL.SSL_SUCCESS) {
                /* Set TLS 1.3 secret callback */
                internTls13SecretCb = cb;

                /* Set TLS 1.3 secret ctx Object, returned to user in cb */
                this.tls13SecretCtx = ctx;
            }
        }

        return ret;
    }

    /**
     * Register session ticket callback.
     *
     * The callback registered by this method is called by native wolfSSL
     * when a session ticket is received from the peer.
     *
     * @param cb callback to be registered with this SSL session
     * @param ctx Object that will be passed back to user inside callback
     *
     * @return <code>SSL_SUCCESS</code> on success. <code>
     *        NOT_COMPILED_IN</code> if wolfSSL was not compiled with
     *        session ticket support, and other negative value on error.
     *
     * @throws IllegalStateException WolfSSLSession has been freed
     * @throws WolfSSLJNIException Internal JNI error
     */
    public int setSessionTicketCb(WolfSSLSessionTicketCallback cb, Object ctx)
        throws IllegalStateException, WolfSSLJNIException {

        int ret = 0;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered setSessionTicketCb(" + cb + ", ctx: " +
                ctx + ")");

            ret = setSessionTicketCb(this.sslPtr);
            if (ret == WolfSSL.SSL_SUCCESS) {
                /* Set session ticket callback */
                internSessionTicketCb = cb;

                /* Set session ticket ctx Object, returned to user in cb */
                this.sessionTicketCtx = ctx;
            }
        }

        return ret;
    }

    /**
     * Do not free temporary arrays at end of handshake.
     *
     * This needs to be called if using the TLS 1.3 secret callback, and
     * should be called after the WolfSSLSession object has been created.
     *
     * @throws IllegalStateException WolfSSLSession has been freed
     * @throws WolfSSLJNIException Internal JNI error
     */
    public void keepArrays()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered keepArrays()");

            keepArrays(this.sslPtr);
        }
    }

    /**
     * Get the client random value used in this SSL/TLS session.
     *
     * @return client random byte array on success, or null if not available
     *
     * @throws IllegalStateException WolfSSLSession has been freed
     * @throws WolfSSLJNIException Internal JNI error
     */
    public byte[] getClientRandom()
        throws IllegalStateException, WolfSSLJNIException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getClientRandom()");

            return getClientRandom(this.sslPtr);
        }
    }

    /**
     * Enable use of secure renegotiation on this session. Calling this
     * API does not initiate secure renegotiation, but enables it. If enabled,
     * and peer requests secure renegotiation, this session will renegotiate.
     *
     * @return <code>WolfSSL.SSL_SUCCESS</code> on success, otherwise negative.
     *         Will return <code>WolfSSL.NOT_COMPILED_IN</code> if native
     *         wolfSSL has not been compiled with
     *         <code>HAVE_SECURE_RENEGOTIATION</code>.
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    public int useSecureRenegotiation() throws IllegalStateException {

        confirmObjectIsActive();

       synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered useSecureRenegotiation()");

             return useSecureRenegotiation(this.sslPtr);
        }
    }

    /**
     * Enable use of secure renegotiation on this session. Calling this
     * API does not initiate secure renegotiation, but enables it. If enabled,
     * and peer requests secure renegotiation, this session will renegotiate.
     *
     * @return WolfSSL.SSL_SUCCESS on success, otherwise negative. Will
     *         return WolfSSL.NOT_COMPILED_IN if native wolfSSL has not been
     *         compiled with HAVE_SECURE_RENEGOTIATION.
     * @throws IllegalStateException WolfSSLSession has been freed
     */
    /**
     * Initiates a secure renegotiation attempt with the peer.
     * For this function to attempt a secure renegotiation,
     * <code>useSecureRenegotiation()</code> must be called prior to calling
     * this method. When called, the underlying communication channel should
     * also already be set up.
     * <p>
     * <code>rehandshake()</code> works with both blocking and non-blocking I/O.
     * When the underlying I/O is non-blocking, <code>rehandshake()</code> will
     * return when the underlying I/O could not satisfy the needs of
     * <code>rehandshake()</code> to continue the handshake. In this case, a
     * call to <code>getError</code> will yield either
     * <b>SSL_ERROR_WANT_READ</b> or <b>SSL_ERROR_WANT_WRITE</b>. The calling
     * process must then repeat the call to <code>rehandshake()</code> when the
     * underlying I/O is ready and wolfSSL will pick up where it left off.
     * <p>
     * If the underlying I/O is blocking, <code>rehandshake()</code> will only
     * return once the handshake has been finished or an error occurred.
     * </p>
     *
     * @return <code>SSL_SUCCESS</code> if successful, otherwise
     *         <code>SSL_FATAL_ERROR</code> if an error occurred. To get
     *         a more detailed error code, call <code>getError()</code>.
     *         <code>WolfSSL.NOT_COMPILED_IN</code> will be returned if
     *         native wolfSSL has not been compiled with
     *         <code>HAVE_SECURE_RENEGOTIATION</code>.
     *         <code>WolfSSL.SECURE_RENEGOTIATION_E</code> will be returned
     *         if secure renegotiation has not been enabled for this session,
     *         or a secure renegotiation error has occurred.
     * @throws IllegalStateException WolfSSLContext has been freed
     */
    public int rehandshake() throws IllegalStateException {

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered rehandshake()");

            return rehandshake(this.sslPtr);
        }
    }

    /**
     * Getter function to tell if shutdown has been sent or received
     * @return WolfSSL.SSL_SENT_SHUTDOWN or WolfSSL.SSL_RECEIVED_SHUTDOWN
     */
    public int getShutdown() throws IllegalStateException {

        int ret;

        confirmObjectIsActive();

        synchronized (sslLock) {
            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "entered getShutdown()");

            ret = getShutdown(this.sslPtr);

            WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                WolfSSLDebug.INFO, this.sslPtr,
                () -> "getShutdown(), ret: " + ret);
        }

        return ret;
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void finalize() throws Throwable
    {
        /* free resources, freeSSL() checks and sets state */
        this.freeSSL();
        super.finalize();
    }

} /* end WolfSSLSession */

