/*
 * 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 "TzwAuth.h"

#include "TigerLogging.h"
#include "TigerMacroses.h"
#include "TzwMemory.h"
#include "TzwString.h"

#include <inttypes.h>

// Blowfish includes
#if defined(TIGER_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(TIGER_TZ_MODEL_QCOM)

TEE_Result getLatestAuthResult(uint8_t* realFid, uint32_t* realFidSize,
                               uint32_t* fpIndex, uint64_t* challenge) {
    LOG_FUNC_BEGIN;

    TIGER_ASSERT(realFid != NULL);
    TIGER_ASSERT(realFidSize != NULL);
    TIGER_ASSERT(fpIndex != NULL);
    TIGER_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);
        TIGER_CHECK_BUFFER_ALLOCATED_BREAK(extentionData.data);
        extentionData.size = MAX_BIO_EXT_DATA_SIZE;

        authenticatorTaName.data = tzwMalloc(MAX_TA_APP_NAME_SIZE);
        TIGER_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;
        }
        TIGER_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
            TIGER_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;
    return status;
}

#else

#define DRV_NAME ("dev://gatekeeper_driver")
typedef enum {
    CMD_IOCTL_VERIFY_AUTH_TOCKEN,
    CMD_IOCTL_GET_HMAC_KEY,
    CMD_IOCTL_MAX
} cmd_ioctl_t;

typedef enum {
    VR_DEFAULT,
    VR_SUCCESS,
    VR_FAIL,
    VR_MAX,
} verify_result_t;

typedef struct __attribute__((__packed__)) {
    uint8_t hmac_key[AUTH_KEY_LENGTH];
    verify_result_t result;
} verify_auth_token_cmd_t;


TEE_Result setUpAuthKey(const uint8_t* wrappedKey, const uint32_t wrappedKeySize,
                        uint8_t* authKey, const uint32_t authKeySize) {
    LOG_D("Blowfish %s", __FUNCTION__);
    S_VAR_NOT_USED(wrappedKey);
    S_VAR_NOT_USED(wrappedKeySize);
    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(authKey != NULL);
    TIGER_CHECK_FUNCTION_ARGUMENT_RETURN(AUTH_KEY_LENGTH == authKeySize)

    tzwMemFill(authKey, 0, authKeySize);

    int drvFd = open(DRV_NAME, O_RDWR, 0);
    if (drvFd < 0) {
        LOG_E("Failed to connect to gatekeeper, error: %d", errno);
        return TEE_ERROR_GENERIC;
    }
    verify_auth_token_cmd_t cmd;
    int ioctlRes = ioctl(drvFd, CMD_IOCTL_GET_HMAC_KEY, (unsigned long) &cmd);

    int closeRes = close(drvFd);
    if (closeRes < 0) {
        LOG_E("Failed to close Gatekeeper service, error: %d", errno);
        return  TEE_ERROR_GENERIC;
    }

    if (ioctlRes < 0) {
        LOG_E("Obtain key from Gatekeeper service failed, error: %d", errno);
        return TEE_ERROR_GENERIC;
    }

    tzwMemMove(authKey, (const void*) (cmd.hmac_key), authKeySize);

    LOG_FUNC_END;
    return TEE_SUCCESS;
}
TEE_Result getLatestAuthResult(uint8_t* realFid, uint32_t* realFidSize,
                               uint32_t* fpIndex, uint64_t* challenge) {
    S_VAR_NOT_USED(realFid);
    S_VAR_NOT_USED(realFidSize);
    S_VAR_NOT_USED(fpIndex);
    S_VAR_NOT_USED(challenge);
    S_VAR_NOT_USED(convertBigEndianToLittle);
    return TEE_ERROR_NOT_SUPPORTED;
}

#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;
}
