/* SPDX-FileCopyrightText: 2019 - 2023, Intel */
/* SPDX-FileCopyrightText: 2022, Erik Larsson */
/* SPDX-License-Identifier: BSD-2-Clause */

#include <stdbool.h> // for bool, true
#include <stddef.h>  // for NULL
#include <stdio.h>   // for snprintf
#include <string.h>  // for strlen

#include "../helper/cmocka_all.h" // for cmocka_unit_test, assert_int_equal, ass...
#include "tss2_common.h"          // for TSS2_RC, TSS2_RC_SUCCESS, TSS2_RC_LAYER
#include "tss2_rc.h"              // for Tss2_RC_Decode, TSS2_RC_INFO, Tss2_RC_S...
#include "tss2_tpm2_types.h"      // for TPM2_RC_HASH, TPM2_RC_EXPIRED, TPM2_RC_...
#include "util/aux_util.h"        // for ARRAY_LEN, UNUSED

#define assert_string_prefix(str, prefix) assert_memory_equal(str, prefix, strlen(prefix))

static void
test_layers(void **state) {
    UNUSED(state);

    static const char *known_layers[] = {
        "tpm:",   NULL,   NULL,  NULL,    NULL,  NULL, "fapi:",
        "esapi:", "sys:", "mu:", "tcti:", "rmt", "rm", "policy",
    };

    UINT8 layer;
    for (layer = 0; layer < (UINT8)ARRAY_LEN(known_layers); layer++) {
        TSS2_RC rc = TSS2_RC_LAYER(layer);

        const char *got = Tss2_RC_Decode(rc);

        char buf[256];
        snprintf(buf, sizeof(buf), "%u:", layer);

        const char *expected = known_layers[layer] ? known_layers[layer] : buf;
        assert_string_prefix(got, expected);
    }
}

static void
test_tpm_format_0_version2_0_error(void **state) {
    (void)state;

    const char *m = Tss2_RC_Decode(TPM2_RC_SEQUENCE);
    assert_string_equal(m, "tpm:error(2.0): improper use of a sequence"
                           " handle");
}

static void
test_tpm_format_0_version2_0_warn(void **state) {
    (void)state;

    const char *m = Tss2_RC_Decode(TPM2_RC_REFERENCE_H0);
    assert_string_equal(m, "tpm:warn(2.0): the 1st handle in the handle area references a"
                           " transient object or session that is not loaded");
}

static void
test_tpm2_format_0_unknown(void **state) {
    (void)state;

    const char *m = Tss2_RC_Decode(TPM2_RC_NOT_USED + 0x80);
    assert_string_equal(m, "tpm:parameter(1):unknown error num: 0x3F");
}

static void
test_tpm_format_1_unk_handle(void **state) {
    (void)state;

    const char *m = Tss2_RC_Decode(TPM2_RC_HASH);
    assert_string_equal(m, "tpm:handle(unk):hash algorithm not supported or not appropriate");
}

static void
test_tpm_format_1_unk_parameter(void **state) {
    (void)state;

    const char *m = Tss2_RC_Decode(TPM2_RC_HASH + TPM2_RC_P);
    assert_string_equal(m, "tpm:parameter(unk):hash algorithm not supported or not appropriate");
}

static void
test_tpm_format_1_unk_session(void **state) {
    (void)state;

    const char *m = Tss2_RC_Decode(TPM2_RC_HASH + TPM2_RC_S);
    assert_string_equal(m, "tpm:session(unk):hash algorithm not supported or not appropriate");
}

static void
test_tpm_format_1_5_handle(void **state) {
    (void)state;

    const char *m = Tss2_RC_Decode(TPM2_RC_HASH + TPM2_RC_5);
    assert_string_equal(m, "tpm:handle(5):hash algorithm not supported or not appropriate");
}

static void
test_tpm2_format_1_unknown(void **state) {
    (void)state;

    const char *m = Tss2_RC_Decode(TPM2_RC_NOT_USED + 0x80);
    assert_string_equal(m, "tpm:parameter(1):unknown error num: 0x3F");
}

static void
test_tpm2_format_1_success(void **state) {
    (void)state;

    const char *m = Tss2_RC_Decode(TPM2_RC_SUCCESS);
    assert_string_equal(m, "tpm:success");
}

static const char *
custom_err_handler(TSS2_RC rc) {

    static const char *err_map[] = { "error 1", "error 2", "error 3" };

    if (rc - 1u >= ARRAY_LEN(err_map)) {
        return NULL;
    }

    return err_map[rc - 1];
}

