#include <com_mysaifu_jvm_java_security_provider_WCESSLEngine.h>
#include "SSLEngine.h"
#include "wcesecurity.h"
#include "KeyStore.h"
#include "JCL_buffer.h"

#define PROTOCOL_NAME_SSLV3                 _T("SSLv3")
#define PROTOCOL_NAME_TLSV1                 _T("TLSv1")

#define SSL_ENGINE_RESULT_CLASS_NAME        "javax/net/ssl/SSLEngineResult"
#define STATUS_CLASS_NAME                   "javax/net/ssl/SSLEngineResult$Status"
#define HANDSHAKE_STATUS_CLASS_NAME         "javax/net/ssl/SSLEngineResult$HandshakeStatus"
#define SSL_EXCEPTION_CLASS_NAME            "javax/net/ssl/SSLException"
#define SSL_HANDSHAKE_EXCEPTION_CLASS_NAME  "javax/net/ssl/SSLHandshakeException"
#define ILLEGAL_ARGUMENT_EXCEPTION_NAME     "java/lang/IllegalArgumentException"

#define STATUS_FIELD_SIG                "L" ## STATUS_CLASS_NAME ## ";"
#define HANDSHAKE_STATUS_FIELD_SIG      "L" ## HANDSHAKE_STATUS_CLASS_NAME ## ";"

static jclass    g_SSLEngineResult_class;
static jclass    g_Status_class;
static jclass    g_HandshakeStatus_class;

static jmethodID g_SSLEngineResult_constructor_mid;

static jobject   g_Status_BUFFER_OVERFLOW;
static jobject   g_Status_BUFFER_UNDERFLOW;
static jobject   g_Status_CLOSED;
static jobject   g_Status_OK;
static jobject   g_HandshakeStatus_FINISHED;
static jobject   g_HandshakeStatus_NEED_TASK;
static jobject   g_HandshakeStatus_NEED_UNWRAP;
static jobject   g_HandshakeStatus_NEED_WRAP;
static jobject   g_HandshakeStatus_NOT_HANDSHAKING;

static jobject createSSLEngineResult(JNIEnv* env, const SSLEngineResult& engineResult);
static void throwSSLException(JNIEnv* env, SSLEngine* pEngine, SSLEngineTaskStatus ts);

/**
 * SSLEngineResultオブジェクトを作成する
 */
static jobject createSSLEngineResult(JNIEnv* env, const SSLEngineResult& engineResult)
{
  jobject status;
  switch (engineResult.getStatus())
  {
  case BUFFER_OVERFLOW:
    status = g_Status_BUFFER_OVERFLOW;
    break;

  case BUFFER_UNDERFLOW:
    status = g_Status_BUFFER_UNDERFLOW;
    break;

  case CLOSED:
    status = g_Status_CLOSED;
    break;

  case OK:
    status = g_Status_OK;
    break;

  default:
    assert(false);
  }

  jobject handshakeStatus;
  switch (engineResult.getHandshakeStatus())
  {
  case FINISHED:
    handshakeStatus = g_HandshakeStatus_FINISHED;
    break;

  case NEED_TASK:
    handshakeStatus = g_HandshakeStatus_NEED_TASK;
    break;

  case NEED_UNWRAP:
    handshakeStatus = g_HandshakeStatus_NEED_UNWRAP;
    break;

  case NEED_WRAP:
    handshakeStatus = g_HandshakeStatus_NEED_WRAP;
    break;

  case NOT_HANDSHAKING:
    handshakeStatus = g_HandshakeStatus_NOT_HANDSHAKING;
    break;

  default:
    assert(false);
  }
  return env->NewObject(g_SSLEngineResult_class,
                        g_SSLEngineResult_constructor_mid,
                        status,
                        handshakeStatus,
                        engineResult.bytesConsumed(),
                        engineResult.bytesProduced()
                        );
}

/**
 * タスク実行結果に対応する例外を生成する
 */
