/*
 * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Created in Samsung Ukraine R&D Center (SRK) under a contract between
 * LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine)
 * and "Samsung Electronics Co", Ltd (Seoul, Republic of Korea)
 *
 * Created on: Dec 13, 2017
 * Author: Kostiantyn Volobuiev <k.volobuyev@samsung.com>
 * Brief: Module that responds for hash operations.
 */

#include "TzwHash.h"

#include "TigerMacros.h"
#include "TzwMemory.h"

#if defined(TZ_MODEL_QCOM)
    #include <qsee_hash.h>
    #include <IxErrno.h>

#elif defined(TZ_MODEL_Kinibi)
    #include "TlApi/TlApiCrypto.h"
#endif

#define HMAC_CHUNK_SIZE (64) // chunk size according RFC 2104

struct __TzwHashOperation {
#if TIGER_TZ_MODEL_BLOWFISH
    TEE_OperationHandle handle;
#elif defined(TZ_MODEL_QCOM)
    qsee_hash_ctx* hashCtx;
#elif defined(TZ_MODEL_Kinibi)
    tlApiCrSession_t pSessionHandle;
#endif
    size_t hashLen;
};

static size_t getHashLen(uint32_t algo) {
    size_t length = 0;
    switch (algo) {
    case TEE_ALG_SHA1:
        length = TEE_ALG_SHA1_DIGEST_LENGTH;
        break;
    case TEE_ALG_SHA256:
        length = TEE_ALG_SHA256_DIGEST_LENGTH;
        break;
    default:
        LOG_E("Unknown hash algorithm.");
        break;
    }
    return length;
}

#ifdef TIGER_TZ_MODEL_QCOM
static QSEE_HASH_ALGO_ET gpToQseeHashAlgo(uint32_t algo) {
    switch (algo) {
    case TEE_ALG_SHA1:
        return QSEE_HASH_SHA1;
        break;
    case TEE_ALG_SHA256:
    default:
        return QSEE_HASH_SHA256;
        break;
    }
}

#elif defined(TIGER_TZ_MODEL_Kinibi)
static tlApiMdAlg_t gpToKinibiHashAlgo(uint32_t algo) {
    switch (algo) {
        case TEE_ALG_SHA1:
            return TLAPI_ALG_SHA1;

        case TEE_ALG_SHA256:
        default:
            return TLAPI_ALG_SHA256;
    }
}
#endif

TzwErrorCode_t tzwAllocateHashOperation(TzwHashOperation_t* pObject, uint32_t algorithm) {

    LOG_FUNC_BEGIN

    if (TEE_HANDLE_NULL == pObject) {
        logTeeError(TEE_ERROR_BAD_PARAMETERS, __func__ );
        return TEE_ERROR_BAD_PARAMETERS;
    }

    // qsee support only SHA1 and SHA256 algorithms
    // to make wrappers identical we don't support other even in TEE
    if (! ((TEE_ALG_SHA1 == algorithm)  || (TEE_ALG_SHA256 == algorithm))) {
        LOG_E("Unsupported hash algorithm!, %d", algorithm);
        return TEE_ERROR_BAD_PARAMETERS;
    }

    TzwErrorCode_t status = TEE_SUCCESS;
    TzwHashOperation_t lHashObj = TEE_HANDLE_NULL;

    do {
        lHashObj = tzwMalloc(sizeof(struct __TzwHashOperation));
        TIGER_CHECK_BUFFER_ALLOCATED_BREAK(lHashObj);
        lHashObj->hashLen = getHashLen(algorithm);

#ifdef TIGER_TZ_MODEL_BLOWFISH
        status = TEE_AllocateOperation(&(lHashObj->handle), algorithm, TEE_MODE_DIGEST, 0);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("TEE_AllocateOperation()");
#elif defined(TIGER_TZ_MODEL_QCOM)

        int ret = qsee_hash_init(gpToQseeHashAlgo(algorithm), &(lHashObj->hashCtx));
        if (ret < E_SUCCESS) {
            LOG_E("Failed to qsee_hash_init()");
            status = TEE_ERROR_GENERIC;
            break;
        }
#elif defined(TIGER_TZ_MODEL_Kinibi)
        tlApiResult_t ret = tlApiMessageDigestInit(&lHashObj->pSessionHandle, gpToKinibiHashAlgo(algorithm));
        if (ret != TLAPI_OK) {
            LOG_E("Failed to tlApiMessageDigestInit(): %d", ret);
            status = TEE_ERROR_GENERIC;
        }
#endif
        *pObject = lHashObj;
        lHashObj = TEE_HANDLE_NULL;
        status = TEE_SUCCESS;
    } while (0);

    if (TEE_SUCCESS != status) {
        tzwFree(lHashObj);
    }

    LOG_FUNC_END

    return status;
}

