/*
 * 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/hmac.h>
#include <openssl/buffer.h>
#include <jni.h>
#include "org_apache_security_juice_provider_JCEHMacOpenSSL.h"

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

#include "openSSLNative.h"

extern char bufferOpenSSL[1024];

/*
 * Class:     org_apache_security_juice_provider_JCEHMacOpenSSLOpenSSL
 * Method:    openSSLDigestInit
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_org_apache_security_juice_provider_JCEHMacOpenSSL_openSSLHMacInit
(JNIEnv *env, jclass jobj, jstring digestName, jbyteArray key, jint keylen) {

    const EVP_MD *digest;
    HMAC_CTX *ctx;
    int ret = 0;
    jbyte *keyBytes;
    jclass clazz;

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

    digest = EVP_get_digestbyname(str);

    if(!digest) {
        clazz = (*env)->FindClass(env, RUNTIME_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 = malloc(sizeof (HMAC_CTX));
    if (ctx == NULL) {
	JUICEerr(F_openSSLHMacInit, ERR_R_MALLOC_FAILURE);
	clazz = (*env)->FindClass(env, OUT_OF_MEMORY_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(OUT_OF_MEMORY_MSG));
	return 0L;
    }

    HMAC_CTX_init(ctx);

    // get pointer to key bytes, if NULL "OutOfMemory"
    keyBytes = (*env)->GetByteArrayElements(env, key, 0);
    if (keyBytes == NULL) {
	return 0;
    }
    HMAC_Init_ex(ctx, keyBytes, keylen, digest, NULL);
    (*env)->ReleaseByteArrayElements(env, key, keyBytes, 0);

    return (unsigned long)ctx;
}

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

    jbyte* inBytes;
    int ret = 0;
    jclass clazz;

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

    if (ctx == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(CONTEXT_NULL_MSG));
    }
    inBytes = (*env)->GetByteArrayElements(env, data, 0);
    if (inBytes == NULL) {
	return 0;
    }
    HMAC_Update(ctx, inBytes+offset, length);
    (*env)->ReleaseByteArrayElements(env, data, inBytes, 0);

    return length;
}

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

    jbyte* outBytes;
    unsigned int len;
    jclass clazz;

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

    if (ctx == NULL) {
        clazz = (*env)->FindClass(env, ILLEGAL_ARGUMENT_EX);
        (*env)->ThrowNew(env, clazz, getErrorStringOpenSSL(CONTEXT_NULL_MSG));
    }
    outBytes = (*env)->GetByteArrayElements(env, hashOut, 0);
    if (outBytes == NULL) {
	return 0;
    }
    HMAC_Final(ctx, outBytes, &len);
    (*env)->ReleaseByteArrayElements(env, hashOut, outBytes, 0);

    return len;
}

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

    jclass clazz;

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


