/*
 * 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: June 15, 2017
 * Author: Oleksii Kachkan <o.kachkan@samsung.com>
 * Brief: Module that responds for communication auth module(fingerprint Ta).
 */

#include "ta_logger.h"

#include "TzwAuth.h"
#include "TzwMacro.h"
#include "TzwMemory.h"
#include "TzwString.h"

#include <inttypes.h>

// Blowfish includes
#if defined(TZ_MODEL_BLOWFISH)
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <errno.h>
#else // QSEE includes
    #include <biometric_result.h>
    #define MAX_TA_APP_NAME_SIZE (32)
#endif

static int32_t convertBigEndianToLittle(int32_t value);

const char SECURE_FP_TA_NAME[12]    = "securefp";
const char DUAL_FP_TA_NAME[12]      = "dualfp";

#if defined(TZ_MODEL_QCOM)

TEE_Result getLatestAuthResult(uint8_t* realFid, uint32_t* realFidSize,
                               uint32_t* fpIndex, uint64_t* challenge) {
    LOG_FUNC_BEGIN
    LOG_PERF_BEGIN

    TA_ASSERT(realFid != NULL);
    TA_ASSERT(realFidSize != NULL);
    TA_ASSERT(fpIndex != NULL);
    TA_ASSERT(challenge != NULL);

    bio_buffer extentionData = {NULL, 0};
    bio_buffer authenticatorTaName = {NULL, 0};
    TEE_Result status = TEE_ERROR_ACCESS_DENIED;
    do {
        extentionData.data = tzwMalloc(MAX_BIO_EXT_DATA_SIZE);
        CHECK_BUFFER_ALLOCATED_BREAK(extentionData.data);
        extentionData.size = MAX_BIO_EXT_DATA_SIZE;

        authenticatorTaName.data = tzwMalloc(MAX_TA_APP_NAME_SIZE);
        CHECK_BUFFER_ALLOCATED_BREAK(authenticatorTaName.data);
        authenticatorTaName.size = MAX_TA_APP_NAME_SIZE;

        uint64_t timeCounter = 0;
        bio_result bioRes = {BIO_FINGERPRINT_MATCHING, false, BIO_NA, BIO_NA, BIO_NA};
        BIO_ERROR_CODE bioStatus = bio_get_auth_result(&bioRes, &timeCounter,
                &extentionData, &authenticatorTaName);
        if (BIO_NO_RESULT == bioStatus) {
            // authentication have not happened yet: return TEE_ERROR_ACCESS_DENIED status
            LOG_I("No recent authentications.");
            break;
        }
        CHECK_CONDITION_BREAK(BIO_NO_ERROR == bioStatus); // unexpected error

        LOG_D("Authenticated: %d", bioRes.result);
        LOG_D("Time counter: %"PRIu64"", timeCounter);
        LOG_D("Fp index    : %"PRIu64"", bioRes.user_entity_id);
        LOG_D("Challenge   : %"PRIu64"", bioRes.transaction_id);

        if (true != bioRes.result) {
            // authentication failed: return TEE_ERROR_ACCESS_DENIED status
            LOG_I("Authentication did not pass.");
            break;
        }
        if (BIO_FINGERPRINT_MATCHING != bioRes.method) {
            LOG_I("Inappropriate auth method.");
            break;
        }
        // Check that BIO-result from dualfp
        if (0 != strncmp((const char*) authenticatorTaName.data,SECURE_FP_TA_NAME, strlen(SECURE_FP_TA_NAME))
                && 0 != strncmp((const char*) authenticatorTaName.data,DUAL_FP_TA_NAME, strlen(DUAL_FP_TA_NAME))) {
            LOG_I("Auth result not from devoted TA: %s or %s", SECURE_FP_TA_NAME, DUAL_FP_TA_NAME);
            break;
        }

        if ((bioRes.user_entity_id != BIO_NA) && (extentionData.size > 0)) {
            // get fp index
            TA_ASSERT(bioRes.user_entity_id <= UINT32_MAX);
            *fpIndex = (uint32_t) bioRes.user_entity_id;

            // get challenge
            *challenge = bioRes.transaction_id;

            // TODO(v.kopp): this is needed only in debug mode
            // First 4 bytes contains big endian int32_t which means version
            // version 2 supports:
            //    uint64 user_entity_id = fid_index
            //    uint64 transaction_id = challenge_id
            // on bio_result
            int32_t version = 0;
            tzwMemMove(&version, extentionData.data, sizeof(int32_t));
            version = convertBigEndianToLittle(version);
            LOG_D("Bio protocol version: %"PRId32"", version);

            // next 32-bytes contains real FID of authorisated FP.
            tzwMemMove(realFid, extentionData.data + sizeof(int32_t), extentionData.size - sizeof(int32_t));
            *realFidSize = extentionData.size - (uint32_t) sizeof(int32_t);

            status = TEE_SUCCESS;
        }
    } while (0);

    tzwFree(extentionData.data);
    tzwFree(authenticatorTaName.data);

    LOG_FUNC_END;
    LOG_PERF_END
    return status;
}