TzwErrorCode_t tzwHashUpdate(TzwHashOperation_t object, void* chunk, size_t chunkSize) {
    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(TEE_HANDLE_NULL != object);
    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(NULL != chunk);

    LOG_FUNC_BEGIN

    if (0 == chunkSize) {
        return TEE_SUCCESS;
    }

    TzwErrorCode_t status = TEE_SUCCESS;
#if defined(TIGER_TZ_MODEL_BLOWFISH)
    TEE_DigestUpdate(object->handle, chunk, chunkSize);
#elif defined(TIGER_TZ_MODEL_QCOM)
    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(NULL != object->hashCtx);

    int ret = qsee_hash_update(object->hashCtx, chunk, chunkSize);
    if (ret < 0) {
        LOG_E("Failed to qsee_hash_update(): %d", ret);
        status = TEE_ERROR_GENERIC;
    }
#elif defined(TIGER_TZ_MODEL_Kinibi)
    tlApiResult_t ret = tlApiMessageDigestUpdate(object->pSessionHandle, chunk, chunkSize);
    if (ret != TLAPI_OK) {
        LOG_E("Failed to tlApiMessageDigestUpdate(): %d", ret);
        status = TEE_ERROR_GENERIC;
    }
#endif

#ifndef __DEV_DEBUG__
    logByteArrayHex(chunk, chunkSize, "Message Digest");
#endif

    LOG_FUNC_END

    return status;
}

TzwErrorCode_t tzwHashFinalize(TzwHashOperation_t object,
                                 void* chunk, size_t chunkLen,
                                 void* hash, size_t* hashLen) {

    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(TEE_HANDLE_NULL != object);
    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(NULL != hash);
    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(0 != hashLen);
    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(*hashLen == object->hashLen)

    LOG_FUNC_BEGIN

    TzwErrorCode_t status = TEE_SUCCESS;

#ifdef __DEV_DEBUG__
    logByteArrayHex(chunk, chunkLen, "Input data");
#endif

#if defined(TIGER_TZ_MODEL_BLOWFISH)
    status = TEE_DigestDoFinal(object->handle, chunk, chunkLen, hash, hashLen);
#elif defined(TIGER_TZ_MODEL_QCOM)
    if (NULL == object->hashCtx) {
        logTeeError(TEE_ERROR_BAD_PARAMETERS, __func__);
        return TEE_ERROR_BAD_PARAMETERS;
    }

    // do update like in TEE
    if ((NULL != chunk) && (0 != chunkLen)) {
        status = tzwHashUpdate(object, chunk, chunkLen);
        TIGER_CHECK_TEE_STATUS_SUCCESS_RETURN("tzwDigestUpdate()");
    }

    int ret = qsee_hash_final(object->hashCtx, hash, *hashLen);
    if (ret < E_SUCCESS)       {
       LOG_E("Failed to qsee_hash_final()");
       status = TEE_ERROR_GENERIC;
    }
#elif defined(TIGER_TZ_MODEL_Kinibi)
    tlApiResult_t ret = tlApiMessageDigestDoFinal(object->pSessionHandle, chunk, chunkLen, hash, hashLen);
    if( TLAPI_OK != ret){
        LOG_E("Failed to tlApiMessageDigestDoFinal(): %d", ret);
        status = TEE_ERROR_GENERIC;
    }
#endif

#ifdef __DEV_DEBUG__
    logByteArrayHex(hash, *hashLen, "Message Digest");
#endif

    LOG_FUNC_END

    return status;
}

