/*
 * Class:     org_opensc_pkcs11_wrap_PKCS11Session
 * Method:    loginNative
 * Signature: (JJJI[B)V
 */
JNIEXPORT void JNICALL JNIX_FUNC_NAME(Java_org_opensc_pkcs11_wrap_PKCS11Session_loginNative)
  (JNIEnv *env, jobject jsession, jlong mh, jlong shandle, jlong hsession, jint type, jbyteArray jpin)
{
  int rv;
  CK_UTF8CHAR_PTR pin=0;
  CK_ULONG pin_len=0;
  pkcs11_slot_t *slot;
  pkcs11_module_t *mod =  pkcs11_module_from_jhandle(env,mh);
  if (!mod) return;

  slot = pkcs11_slot_from_jhandle(env,shandle);
  if (!slot) return;

  if (jpin)
    {
      allocaCArrayFromJByteArray(pin,pin_len,env,jpin);
    }

  rv = mod->method->C_Login(hsession,type,pin,pin_len);
  if (rv != CKR_OK)
    {
      jnixThrowExceptionI(env,"org/opensc/pkcs11/wrap/PKCS11Exception",rv,
                         "C_Login for PKCS11 slot %d failed.",
                         (int)slot->id);
      return;
    }
}
/*
 * Class:     org_opensc_pkcs11_wrap_PKCS11Object
 * Method:    createObjectNative
 * Signature: (JJJ[Lorg/opensc/pkcs11/wrap/PKCS11Attribute;)J
 */
JNIEXPORT jlong JNICALL JNIX_FUNC_NAME(Java_org_opensc_pkcs11_wrap_PKCS11Object_createObjectNative)
  (JNIEnv *env, jclass jp11obj, jlong mh, jlong shandle, jlong hsession, jobjectArray attrs)
{
  int rv;
  CK_ULONG i;
  CK_ULONG ulAttributeCount;
  CK_ATTRIBUTE_PTR pAttributes;
  CK_OBJECT_HANDLE hObject;
  jclass clazz;
  jmethodID getKindID,getDataID;
  pkcs11_slot_t *slot;
  pkcs11_module_t *mod =  pkcs11_module_from_jhandle(env,mh);
  if (!mod) return 0;
  
  slot = pkcs11_slot_from_jhandle(env,shandle);
  if (!slot) return 0;

  clazz = (*env)->FindClass(env,"org/opensc/pkcs11/wrap/PKCS11Attribute");

  if (!clazz) return 0;

  getKindID = (*env)->GetMethodID(env,clazz,"getKind","()I");

  if (!getKindID) return 0;

   getDataID = (*env)->GetMethodID(env,clazz,"getData","()[B");

  if (!getDataID) return 0;

  ulAttributeCount = (*env)->GetArrayLength(env,attrs);
  pAttributes = alloca(ulAttributeCount * sizeof(CK_ATTRIBUTE));

  for (i=0;i<ulAttributeCount;++i)
    {
      jbyteArray data;
      jobject jattr = (*env)->GetObjectArrayElement(env,attrs,i);
      if (!jattr) return 0;

      pAttributes[i].type = (*env)->CallIntMethod(env,jattr,getKindID);

      data = (jbyteArray)(*env)->CallObjectMethod(env,jattr,getDataID);

      allocaCArrayFromJByteArray(pAttributes[i].pValue,pAttributes[i].ulValueLen,env,data);
    }


  rv = mod->method->C_CreateObject(hsession, pAttributes, ulAttributeCount, &hObject);

  if (rv  != CKR_OK)
    {
      jnixThrowExceptionI(env,"org/opensc/pkcs11/wrap/PKCS11Exception",rv,
                          "C_CreateObject failed.");
      return 0;
    }

  return hObject;
}
/*
 * Class:     org_opensc_pkcs11_wrap_PKCS11Session
 * Method:    verifyFinalNative
 * Signature: (JJJ[B)Z
 */
JNIEXPORT jboolean JNICALL JNIX_FUNC_NAME(Java_org_opensc_pkcs11_wrap_PKCS11Session_verifyFinalNative)
  (JNIEnv *env, jclass cls, jlong mh, jlong shandle, jlong hsession, jbyteArray data)
{
  int rv;
  CK_BYTE_PTR pSignature;
  CK_ULONG    ulSignatureLen;
  pkcs11_slot_t *slot;
  pkcs11_module_t *mod =  pkcs11_module_from_jhandle(env,mh);
  if (!mod) return JNI_FALSE;

  slot = pkcs11_slot_from_jhandle(env,shandle);
  if (!slot) return JNI_FALSE;

  if (data == NULL)
    {
      jnixThrowException(env,"org/opensc/pkcs11/wrap/PKCS11Exception",
                         "NULL input data.");
      return JNI_FALSE;
    }

  allocaCArrayFromJByteArray(pSignature,ulSignatureLen,env,data);

  rv = mod->method->C_VerifyFinal(hsession,pSignature,ulSignatureLen);

  switch (rv)
    {
    case CKR_SIGNATURE_INVALID:
      return JNI_FALSE;

    case CKR_OK:
      return JNI_TRUE;

    default:
      jnixThrowExceptionI(env,"org/opensc/pkcs11/wrap/PKCS11Exception",rv,
                          "C_VerifyFinal failed for slot %d.",
                          (int)slot->id);
      return JNI_FALSE;
    }
}
/*
 * Class:     org_opensc_pkcs11_wrap_PKCS11Object
 * Method:    enumObjectsNative
 * Signature: (JJJ[Lorg/opensc/pkcs11/wrap/PKCS11Attribute;)[J
 */
