/*
  @file app_fips_test.c
  @brief This file is sample code that show how QSEE fips services APIs can be
  used.
*/

#include "CDeviceID.h"
#include "IDeviceID.h"
#include "IxErrno.h"
#include "check.h"
#include "object.h"
#include "qsee_core.h"
#include "qsee_env.h"
#include "qsee_fips_services.h"
#include "qsee_heap.h"
#include "qsee_log.h"
#include "stdio.h"

/* The following mask values are from swcryptoselfteststate.h */
#define CRYPTO_SELFTEST_PASS_MASKS 0x000000FF
#define CRYPTO_SELFTEST_FAIL_MASKS 0x001FE000

/* Trusted OS version string Maximum length*/
#define QTEE_VERSION_STRING_MAX_LEN 128

static void print_hmac(uint8_t* hmac, size_t len)
{
    size_t out_buf_len = (len * 2) + 1;
    char* out = qsee_malloc(out_buf_len);
    if (out == NULL) {
        return;
    }

    for (size_t i = 0; i < len; i++) {
        snprintf(out + (i * 2), out_buf_len - (i * 2), "%02x", hmac[i]);
    }

    qsee_log(QSEE_LOG_MSG_DEBUG, "FIPS Module HMAC: %s", out);
    qsee_free(out);
}

static int test_fips_get_service_status(void)
{
    qsee_log(QSEE_LOG_MSG_ERROR, "qsee_get_fips_approval_status tests START");

#define FIPS_SVC_STATUS_TEST_CASE_COUNT 13
    QSEE_FIPS_CRYPTO_SVC_TYPE test_svc[FIPS_SVC_STATUS_TEST_CASE_COUNT] = {
        // approved
        QSEE_FIPS_ECDSA_SIG_VER_P224_SHA256, QSEE_FIPS_AES_256_CCM,
        QSEE_FIPS_TDES_ECB_DECRYPT, QSEE_FIPS_PBKDF2_SHA224,
        // sunset after 2023
        QSEE_FIPS_TDES_CBC_ENCRYPT,
        // invalid service id
        QSEE_FIPS_PBKDF2,
        // not approved
        QSEE_FIPS_ECDH_SHARED_SECRET_GEN_P256, QSEE_FIPS_SHA1,
        QSEE_FIPS_HMAC_SHA1_KEYLEN_BETWEEN_112_AND_512_BITS,
        QSEE_FIPS_ECDSA_SIG_VER_P192_SHA1, QSEE_FIPS_RSA_SIG_VER_PSS_4096_SHA1,
        QSEE_FIPS_CRYPTO_SVC_APPROVED_END, QSEE_FIPS_CRYPTO_SVC_END,
    };

    char* test_str[FIPS_SVC_STATUS_TEST_CASE_COUNT] = {
        // approved
        "QSEE_FIPS_ECDSA_SIG_VER_P224_SHA256", "QSEE_FIPS_AES_256_CCM",
        "QSEE_FIPS_TDES_ECB_DECRYPT", "QSEE_FIPS_PBKDF2_SHA224",
        // sunset after 2023
        "QSEE_FIPS_TDES_CBC_ENCRYPT",
        // invalid service id
        "QSEE_FIPS_PBKDF2",
        // not approved
        "QSEE_FIPS_ECDH_SHARED_SECRET_GEN_P256", "QSEE_FIPS_SHA1",
        "QSEE_FIPS_HMAC_SHA1_KEYLEN_BETWEEN_112_AND_512_BITS",
        "QSEE_FIPS_ECDSA_SIG_VER_P192_SHA1",
        "QSEE_FIPS_RSA_SIG_VER_PSS_4096_SHA1",
        "QSEE_FIPS_CRYPTO_SVC_APPROVED_END", "QSEE_FIPS_CRYPTO_SVC_END"};

    int exp_ret[FIPS_SVC_STATUS_TEST_CASE_COUNT] = {
        QSEE_FIPS_APPROVED,           QSEE_FIPS_APPROVED,
        QSEE_FIPS_APPROVED,           QSEE_FIPS_APPROVED,

        QSEE_FIPS_NOT_APPROVED,

        QSEE_FIPS_INVALID_SERVICE_ID,

        QSEE_FIPS_NOT_APPROVED,       QSEE_FIPS_NOT_APPROVED,
        QSEE_FIPS_NOT_APPROVED,       QSEE_FIPS_NOT_APPROVED,
        QSEE_FIPS_NOT_APPROVED,       QSEE_FIPS_NOT_APPROVED,
        QSEE_FIPS_NOT_APPROVED};

    char* ret_str[] = {[QSEE_FIPS_APPROVED] = "APPROVED",
                       [QSEE_FIPS_NOT_APPROVED] = "NOT_APPROVED",
                       [QSEE_FIPS_SUNSET_AFTER_2023] = "SUNSET_AFTER_2023",
                       [QSEE_FIPS_INVALID_SERVICE_ID] = "INVALID_SERVICE_ID"};

    for (int i = 0; i < FIPS_SVC_STATUS_TEST_CASE_COUNT; i++) {
        int ret = qsee_get_fips_approval_status(test_svc[i]);
        if (ret != exp_ret[i]) {
            qsee_log(QSEE_LOG_MSG_ERROR,
                     "Service %s, expected status %s, returned status %s, Test "
                     "Failed",
                     test_str[i], ret_str[exp_ret[i]], ret_str[ret]);
            return E_FAILURE;
        } else {
            qsee_log(QSEE_LOG_MSG_ERROR,
                     "Service %s, expected status %s, returned status %s, Test "
                     "Passed",
                     test_str[i], ret_str[exp_ret[i]], ret_str[ret]);
        }
    }
    qsee_log(QSEE_LOG_MSG_ERROR, "qsee_get_fips_approval_status tests END");
    return E_SUCCESS;
}