void tzwFreeHashOperation(TzwHashOperation_t object) {
    LOG_FUNC_BEGIN

    if (object != TEE_HANDLE_NULL) {
#if defined(TIGER_TZ_MODEL_BLOWFISH)
        TEE_FreeOperation(object->handle);
#elif defined(TIGER_TZ_MODEL_QCOM)
        if (object->hashCtx != TEE_HANDLE_NULL) {
            int ret = qsee_hash_free_ctx(object->hashCtx);
            if (ret < E_SUCCESS) {
                LOG_E("Failed to qsee_hash_free_ctx(): %d", ret);
            }
        }
#elif defined(TIGER_TZ_MODEL_Kinibi)
     // TODO:tlApiCrAbort system crash
     //   tlApiResult_t ret = tlApiCrAbort(object->pSessionHandle);
     //   if(TLAPI_OK != ret){
     //       LOG_E("Failed to tlApiCrAbort(): %d", ret);
     //   }
#endif
        tzwFree(object);
    }

    LOG_FUNC_END
}

TEE_Result tzwHmac(const void* keyBytes, uint32_t keySize, const uint8_t* tbs, const uint32_t tbsLen, void* hash) {
    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(keyBytes != NULL);
    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(hash != NULL);

    TEE_Result status = TEE_SUCCESS;

#if defined(TIGER_TZ_MODEL_BLOWFISH)
    TEE_OperationHandle operationHandle = TEE_HANDLE_NULL;
    TEE_ObjectHandle keyHandle = TEE_HANDLE_NULL;

    do {
        TEE_Attribute attrs[1];
        uint32_t keySizeInBits = BYTES_TO_BITS(keySize);

        status = TEE_AllocateTransientObject(TEE_TYPE_HMAC_SHA256, keySizeInBits, &keyHandle);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("TEE_AllocateTransientObject()");

        TEE_InitRefAttribute(&attrs[0], TEE_ATTR_SECRET_VALUE, keyBytes, keySize);

        status = TEE_PopulateTransientObject(keyHandle, attrs, 1);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("TEE_PopulateTransientObject()");

        status = TEE_AllocateOperation(&operationHandle, TEE_ALG_HMAC_SHA256, TEE_MODE_MAC, keySizeInBits);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("TEE_AllocateOperation()");

        status = TEE_SetOperationKey(operationHandle, keyHandle);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("TEE_SetOperationKey()");

        TEE_MACInit(operationHandle, NULL, 0);

        int tempTbsLen = tbsLen;
        uint8_t* tempTbs = (uint8_t*) tbs;

        while (tempTbsLen > HMAC_CHUNK_SIZE) {
            TEE_MACUpdate(operationHandle, tempTbs, HMAC_CHUNK_SIZE);
            tempTbsLen -= HMAC_CHUNK_SIZE;
            tempTbs += HMAC_CHUNK_SIZE;
        };

        uint32_t hashLen = HMAC_SHA256_SIZE;
        status = TEE_MACComputeFinal(operationHandle, tempTbs, tempTbsLen, hash, &hashLen);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("TEE_MACComputeFinal()");
    } while(0);

    TEE_FreeOperation(operationHandle);
    TEE_FreeTransientObject(keyHandle);
#elif defined(TIGER_TZ_MODEL_QCOM)
    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(tbs != NULL);
    int ret = qsee_hmac(QSEE_HMAC_SHA256, tbs, tbsLen, keyBytes, keySize, hash);
    if (ret) {
        LOG_E("Failed to generate hmac, qsee code %x", ret);
        status = TEE_ERROR_GENERIC;
    }
#elif defined(TIGER_TZ_MODEL_Kinibi)

#endif

    return status;
}