#else

#define BIO_NA    0xFFFFFFFF
#define BIO_AUTH_METHOD_FINGERPRINT_MATCHING 0x01
#define BIO_AUTH_METHOD_IRIS_MATCHING        0x02
#define BIO_AUTH_METHOD_AUTH_METHOD_NUMBER   0x03


#ifndef __AUTH_DRIVER_V4__
#define BIO_AUTH_DRV_NAME ("dev://bio_auth_driver")
#else
#define BIO_AUTH_DRV_NAME ("/dev/bio_auth_driver")
#endif

const TEEC_UUID TL_BIOAUTH_UUID_SECUREFP = { // seucrefp.
    0x0, 0x0, 0x0, {0x00, 0x00, 0x46, 0x49, 0x4e, 0x47, 0x45, 0x02}
};

const TEEC_UUID TL_BIOAUTH_UUID_DUALFP = { // dualfp.
    0x0, 0x0, 0x0, {0x00, 0x00, 0x46, 0x49, 0x4e, 0x47, 0x45, 0x52}
};

typedef struct {
    TEE_Time time;
    TEE_UUID uuid;
} __attribute__((aligned(1), packed)) bio_extra_t, *bio_extra_p;

typedef struct {
    uint8_t method;
    bool result;
    uint64_t user_id;
    uint64_t user_entity_id;
    uint64_t transaction_id;
} __attribute__((aligned(1), packed)) bio_result_t, *bio_result_p;

typedef struct {
    uint8_t data[512]; //FID (36 Bytes are valid)
    uint32_t size;
} __attribute__((aligned(1), packed)) bio_buffer_t, *bio_buffer_p;

typedef union {
    struct {
        bio_result_t bioResult;
        bio_buffer_t bioBuffer;
        bio_extra_t  bioExtra;
    };
    uint8_t buffer[0];
} __attribute__((aligned(1), packed)) bio_package_t, *bio_package_p;