static void
throwSSLException(JNIEnv* env, SSLEngine* pEngine, SSLEngineTaskStatus ts)
{
  switch (ts)
  {
  case TASK_CERTIFICATE_VERIFY_ERROR:
    {
      const CERT_CHAIN_CONTEXT* chain = pEngine->getPeerCertificateChain();
      char msg[128];
      if (chain)
      {
        DWORD dwErrorStatus = chain->TrustStatus.dwErrorStatus;
        DWORD dwInfoStatus  = chain->TrustStatus.dwInfoStatus;

        const char format[] = "Certification verify error: dwErrorStatus=0x%x dwInfoStatus=0x%x";
        // ToDo:適切なエラーメッセージを作る
        sprintf(msg, format, dwErrorStatus, dwInfoStatus);
      }
      else
      {
        strcpy(msg, "Failed to get certificate chain");
      }
      env->ThrowNew(env->FindClass(SSL_HANDSHAKE_EXCEPTION_CLASS_NAME), msg);
    }
    break;

  case TASK_ILLEGAL_STATE_ERROR:
    {
      env->ThrowNew(env->FindClass(SSL_HANDSHAKE_EXCEPTION_CLASS_NAME), "Internal error");
    }
    break;

  default:
    break;

  }
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    initIDs
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_initIDs
  (JNIEnv *env, jclass)
{
#define NONNULL(o) { if (!o){env->ThrowNew(env->FindClass("java/lang/NullPointerException"), #o); return; }}
  
  g_SSLEngineResult_class = env->FindClass(SSL_ENGINE_RESULT_CLASS_NAME);
  g_Status_class = env->FindClass(STATUS_CLASS_NAME);
  g_HandshakeStatus_class = env->FindClass(HANDSHAKE_STATUS_CLASS_NAME);
  NONNULL(g_SSLEngineResult_class);
  NONNULL(g_Status_class);
  NONNULL(g_HandshakeStatus_class);

  jfieldID fid = env->GetStaticFieldID(g_Status_class, "BUFFER_OVERFLOW", STATUS_FIELD_SIG);
  g_Status_BUFFER_OVERFLOW  = env->NewGlobalRef(env->GetStaticObjectField(g_Status_class, fid));

  fid = env->GetStaticFieldID(g_Status_class, "BUFFER_UNDERFLOW", STATUS_FIELD_SIG);
  g_Status_BUFFER_UNDERFLOW = env->NewGlobalRef(env->GetStaticObjectField(g_Status_class, fid));
   
  fid = env->GetStaticFieldID(g_Status_class, "CLOSED", STATUS_FIELD_SIG);                                                                           
  g_Status_CLOSED           = env->NewGlobalRef(env->GetStaticObjectField(g_Status_class, fid));

  fid = env->GetStaticFieldID(g_Status_class, "OK", STATUS_FIELD_SIG);
  g_Status_OK               =  env->NewGlobalRef(env->GetStaticObjectField(g_Status_class, fid));

  fid = env->GetStaticFieldID(g_HandshakeStatus_class, "FINISHED", HANDSHAKE_STATUS_FIELD_SIG);
  g_HandshakeStatus_FINISHED = env->NewGlobalRef(env->GetStaticObjectField(g_HandshakeStatus_class, fid));

  fid = env->GetStaticFieldID(g_HandshakeStatus_class, "NEED_TASK", HANDSHAKE_STATUS_FIELD_SIG);
  g_HandshakeStatus_NEED_TASK = env->NewGlobalRef(env->GetStaticObjectField(g_HandshakeStatus_class, fid));

  fid = env->GetStaticFieldID(g_HandshakeStatus_class, "NEED_UNWRAP", HANDSHAKE_STATUS_FIELD_SIG);
  g_HandshakeStatus_NEED_UNWRAP = env->NewGlobalRef(env->GetStaticObjectField(g_HandshakeStatus_class, fid));

  fid = env->GetStaticFieldID(g_HandshakeStatus_class, "NEED_WRAP", HANDSHAKE_STATUS_FIELD_SIG);
  g_HandshakeStatus_NEED_WRAP = env->NewGlobalRef(env->GetStaticObjectField(g_HandshakeStatus_class, fid));

  fid = env->GetStaticFieldID(g_HandshakeStatus_class, "NOT_HANDSHAKING", HANDSHAKE_STATUS_FIELD_SIG);
  g_HandshakeStatus_NOT_HANDSHAKING = env->NewGlobalRef(env->GetStaticObjectField(g_HandshakeStatus_class, fid));

  g_SSLEngineResult_constructor_mid = env->GetMethodID(
                                                      g_SSLEngineResult_class,
                                                      "<init>",
                                                      "(Ljavax/net/ssl/SSLEngineResult$Status;Ljavax/net/ssl/SSLEngineResult$HandshakeStatus;II)V");
  NONNULL(g_SSLEngineResult_constructor_mid);

#undef NONNULL
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    openNative
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_openNative
  (JNIEnv *env, jobject, jstring host)
{
  const _TCHAR* hoststr = getString(env, host);
  jint result = (jint) new SSLEngine(hoststr);
  free(const_cast<_TCHAR*>(hoststr));
  return result;
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    closeNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_closeNative
  (JNIEnv *, jobject, jint nativePointer)
{
  if (! nativePointer)
  {
    return;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  delete pEngine;
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    beginNativeHandshake
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_beginNativeHandshake
  (JNIEnv *env, jobject obj, jint nativePointer)
{
  if (! nativePointer)
  {
    return;
  }
  // ハンドシェイクを開始する
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  SECURITY_STATUS ss = pEngine->beginHandshake();
  if (FAILED(ss))
  {
    if (! pEngine->getUseClientMode() && SEC_E_UNKNOWN_CREDENTIALS == ss)
    {
      // 恐らく関連付けられた鍵がエクスポート可能になっていない
      // ToDo: 証明書に関連付けられたキーがエクスポート可能かを調べる
      env->ThrowNew(env->FindClass(SSL_EXCEPTION_CLASS_NAME),
                "The associated key is possibly marked as not exportable");
    }
    else
    {
      throwException(env, SSL_EXCEPTION_CLASS_NAME, ss);
    }
  }
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    closeNativeInbound
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_closeNativeInbound
  (JNIEnv *env, jobject, jint nativePointer)
{
  if (! nativePointer)
  {
    return;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  if (! pEngine->closeInbound())
  {
    env->ThrowNew(env->FindClass(SSL_EXCEPTION_CLASS_NAME), "Inbound closed before receiving close_notify");
  }
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    closeNativeOutbound
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_closeNativeOutbound
  (JNIEnv *, jobject, jint nativePointer)
{
  if (! nativePointer)
  {
    return;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  pEngine->closeOutbound();
}


/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    hasNativeTask
 * Signature: (I)Z
 */
JNIEXPORT jboolean JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_hasNativeTask
  (JNIEnv *env, jobject, jint nativePointer)
{
  if (! nativePointer)
  {
    return JNI_FALSE;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  return pEngine->hasDelegatedTask();
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    executeNativeTask
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_executeNativeTask
  (JNIEnv *env, jobject, jint nativePointer)
{
  if (! nativePointer)
  {
    return;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  if (pEngine->hasDelegatedTask())
  {
    SSLEngineTaskStatus ts = pEngine->executeDelegatedTask();
    if (TASK_OK != ts)
      // SSLHandshakeExceptionを投げる
      {
        throwSSLException(env, pEngine, ts);
      }
  }
  else
  {
    assert(false);
    // 例外を投げる？
  }
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    getNativeHandshakeStatus
 * Signature: (I)Ljavax/net/ssl/SSLEngineResult$HandshakeStatus;
 */
JNIEXPORT jobject JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_getNativeHandshakeStatus
  (JNIEnv *env, jobject, jint nativePointer)
{
  if (! nativePointer)
  {
    return g_HandshakeStatus_NOT_HANDSHAKING;
  }

  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  SSLEngineHandshakeStatus hs = pEngine->getHandshakeStatus();
  jobject result = NULL;
  switch (hs)
  {
  case FINISHED:
    result = g_HandshakeStatus_FINISHED;
    break;

  case NEED_TASK:
    result = g_HandshakeStatus_NEED_TASK;
    break;

  case NEED_UNWRAP:
    result = g_HandshakeStatus_NEED_UNWRAP;
    break;

  case NEED_WRAP:
    result = g_HandshakeStatus_NEED_WRAP;
    break;

  case NOT_HANDSHAKING:
    result = g_HandshakeStatus_NOT_HANDSHAKING;
    break;

  default:
    assert(false);
  }
  return result;
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    getNativeNeedClientAuth
 * Signature: (I)Z
 */
JNIEXPORT jboolean JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_getNativeNeedClientAuth
  (JNIEnv *, jobject, jint nativePointer)
{
  if (! nativePointer)
  {
    return JNI_FALSE;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  return pEngine->getNeedClientAuth();
}


/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    updateNativeSession
 * Signature: (ILcom/mysaifu/jvm/java/security/provider/WCESSLSession;)V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_updateNativeSession
  (JNIEnv *env, jobject, jint nativePointer, jobject session)
{
  if (! nativePointer)
  {
    return;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  jclass clazz = env->GetObjectClass(session);
  
  // Server helloメッセージを設定する
  if (pEngine->getSessionIDLength())
  {
    static jmethodID getSessionID_mid;
    if (! getSessionID_mid)
    {
      getSessionID_mid = env->GetMethodID(clazz, "getId", "()[B");
      assert(getSessionID_mid);
    }

    if (env->CallObjectMethod(session, getSessionID_mid) == NULL)
    {
      jmethodID mid = env->GetMethodID(env->GetObjectClass(session), "setServerHelloData", "(S[BS)V");
      assert(mid);

      // Server hello メッセージを設定する
      jbyteArray sid = env->NewByteArray(pEngine->getSessionIDLength());
      if (sid && pEngine->getSessionIDLength())
      {
        jbyte* tmp = static_cast<jbyte*>(env->GetPrimitiveArrayCritical(sid, NULL));
        memcpy(tmp, pEngine->getSessionID(), pEngine->getSessionIDLength());
        env->ReleasePrimitiveArrayCritical(sid, tmp, 0);
      }
      env->CallVoidMethod(session,
                          mid,
                          static_cast<jshort>(pEngine->getProtocolVersion()),
                          sid,
                          static_cast<jshort>(pEngine->getCipherSuiteCode()));
    }
  }

  // 自分の証明書を設定する
  PCCERT_CHAIN_CONTEXT localChain = pEngine->getLocalCertificateChain();
  if (localChain && localChain->cChain > 0)
  {
    jmethodID hasLocalCertificate_mid = env->GetMethodID(clazz, "hasLocalCertificate", "()Z");
    assert(hasLocalCertificate_mid);

    if (! env->CallBooleanMethod(session, hasLocalCertificate_mid))
    {

      jobjectArray encodedCerts = createEncodedCertificates(env, localChain);
      if (encodedCerts)
      {
        // WCESSLSession.setEncodedLocalCertificates()を呼び、エンコードされた証明書情報を設定する
        jmethodID mid = env->GetMethodID(clazz, "setEncodedLocalCertificates", "([[BII)V");
        assert(mid);

        PCERT_SIMPLE_CHAIN simple_chain = localChain->rgpChain[0];
        env->CallVoidMethod(session, mid, encodedCerts, simple_chain->TrustStatus.dwErrorStatus,
          simple_chain->TrustStatus.dwInfoStatus);
      }
    }
  }

  // 相手側証明書を設定する
  PCCERT_CHAIN_CONTEXT peerChain = pEngine->getPeerCertificateChain();
  if (peerChain && peerChain->cChain > 0)
  {
    jmethodID hasPeerCertificates_mid = env->GetMethodID(clazz, "hasPeerCertificate", "()Z");
    assert(hasPeerCertificates_mid);

    if (! env->CallBooleanMethod(session, hasPeerCertificates_mid))
    {

      jobjectArray encodedCerts = createEncodedCertificates(env, peerChain);
      if (encodedCerts)
      {
        // WCESSLSession.setEncodedPeerCertificates()を呼び、エンコードされた証明書情報を設定する
        jmethodID mid = env->GetMethodID(clazz, "setEncodedPeerCertificates", "([[BII)V");
        assert(mid);

        PCERT_SIMPLE_CHAIN simple_chain = peerChain->rgpChain[0];
        env->CallVoidMethod(session, mid, encodedCerts, simple_chain->TrustStatus.dwErrorStatus,
          simple_chain->TrustStatus.dwInfoStatus);
      }
    }
  }

  // パケットサイズを設定する
  unsigned long packetSize = pEngine->getPacketBufferSize();
  unsigned long appSize = pEngine->getApplicationBufferSize();
  if (packetSize && appSize)
  {
    static jmethodID setPacketSize_mid;
    static jmethodID setAppSize_mid;
    if (! setPacketSize_mid)
    {
      setPacketSize_mid = env->GetMethodID(clazz, "setPacketBufferSize", "(I)V");
    }
    if (! setAppSize_mid)
    {
      setAppSize_mid = env->GetMethodID(clazz, "setApplicationBufferSize", "(I)V");
    }

    env->CallVoidMethod(session, setPacketSize_mid, static_cast<jint>(packetSize));
    env->CallVoidMethod(session, setAppSize_mid, static_cast<jint>(appSize));
  }
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    getNativeUseClientMode
 * Signature: (I)Z
 */
JNIEXPORT jboolean JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_getNativeUseClientMode
  (JNIEnv *, jobject, jint nativePointer)
{
  if (! nativePointer)
  {
    return JNI_FALSE;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  return pEngine->getUseClientMode() ? JNI_TRUE : JNI_FALSE;
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    getNativeWantClientAuth
 * Signature: (I)Z
 */
JNIEXPORT jboolean JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_getNativeWantClientAuth
  (JNIEnv *, jobject, jint nativePointer)
{
  if (! nativePointer)
  {
    return JNI_FALSE;
  }

  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  return pEngine->getWantClientAuth();
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    isNativeInboundDone
 * Signature: (I)Z
 */
JNIEXPORT jboolean JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_isNativeInboundDone
  (JNIEnv *, jobject, jint nativePointer)
{
  if (! nativePointer)
  {
    return JNI_TRUE;
  }

  SSLEngine* pSSLEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  return pSSLEngine->isInboundDone();
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    isNativeOutboundDone
 * Signature: (I)Z
 */
JNIEXPORT jboolean JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_isNativeOutboundDone
  (JNIEnv *, jobject, jint nativePointer)
{
  if (! nativePointer)
  {
    return JNI_TRUE;
  }
  SSLEngine* pSSLEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  return pSSLEngine->isOutboundDone();
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    setNativeEnabledProtocols
 * Signature: (I[Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_setNativeEnabledProtocols
  (JNIEnv *env, jobject, jint nativePointer, jobjectArray protocols)
{
  if (! nativePointer)
  {
    return;
  }

  const jsize array_size = env->GetArrayLength(protocols);
  int flags = 0;
  for (int i = 0; i < array_size; ++i)
  {
    jstring str = static_cast<jstring>(env->GetObjectArrayElement(protocols, i));
    _TCHAR* name = getString(env, str);
    if (_tcscmp(name, PROTOCOL_NAME_SSLV3) == 0)
    {
      flags |= SP_PROT_SSL3;
    }
    else if (_tcscmp(name, PROTOCOL_NAME_TLSV1) == 0)
    {
      flags |= SP_PROT_TLS1;
    }

    free(name);
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  pEngine->setEnabledProtocols(flags);
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    setNativeNeedClientAuth
 * Signature: (IZ)V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_setNativeNeedClientAuth
  (JNIEnv *, jobject, jint nativePointer, jboolean needClientAuth)
{
  if (! nativePointer)
  {
    return;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  pEngine->setNeedClientAuth(needClientAuth ? true : false);
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    setNativeUseClientMode
 * Signature: (IZ)V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_setNativeUseClientMode
  (JNIEnv *, jobject, jint nativePointer, jboolean clientMode)
{
  if (! nativePointer)
  {
    return;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  pEngine->setUseClientMode(clientMode ? true : false);
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    setNativeWantClientAuth
 * Signature: (IZ)V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_setNativeWantClientAuth
  (JNIEnv *, jobject, jint nativePointer, jboolean wantClientAuth)
{
  if (! nativePointer)
  {
    return;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  pEngine->setWantClientAuth(wantClientAuth ? true : false);
}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    wrapNative
 * Signature: (I[Ljava/nio/ByteBuffer;IILjava/nio/ByteBuffer;)Ljavax/net/ssl/SSLEngineResult;
 */
JNIEXPORT jobject JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_wrapNative
  (JNIEnv *env, jobject, jint nativePointer, jobjectArray srcs, jint offset, jint length, jobject dst)
{
  if (! nativePointer)
  {
    return NULL;
  }
  // 現在の実装では、1つの入力バッファしか処理できない
  if (length != 1)
  {
    env->ThrowNew(env->FindClass(ILLEGAL_ARGUMENT_EXCEPTION_NAME), "length!=1");
    return NULL;
  }

  JCL_buffer srcbuff, dstbuff;
  JCL_init_buffer(env, &srcbuff, env->GetObjectArrayElement(srcs, offset));
  JCL_init_buffer(env, &dstbuff, dst);

  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  const SSLEngineResult& engineResult
    = pEngine->wrap(srcbuff.ptr + srcbuff.offset + srcbuff.position,
                    srcbuff.limit - srcbuff.position, 
                    dstbuff.ptr + dstbuff.offset + dstbuff.position,
                    dstbuff.limit - dstbuff.position);
  srcbuff.count = engineResult.bytesConsumed();
  dstbuff.count = engineResult.bytesProduced();

  JCL_release_buffer(env, &srcbuff, env->GetObjectArrayElement(srcs, offset), 0);
  JCL_release_buffer(env, &dstbuff, dst, 0);

  if (FAILED(engineResult.getResult()))
  {
    // エラー発生
    throwException(env, SSL_EXCEPTION_CLASS_NAME, engineResult.getResult());
    return NULL;
  }

  // SSLEngineResultを生成する
  return createSSLEngineResult(env, engineResult);
}


/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    unwrapNative
 * Signature: (ILjava/nio/ByteBuffer;[Ljava/nio/ByteBuffer;II)Ljavax/net/ssl/SSLEngineResult;
 */
JNIEXPORT jobject JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_unwrapNative
  (JNIEnv *env, jobject, jint nativePointer, jobject source, jobjectArray sinks, jint offset, jint length)
{
  if (! nativePointer)
  {
    return NULL;
  }
  // 現在の実装では、1つの入力バッファしか処理できない
  if (length != 1)
  {
    env->ThrowNew(env->FindClass(ILLEGAL_ARGUMENT_EXCEPTION_NAME), "length!=1");
    return NULL;
  }

  JCL_buffer srcbuff, dstbuff;
  JCL_init_buffer(env, &srcbuff, source);
  JCL_init_buffer(env, &dstbuff, env->GetObjectArrayElement(sinks, offset));

  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  const SSLEngineResult& engineResult
    = pEngine->unwrap(srcbuff.ptr + srcbuff.offset + srcbuff.position,
                      srcbuff.limit - srcbuff.position, 
                      dstbuff.ptr + dstbuff.offset + dstbuff.position,
                      dstbuff.limit - dstbuff.position);
  srcbuff.count = engineResult.bytesConsumed();
  dstbuff.count = engineResult.bytesProduced();

  JCL_release_buffer(env, &srcbuff, source, 0);
  JCL_release_buffer(env, &dstbuff, env->GetObjectArrayElement(sinks, offset), 0);

  if (FAILED(engineResult.getResult()))
  {
    // エラー発生
    throwException(env, SSL_EXCEPTION_CLASS_NAME, engineResult.getResult());
    return NULL;
  }
  
  return createSSLEngineResult(env, engineResult);

}

/*
 * Class:     com_mysaifu_jvm_java_security_provider_WCESSLEngine
 * Method:    setNativeLocalCertificate
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_com_mysaifu_jvm_java_security_provider_WCESSLEngine_setNativeLocalCertificate
  (JNIEnv *, jobject, jint nativePointer, jint certContext)
{
  if (! nativePointer)
  {
    return;
  }
  SSLEngine* pEngine = reinterpret_cast<SSLEngine*>(nativePointer);
  PCCERT_CONTEXT cert = reinterpret_cast<PCCERT_CONTEXT>(certContext);
  pEngine->setLocalCertificate(cert);
}