static int get_firmware_version(void)
{
    int ret = Object_ERROR;
    uint8_t imp_version[QTEE_VERSION_STRING_MAX_LEN] = {0};
    size_t imp_version_len = sizeof(imp_version);

    Object devID_Obj = qsee_open_singleton(CDeviceID_UID);

    CHECK_ELSE((!Object_isNull(devID_Obj)), goto __Exit);

    ret = IDeviceID_getComponentVersion(devID_Obj, IDeviceID_QSEE_VERSION,
                                        imp_version, imp_version_len,
                                        &imp_version_len);
    CHECK_ELSE(ret == Object_OK, goto __Exit);
    qsee_log(QSEE_LOG_MSG_DEBUG, "FIPS Trusted OS Component Version: %s",
             imp_version);

    memset(imp_version, 0, sizeof(imp_version));
    imp_version_len = sizeof(imp_version);
    ret = IDeviceID_getComponentVersion(devID_Obj, IDeviceID_SBL_VERSION,
                                        imp_version, imp_version_len,
                                        &imp_version_len);
    CHECK_ELSE(ret == Object_OK, goto __Exit);
    qsee_log(QSEE_LOG_MSG_DEBUG,
             "FIPS Trusted OS Firmware Component Version: %s", imp_version);

#ifdef IDeviceID_TME_VERSION
    memset(imp_version, 0, sizeof(imp_version));
    imp_version_len = sizeof(imp_version);
    ret = IDeviceID_getComponentVersion(devID_Obj, IDeviceID_TME_VERSION,
                                        imp_version, imp_version_len,
                                        &imp_version_len);
    CHECK_ELSE(ret == Object_OK, goto __Exit);
    qsee_log(QSEE_LOG_MSG_DEBUG,
             "FIPS TME Component Version: %s", imp_version);
#endif // IDeviceID_TME_VERSION

__Exit:
    return ret;
}

