/*
 * 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 "TigerMacroses.h"
#include "TzwMemory.h"

#if defined(TIGER_TZ_MODEL_QCOM)
    #include <qsee_hash.h>
    #include <IxErrno.h>
#endif

#define HMAC_CHUNK_SIZE (64) // chunk size according RFC 2104

struct __TzwHashOperation {
#if TIGER_TZ_MODEL_BLOWFISH
    TEE_OperationHandle handle;
#else // defined(TIGER_TZ_MODEL_QCOM_API)
    qsee_hash_ctx* hashCtx;
#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;
}

#if ! TIGER_TZ_MODEL_BLOWFISH
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;
    }
}
#endif

TzwErrorCode_t tzwAllocateHashOperation(TzwHashOperation_t* pObject, uint32_t algorithm) {

    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);

#if TIGER_TZ_MODEL_BLOWFISH
        status = TEE_AllocateOperation(&(lHashObj->handle), algorithm, TEE_MODE_DIGEST, 0);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("TEE_AllocateOperation()");
#else

        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;
        }
#endif
        *pObject = lHashObj;
        lHashObj = TEE_HANDLE_NULL;
        status = TEE_SUCCESS;
    } while (0);

    if (TEE_SUCCESS != status) {
        tzwFree(lHashObj);
    }

    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);

    if (0 == chunkSize) {
        return TEE_SUCCESS;
    }

    TzwErrorCode_t status = TEE_SUCCESS;
#if defined(TIGER_TZ_MODEL_BLOWFISH)
    TEE_DigestUpdate(object->handle, chunk, chunkSize);
#else
    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;
    }
#endif
    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)

    TzwErrorCode_t status = TEE_SUCCESS;

#if TIGER_TZ_MODEL_BLOWFISH
    status = TEE_DigestDoFinal(object->handle, chunk, chunkLen, hash, hashLen);
#else
    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;
    }
#endif
    return status;
}

void tzwFreeHashOperation(TzwHashOperation_t object) {
    if (object != TEE_HANDLE_NULL) {
#if TIGER_TZ_MODEL_BLOWFISH
        TEE_FreeOperation(object->handle);
#else
        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);
            }
        }
#endif
        tzwFree(object);
    }
}

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 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);
#else
    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;
    }
#endif

    return status;
}