static void
test_custom_handler(void **state) {
    (void)state;

    /*
     * Test registering a custom handler
     */
    TSS2_RC_HANDLER old = Tss2_RC_SetHandler(1, "cstm", custom_err_handler);
    assert_null(old);

    /*
     * Test getting error strings
     */
    unsigned i;
    for (i = 1; i < 4; i++) {
        // Make a layer 1 error with an error number of i.
        TSS2_RC rc = TSS2_RC_LAYER(1) | i;
        char    buf[256];
        snprintf(buf, sizeof(buf), "cstm:error %u", i);

        const char *e = Tss2_RC_Decode(rc);
        assert_string_equal(e, buf);
    }

    TSS2_RC rc = TSS2_RC_LAYER(1) | 42;

    /*
     * Test an unknown error
     */
    const char *e = Tss2_RC_Decode(rc);
    assert_string_equal(e, "cstm:0x2A");

    /*
     * Test clearing a handler
     */
    old = Tss2_RC_SetHandler(1, "cstm", NULL);
    assert_ptr_equal(old, custom_err_handler);

    /*
     * Test an unknown layer
     */
    e = Tss2_RC_Decode(rc);
    assert_string_equal(e, "1:0x1002A");
}

static void
test_zero_length_name(void **state) {
    (void)state;

    TSS2_RC_HANDLER old = Tss2_RC_SetHandler(TSS2_TPM_RC_LAYER, "", custom_err_handler);
    assert_non_null(old);

    old = Tss2_RC_SetHandler(TSS2_TPM_RC_LAYER, "", custom_err_handler);
    assert_ptr_equal(old, custom_err_handler);
}

static void
test_over_length_name(void **state) {
    (void)state;

    TSS2_RC_HANDLER old = Tss2_RC_SetHandler(1, "way to long of name", custom_err_handler);
    assert_null(old);

    old = Tss2_RC_SetHandler(1, "way to long of name", custom_err_handler);
    assert_ptr_equal(old, custom_err_handler);
}

static void
test_null_name(void **state) {
    (void)state;

    TSS2_RC_HANDLER old = Tss2_RC_SetHandler(1, NULL, custom_err_handler);
    assert_ptr_equal(old, custom_err_handler);

    old = Tss2_RC_SetHandler(1, NULL, custom_err_handler);
    assert_ptr_equal(old, custom_err_handler);
}

static void
test_sys(void **state) {
    (void)state;

    const char *e = Tss2_RC_Decode(TSS2_SYS_RC_ABI_MISMATCH);
    assert_string_equal(e, "sys:Passed in ABI version doesn't match called module's ABI version");
}

static void
test_esys(void **state) {
    (void)state;

    const char *e = Tss2_RC_Decode(TSS2_ESYS_RC_BAD_VALUE);
    assert_string_equal(e, "esapi:A parameter has a bad value");
}

static void
test_mu(void **state) {
    (void)state;

    const char *e = Tss2_RC_Decode(TSS2_MU_RC_BAD_REFERENCE);
    assert_string_equal(e, "mu:A pointer is NULL that isn't allowed to be NULL.");
}

static void
test_tcti(void **state) {
    (void)state;

    const char *e = Tss2_RC_Decode(TSS2_TCTI_RC_NO_CONNECTION);
    assert_string_equal(e, "tcti:Fails to connect to next lower layer");
}

static void
test_info_fmt0(void **state) {
    TSS2_RC_INFO info = { 1, 2, 3, 4, 5, 6 };
    TSS2_RC      test_rc = TSS2_MU_RC_LAYER | TPM2_RC_SESSION_HANDLES;
    TSS2_RC      r = Tss2_RC_DecodeInfo(test_rc, &info);
    assert_int_equal(info.layer, 9);
    assert_int_equal(info.format, 0);
    assert_int_equal(info.error, TPM2_RC_SESSION_HANDLES);
    assert_int_equal(info.parameter, 0);
    assert_int_equal(info.handle, 0);
    assert_int_equal(info.session, 0);
    assert_int_equal(r, TSS2_RC_SUCCESS);
}

static void
test_info_fmt1_parameter(void **state) {
    TSS2_RC_INFO info;
    TSS2_RC      test_rc = TSS2_SYS_RC_LAYER | TPM2_RC_ASYMMETRIC | TPM2_RC_P | TPM2_RC_1;
    TSS2_RC      r = Tss2_RC_DecodeInfo(test_rc, &info);
    assert_int_equal(info.layer, 8);
    assert_int_equal(info.format, 1);
    assert_int_equal(info.error, TPM2_RC_ASYMMETRIC);
    assert_int_equal(info.parameter, 1);
    assert_int_equal(info.handle, 0);
    assert_int_equal(info.session, 0);
    assert_int_equal(r, TSS2_RC_SUCCESS);
}

static void
test_info_fmt1_handle(void **state) {
    TSS2_RC_INFO info;
    TSS2_RC      test_rc = TSS2_ESAPI_RC_LAYER | TPM2_RC_HANDLE | TPM2_RC_H | TPM2_RC_2;
    TSS2_RC      r = Tss2_RC_DecodeInfo(test_rc, &info);
    assert_int_equal(info.layer, 7);
    assert_int_equal(info.error, TPM2_RC_HANDLE);
    assert_int_equal(info.parameter, 0);
    assert_int_equal(info.handle, 2);
    assert_int_equal(info.session, 0);
    assert_int_equal(r, TSS2_RC_SUCCESS);
}