JNIEXPORT jlongArray JNICALL JNIX_FUNC_NAME(Java_org_opensc_pkcs11_wrap_PKCS11Object_enumObjectsNative)
  (JNIEnv *env, jclass jp11obj, jlong mh, jlong shandle, jlong hsession, jobjectArray attrs)
{
  jclass clazz;
  jmethodID getKindID,getDataID;
  CK_ULONG ulAttributeCount;
  CK_ATTRIBUTE_PTR pAttributes;
  CK_ULONG i,count;
  int nobjs,rv;
  CK_ULONG obj_ids[ENUM_HANDLES_BLOCK_SZ];
  size_t ret_obj_ids_sz;
  jlong *ret_obj_ids;
  jlongArray ret;
  pkcs11_slot_t *slot;

  pkcs11_module_t *mod =  pkcs11_module_from_jhandle(env,mh);
  if (!mod) return 0;

  slot = pkcs11_slot_from_jhandle(env,shandle);
  if (!slot) return 0;

  clazz = (*env)->FindClass(env,"org/opensc/pkcs11/wrap/PKCS11Attribute");

  if (!clazz) return 0;

  getKindID = (*env)->GetMethodID(env,clazz,"getKind","()I");

  if (!getKindID) return 0;

  getDataID = (*env)->GetMethodID(env,clazz,"getData","()[B");

  if (!getDataID) return 0;

  ulAttributeCount = (*env)->GetArrayLength(env,attrs);
  pAttributes = alloca(ulAttributeCount * sizeof(CK_ATTRIBUTE));

  for (i=0;i<ulAttributeCount;++i)
    {
      jbyteArray data;
      jobject jattr = (*env)->GetObjectArrayElement(env,attrs,i);
      if (!jattr) return 0;

      pAttributes[i].type = (*env)->CallIntMethod(env,jattr,getKindID);

      data = (jbyteArray)(*env)->CallObjectMethod(env,jattr,getDataID);

      allocaCArrayFromJByteArray(pAttributes[i].pValue,pAttributes[i].ulValueLen,env,data);
    }

  ret_obj_ids_sz = ENUM_HANDLES_BLOCK_SZ;
  nobjs = 0;
  ret_obj_ids=(jlong*)malloc(ret_obj_ids_sz * sizeof(jlong));

  if (!ret_obj_ids)
    {
      jnixThrowException(env,"org/opensc/pkcs11/wrap/PKCS11Exception",
                         "Out of memory during object enumeration for slot number %d.",
                         (int)slot->id);
      goto failed;
    }

  rv = mod->method->C_FindObjectsInit(hsession,pAttributes,ulAttributeCount);

  if (rv  != CKR_OK)
    {
      jnixThrowExceptionI(env,"org/opensc/pkcs11/wrap/PKCS11Exception",rv,
                          "C_FindObjectsInit failed for slot number %d.",
                          (int)slot->id);
      goto failed;
    }

  count = 0;

  rv = mod->method->C_FindObjects(hsession,obj_ids, ENUM_HANDLES_BLOCK_SZ, &count);

  if (rv  != CKR_OK)
    {
      jnixThrowExceptionI(env,"org/opensc/pkcs11/wrap/PKCS11Exception",rv,
                          "C_FindObjects failed for slot number %d.",
                          (int)slot->id);
      goto failed;
    }

  for (i=0; i<count ;++i)
    {
      jlong id = (jlong)obj_ids[i];
      ret_obj_ids[nobjs] = id;
      ++nobjs;
    }

  while (count == ENUM_HANDLES_BLOCK_SZ)
    {
      jlong *new_obj_ids;
      ret_obj_ids_sz += ENUM_HANDLES_BLOCK_SZ;

      new_obj_ids=(jlong*)realloc(ret_obj_ids,ret_obj_ids_sz * sizeof(jlong));
     
      if (!new_obj_ids)
        {
          jnixThrowException(env,"org/opensc/pkcs11/wrap/PKCS11Exception",
                             "Out of memory during object enumeration for slot number %d.",
                             (int)slot->id);
          goto failed;
        }

      ret_obj_ids=new_obj_ids;

      rv = mod->method->C_FindObjects(hsession,obj_ids,ENUM_HANDLES_BLOCK_SZ, &count);
      
      if (rv  != CKR_OK)
        {
          jnixThrowExceptionI(env,"org/opensc/pkcs11/wrap/PKCS11Exception",rv,
                              "C_FindObjects failed for slot number %d.",
                              (int)slot->id);
          goto failed;
        }

      for (i=0; i<count ;++i)
        {
          jlong id = (jlong)obj_ids[i];
          ret_obj_ids[nobjs] = id;
          ++nobjs;
        }
    }

  rv = mod->method->C_FindObjectsFinal(hsession);
  if (rv  != CKR_OK)
    {
      jnixThrowExceptionI(env,"org/opensc/pkcs11/wrap/PKCS11Exception",rv,
                          "C_FindObjectsFinal failed for slot number %d.",
                          (int)slot->id);
      goto failed;
    }

  /* OK, akc it into JAVA's realm. */
  ret = (*env)->NewLongArray(env,nobjs);
  (*env)->SetLongArrayRegion(env,ret,0,nobjs,ret_obj_ids);

  free(ret_obj_ids);
  return ret;

 failed:
  free(ret_obj_ids);
  return 0;
}