TEE_Result getLatestAuthResult(uint8_t* realFid, uint32_t* realFidSize,
                                uint32_t* fpIndex, uint64_t* challenge) {
    LOG_FUNC_BEGIN
    LOG_PERF_BEGIN

    TEE_Result status = TEE_SUCCESS;
    bio_package_t bio_package;
    bio_result_p bioRes = &bio_package.bioResult;
    bio_buffer_p extentionData = &bio_package.bioBuffer;
    bio_extra_p extraData = &bio_package.bioExtra;

    int ret = 0;
    int drv_fd = open(BIO_AUTH_DRV_NAME, O_RDWR, 0);
    if (drv_fd < 0) {
        LOG_E("open error, error message = %s", strerror(errno));
        return TEE_ERROR_GENERIC;
    }

    ret = read(drv_fd, (uint8_t*)bio_package.buffer, sizeof(bio_package_t));
    if (ret < 0) {
        LOG_E("read error, error message = %s", strerror(errno));
        status = TEE_ERROR_GENERIC;
    }

    ret = close(drv_fd);
    if (ret < 0) {
        LOG_E("close error, error message = %s", strerror(errno));
        status = TEE_ERROR_GENERIC;
    }

    if(status != TEE_SUCCESS){
        LOG_E("getLatestAuthResult failed: 0x%08x", status);
        return status;
    }

#ifdef __DEV_DEBUG__
    uint32_t auth_size = sizeof(bio_package_t);
    logByteArrayHex(bio_package.buffer, auth_size, "bio auth data");
#endif

    do{
        LOG_D("Result of BIO: 0x%x", bioRes->result);
        CHECK_CONDITION_BREAK(TRUE == bioRes->result); // unexpected error

        uint64_t timeCounter = extraData->time.seconds * 1000 + extraData->time.millis;
        LOG_I("Authenticate Result: %s", bioRes->result == 1 ? "PASS":"FAIL");

#ifdef __DEV_DEBUG__
        LOG_D("Time counter: %"PRIu64"", timeCounter);
        LOG_D("Fp index    : %"PRIu64"", bioRes->user_entity_id);
        LOG_D("Challenge   : %"PRIu64"", bioRes->transaction_id);
#endif

        if(true != bioRes->result) {
            // authentication failed: return TEE_ERROR_ACCESS_DENIED status
            LOG_I("Authentication did not pass.");
            break;
        }
        if (BIO_AUTH_METHOD_FINGERPRINT_MATCHING!= bioRes->method) {
            LOG_I("Inappropriate auth method.");
            break;
        }

        TEE_UUID *fpTaUuid = &extraData->uuid;
#ifdef __DEV_DEBUG__
        char uuidStr[48];
        uuid_unparse(fpTaUuid, uuidStr);
        LOG_D("fpTA uuid: %s", uuidStr);
        uuid_unparse(&TL_BIOAUTH_UUID_DUALFP, uuidStr);
        LOG_D("dual fp uuid: %s", uuidStr);
        uuid_unparse(&TL_BIOAUTH_UUID_SECUREFP, uuidStr);
        LOG_D("secure fp uuid: %s", uuidStr);
        logByteArrayHex((uint8_t*)fpTaUuid, sizeof(TEEC_UUID), "fpTA");
        logByteArrayHex((uint8_t*)&TL_BIOAUTH_UUID_DUALFP, sizeof(TEEC_UUID), "dual fp");
        logByteArrayHex((uint8_t*)&TL_BIOAUTH_UUID_SECUREFP, sizeof(TEEC_UUID), "secure fp");
#endif
        if((0 != memcmp((uint8_t*)fpTaUuid, (uint8_t*)(&TL_BIOAUTH_UUID_DUALFP), sizeof(TEE_UUID)))
                && ( 0 != memcmp((uint8_t*)fpTaUuid, (uint8_t*)(&TL_BIOAUTH_UUID_SECUREFP), sizeof(TEE_UUID)))){
            LOG_E("The AUTH TA is not defined");
            break;
        }
        // FP extention data layout
        //|<---4B--->|<--------- 32B ------------->|
        //+----------------------------------------+
        //|  VERSION |           FID DATA          |
        //+----------------------------------------+
        // force assignment to 36
        extentionData->size = 36;

        // check valid time
        TEE_Time local_tee_time;
        TEE_GetSystemTime(&local_tee_time);
        uint64_t curr_time = local_tee_time.seconds * 1000 + local_tee_time.millis;

        if( curr_time > timeCounter + MAX_BIO_AUTH_VALID_TIME_IN_SECOND * 1000){
            LOG_E("FP auth data is invalide due to timeout(%"PRIu64")", (curr_time - timeCounter)/1000);
#ifndef __DEV_DEBUG__
            return TEE_ERROR_TIMEOUT;
#endif
        }

#ifdef __DEV_DEBUG__
        LOG_D("BIO_NA: %d", BIO_NA);
        LOG_D("current tee time: %d-%d(%"PRIu64")", local_tee_time.seconds, local_tee_time.millis,curr_time);
        LOG_D("extentionData: %"PRIu32"", extentionData->size);
        logByteArrayHex((uint8_t*)extentionData->data, extentionData->size, "extention data from FP");
#endif

        if ((bioRes->user_entity_id != BIO_NA) && (extentionData->size > 0)) {
            // get fp index
            TA_ASSERT(bioRes->user_entity_id <= UINT32_MAX);
            *fpIndex = (uint32_t) bioRes->user_entity_id;
            LOG_I_AM_HERE

            // get challenge
            *challenge = bioRes->transaction_id;

            int32_t version = 0;
            tzwMemMove(&version, extentionData->data, sizeof(int32_t));
            version = convertBigEndianToLittle(version);
            LOG_D("Bio protocol version: %"PRId32"", version);

            // next 32-bytes contains real FID of authorisated FP.
            uint32_t off = sizeof(uint32_t);
            tzwMemMove(realFid, (uint8_t*)(extentionData->data + off), extentionData->size - off );
            *realFidSize = extentionData->size - off;

            status = TEE_SUCCESS;
        }
    } while(0);

    LOG_FUNC_END
    LOG_PERF_END

    return status;
}

#endif

int32_t convertBigEndianToLittle(int32_t value) {
    int32_t b0 = 0;
    int32_t b1 = 0;
    int32_t b2 = 0;
    int32_t b3 = 0;
    int32_t res = 0;

    b0 = (value & 0x000000ff) << 24;
    b1 = (value & 0x0000ff00) << 8;
    b2 = (value & 0x00ff0000) >> 8;
    b3 = (value & 0xff000000) >> 24;
    res = b0 | b1 | b2 | b3;

    return res;
}
