/*
 * Copyright 2002-2006 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * imitations under the License.
 */

#include <stdio.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/buffer.h>
#include <jni.h>
#include "org_apache_security_juice_provider_JDKMessageDigestOpenSSL.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "openSSLNative.h"

extern char bufferOpenSSL[1024];

/*
 * Class:     org_apache_security_juice_provider_JDKMessageDigestOpenSSL
 * Method:    openSSLDigestInit
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_org_apache_security_juice_provider_JDKMessageDigestOpenSSL_openSSLDigestInit
(JNIEnv *env, jclass jobj, jstring digestName) {

    const EVP_MD *digest; 
    EVP_MD_CTX *ctx;
    int ret = 0;
    jclass clazz;

    const char *str = (*env)->GetStringUTFChars(env, digestName, 0);

    digest = EVP_get_digestbyname(str);

    if (!digest) {
        clazz = (*env)->FindClass(env, NO_SUCH_ALGO_EX);
        BIO_snprintf(bufferOpenSSL, sizeof(bufferOpenSSL), "JuICE OpenSSL: digest algorithm not supported: %s", 
                     str);
        (*env)->ReleaseStringUTFChars(env, digestName, str);
        (*env)->ThrowNew(env, clazz, bufferOpenSSL);
        return 0L;
    }
    (*env)->ReleaseStringUTFChars(env, digestName, str);

    ctx = EVP_MD_CTX_create();
    if (ctx == NULL) {
        JUICEerr(F_openSSLDigestInit, ERR_R_MALLOC_FAILURE);
        clazz = (*env)->FindClass(env, OUT_OF_MEMORY_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(OUT_OF_MEMORY_MSG));
        return 0L;
    }
    ret = EVP_DigestInit_ex(ctx, digest, NULL);
    if(ret == 0) {
        EVP_MD_CTX_destroy(ctx);
        clazz = (*env)->FindClass(env, RUNTIME_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: digest initialization failed"));
        return 0L;
    }
    return (unsigned long)ctx;
}

/*
 * Class:     org_apache_security_juice_provider_JDKMessageDigestOpenSSL
 * Method:    openSSLDigestUpdate
 * Signature: (J[BII)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JDKMessageDigestOpenSSL_openSSLDigestUpdate
(JNIEnv * env, jclass jobj, jlong handle, jbyteArray data, jint offset, jint length) {

    jbyte* inBytes;
    jclass clazz;

#if SIZEOF_INT_P == 4
    EVP_MD_CTX *ctx = (EVP_MD_CTX *)((int)(handle & 0xffffffff));
#else
    EVP_MD_CTX *ctx = (EVP_MD_CTX *)handle;
#endif
    if (ctx == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }

    inBytes = (*env)->GetByteArrayElements(env, data, 0);
    if (inBytes == NULL) {
        return 0;
    }
    EVP_DigestUpdate(ctx, inBytes+offset, length);
    (*env)->ReleaseByteArrayElements(env, data, inBytes, 0);
    return length;
}

/*
 * Class:     org_apache_security_juice_provider_JDKMessageDigestOpenSSL
 * Method:    openSSLDigestFinal
 * Signature: (J[B)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JDKMessageDigestOpenSSL_openSSLDigestFinal
(JNIEnv * env, jclass jobj, jlong handle, jbyteArray hashOut) {

    jbyte* outBytes;
    jclass clazz;

#if SIZEOF_INT_P == 4
    EVP_MD_CTX *ctx = (EVP_MD_CTX *)((int)(handle & 0xffffffff));
#else
    EVP_MD_CTX *ctx = (EVP_MD_CTX *)handle;
#endif

    if (ctx == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    outBytes = (*env)->GetByteArrayElements(env, hashOut, 0);
    if (outBytes == NULL) {
        return 0;
    }
    EVP_DigestFinal_ex(ctx, outBytes, NULL);
    (*env)->ReleaseByteArrayElements(env, hashOut, outBytes, 0);
    return EVP_MD_CTX_size(ctx);
}

/*
 * Class:     org_apache_security_juice_provider_JDKMessageDigestOpenSSL
 * Method:    openSSLDigestDestroy
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_org_apache_security_juice_provider_JDKMessageDigestOpenSSL_openSSLDigestDestroy
(JNIEnv * env, jclass jobj, jlong handle) {

    jclass clazz;

#if SIZEOF_INT_P == 4
    EVP_MD_CTX *ctx = (EVP_MD_CTX *)((int)(handle & 0xffffffff));
#else
    EVP_MD_CTX *ctx = (EVP_MD_CTX *)handle;
#endif
    if (ctx == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    EVP_MD_CTX_destroy(ctx);
}

/*
 * Class:     org_apache_security_juice_provider_JDKMessageDigestOpenSSL
 * Method:    openSSLDigestCopy
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_org_apache_security_juice_provider_JDKMessageDigestOpenSSL_openSSLDigestCopy
(JNIEnv * env, jclass jobj, jlong handle) {

#if SIZEOF_INT_P == 4
    EVP_MD_CTX *ctx = (EVP_MD_CTX *)((int)(handle & 0xffffffff));
#else
    EVP_MD_CTX *ctx = (EVP_MD_CTX *)handle;
#endif
    jclass clazz;
    EVP_MD_CTX *ctxNew;

    if (ctx == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    ctxNew = EVP_MD_CTX_create();
    if (!EVP_DigestInit_ex(ctxNew, EVP_MD_CTX_md(ctx), NULL)) {
        EVP_MD_CTX_destroy(ctxNew);
        clazz = (*env)->FindClass(env, CLONE_NOT_SUPP_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: copy digest initialization failed"));
        return 0;
    }

    if (!EVP_MD_CTX_copy_ex(ctxNew, ctx)) {
        EVP_MD_CTX_destroy(ctxNew);
        clazz = (*env)->FindClass(env, CLONE_NOT_SUPP_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL("JuiCE OpenSSL: copy digest failed"));
        return 0;
    }
    return (long)ctx;
}

/*
 * Class:     org_apache_security_juice_provider_JDKMessageDigestOpenSSL
 * Method:    openSSLDigestGetType
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_org_apache_security_juice_provider_JDKMessageDigestOpenSSL_openSSLDigestGetType
(JNIEnv *env, jclass jobj, jlong handle) {

    jclass clazz;
 
#if SIZEOF_INT_P == 4
    EVP_MD_CTX *ctx = (EVP_MD_CTX *)((int)(handle & 0xffffffff));
#else
    EVP_MD_CTX *ctx = (EVP_MD_CTX *)handle;
#endif
    if (ctx == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(KEY_NULL_MSG));
    }
    return EVP_MD_CTX_type(ctx);
}