static void
test_info_fmt1_session(void **state) {
    TSS2_RC_INFO info;
    TSS2_RC      test_rc = TSS2_FEATURE_RC_LAYER | TPM2_RC_EXPIRED | TPM2_RC_S | TPM2_RC_3;
    TSS2_RC      r = Tss2_RC_DecodeInfo(test_rc, &info);
    assert_int_equal(info.layer, 6);
    assert_int_equal(info.error, TPM2_RC_EXPIRED);
    assert_int_equal(info.parameter, 0);
    assert_int_equal(info.handle, 0);
    assert_int_equal(info.session, 3);
    assert_int_equal(r, TSS2_RC_SUCCESS);
}

static void
test_info_null(void **state) {
    TSS2_RC r = Tss2_RC_DecodeInfo(TSS2_RC_SUCCESS, NULL);
    assert_int_equal(r, TSS2_BASE_RC_BAD_REFERENCE);
}

static void
test_info_str_fmt1(void **state) {
    TSS2_RC_INFO info = {
        .error = TPM2_RC_EXPIRED,
        .format = 1,
    };
    const char *m = Tss2_RC_DecodeInfoError(&info);
    assert_string_equal(m, "the policy has expired");
}

static void
test_info_str_fmt1_ff(void **state) {
    TSS2_RC_INFO info = {
        .error = 0xFF,
        .format = 1,
    };
    const char *m = Tss2_RC_DecodeInfoError(&info);
    assert_string_equal(m, "0xFF");
}

static void
test_info_str_fmt0_err(void **state) {
    TSS2_RC_INFO info = {
        .error = TPM2_RC_COMMAND_CODE,
        .format = 0,
    };
    const char *m = Tss2_RC_DecodeInfoError(&info);
    assert_string_equal(m, "command code not supported");
}

static void
test_info_str_fmt0_warn(void **state) {
    TSS2_RC_INFO info = {
        .error = TPM2_RC_TESTING,
        .format = 0,
    };
    const char *m = Tss2_RC_DecodeInfoError(&info);
    assert_string_equal(m, "TPM is performing selftests");
}

static void
test_info_str_fmt0_ff(void **state) {
    TSS2_RC_INFO info = {
        .error = 0xFF,
        .format = 0,
    };
    const char *m = Tss2_RC_DecodeInfoError(&info);
    assert_string_equal(m, "0xFF");
}

static void
test_info_str_null(void **state) {
    const char *m = Tss2_RC_DecodeInfoError(NULL);
    assert_null(m);
}

static void
test_all_FFs(void **state) {
    (void)state;

    const char *e = Tss2_RC_Decode(0xFFFFFFFF);
    assert_string_equal(e, "255:0xFFFFFFFF");
}

static void
test_all_FFs_set_handler(void **state) {
    (void)state;
    Tss2_RC_SetHandler(0xFF, "garbage", custom_err_handler);
    Tss2_RC_SetHandler(0xFF, NULL, NULL);
}

/* link required symbol, but tpm2_tool.c declares it AND main, which
 * we have a main below for cmocka tests.
 */
bool output_enabled = true;

int
main(int argc, char *argv[]) {
    (void)argc;
    (void)argv;

    const struct CMUnitTest tests[] = { /* Layer tests */
                                        cmocka_unit_test(test_layers),
                                        cmocka_unit_test(test_tpm_format_0_version2_0_error),
                                        cmocka_unit_test(test_tpm_format_0_version2_0_warn),
                                        cmocka_unit_test(test_tpm2_format_0_unknown),
                                        cmocka_unit_test(test_tpm_format_1_unk_handle),
                                        cmocka_unit_test(test_tpm_format_1_unk_parameter),
                                        cmocka_unit_test(test_tpm_format_1_unk_session),
                                        cmocka_unit_test(test_tpm_format_1_5_handle),
                                        cmocka_unit_test(test_tpm2_format_1_unknown),
                                        cmocka_unit_test(test_tpm2_format_1_success),
                                        cmocka_unit_test(test_custom_handler),
                                        cmocka_unit_test(test_zero_length_name),
                                        cmocka_unit_test(test_over_length_name),
                                        cmocka_unit_test(test_null_name),
                                        cmocka_unit_test(test_sys),
                                        cmocka_unit_test(test_esys),
                                        cmocka_unit_test(test_mu),
                                        cmocka_unit_test(test_tcti),
                                        cmocka_unit_test(test_info_fmt0),
                                        cmocka_unit_test(test_info_fmt1_parameter),
                                        cmocka_unit_test(test_info_fmt1_handle),
                                        cmocka_unit_test(test_info_fmt1_session),
                                        cmocka_unit_test(test_info_null),
                                        cmocka_unit_test(test_info_str_fmt1),
                                        cmocka_unit_test(test_info_str_fmt1_ff),
                                        cmocka_unit_test(test_info_str_fmt0_err),
                                        cmocka_unit_test(test_info_str_fmt0_warn),
                                        cmocka_unit_test(test_info_str_fmt0_ff),
                                        cmocka_unit_test(test_info_str_null),
                                        cmocka_unit_test(test_all_FFs),
                                        cmocka_unit_test(test_all_FFs_set_handler)
    };

    return cmocka_run_group_tests(tests, NULL, NULL);
}