static int test_fips_get_info(void)
{
    uint8_t hmac[32] = {0};
    size_t len = 0;
    uint32_t fuse_status = 0;
    uint32_t state = 0;
    uint32_t enablement = 0;
    uint32_t hw_version = 0;
    size_t out_len = 0;
    int ret = 0;

    /* Get HW version */
    ret = qsee_get_fips_info(QSEE_FIPS_HW_VERSION, &hw_version,
                             sizeof(hw_version), &out_len);
    if (ret != QSEE_FIPS_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_ERROR,
                 "ERROR: qsee_get_fips_information failed to get HW version ");
        return E_FAILURE;
    }
    qsee_log(QSEE_LOG_MSG_DEBUG, "FIPS HW vesion: 0X%08x", hw_version);

    /* Get Firmware version */
    ret = get_firmware_version();
    if (ret != Object_OK) {
        qsee_log(QSEE_LOG_MSG_ERROR,
                 "ERROR: get_firmware_version failed to get target firmware "
                 "versions");
        return E_FAILURE;
    }

    // This will fail if the HMAC of the crypto module is not present, or the
    // integrity check does not pass.
    ret =
        qsee_get_fips_info(QSEE_FIPS_MODULE_HMAC, hmac, sizeof(hmac), &out_len);
    if (ret != QSEE_FIPS_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_ERROR,
                 "ERROR: qsee_get_fips_information failed to get HMAC");
        return E_FAILURE;
    }

    print_hmac(hmac, sizeof(hmac));

    ret = qsee_get_fips_info(QSEE_FIPS_FUSE_STATUS, &fuse_status,
                             sizeof(fuse_status), &out_len);
    if (ret != QSEE_FIPS_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_ERROR,
                 "ERROR: qsee_get_fips_information failed to get selftest fuse "
                 "status");
        return E_FAILURE;
    }

    ret = qsee_get_fips_info(QSEE_FIPS_ENABLEMENT, &enablement,
                             sizeof(enablement), &out_len);
    if (ret != QSEE_FIPS_SUCCESS) {
        qsee_log(
            QSEE_LOG_MSG_ERROR,
            "ERROR: qsee_get_fips_information failed to get selftest level");
        return E_FAILURE;
    }

    ret = qsee_get_fips_info(QSEE_FIPS_SELFTEST_STATUS, &state, sizeof(state),
                             &out_len);
    if (ret != QSEE_FIPS_SUCCESS) {
        qsee_log(
            QSEE_LOG_MSG_ERROR,
            "ERROR: qsee_get_fips_information failed to get selftest status");
        return E_FAILURE;
    }

    qsee_log(QSEE_LOG_MSG_DEBUG, "FIPS fuse status: %d", fuse_status);
    qsee_log(QSEE_LOG_MSG_DEBUG, "FIPS enablement status: %d", enablement);
    qsee_log(QSEE_LOG_MSG_DEBUG, "FIPS selftest state: 0x%x", state);

    // Since a selftest should be enabled in the sampleapp, the crypto selftest
    // should be run
    // This does not return an error because in modem targets it will not be run
    if (CRYPTO_SELFTEST_PASS_MASKS == (state & CRYPTO_SELFTEST_PASS_MASKS)) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "Crypto selftest passed");
    } else if ((state & CRYPTO_SELFTEST_FAIL_MASKS)) {
        qsee_log(QSEE_LOG_MSG_ERROR, "Crypto selftest failed!");
    } else if (!(state & CRYPTO_SELFTEST_PASS_MASKS) &&
               !(state & CRYPTO_SELFTEST_FAIL_MASKS)) {
        qsee_log(QSEE_LOG_MSG_ERROR,
                 "ERROR: Selftest is enabled, but was not run");
    } else if (state & CRYPTO_SELFTEST_PASS_MASKS) {
        qsee_log(QSEE_LOG_MSG_ERROR,
                 "Crypto Selftest is enabled, but not all tests are done");
    } else {
        qsee_log(
            QSEE_LOG_MSG_ERROR,
            "Unexpected state from qsee_get_selftest_status_information: 0x%x",
            state);
    }

    return E_SUCCESS;
}

int run_fips_services_test(void)
{
    int status = E_SUCCESS;

    qsee_log(QSEE_LOG_MSG_DEBUG,
             "----------Start Sample App FIPS Services Tests ------------");

    if (test_fips_get_info() != E_SUCCESS) {
        status = -E_FAILURE;
    }

    if (E_SUCCESS == status && test_fips_get_service_status() != E_SUCCESS) {
        status = -E_FAILURE;
    }
    qsee_log(QSEE_LOG_MSG_DEBUG,
             "----------End Sample App FIPS Services Tests ------------");

    return status;
}
