/* SPDX-License-Identifier: BSD-2-Clause */
/*******************************************************************************
 * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG
 * All rights reserved.
 *******************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h" // IWYU pragma: keep
#endif

#include <stdlib.h> // for EXIT_FAILURE, EXIT_SUCCESS, NULL
#include <string.h> // for memcmp

#include "esys_int.h"    // for ESYS_CONTEXT
#include "tss2_common.h" // for TSS2_RC_SUCCESS, TSS2_RC, TSS2_ESYS_RC_BAD_...
#include "tss2_esys.h"   // for ESYS_CONTEXT, ESYS_CRYPTO_CALLBACKS, Esys_S...

#define LOGMODULE test
#include "util/log.h" // for LOG_ERROR, xstr

#define TEST_FN_PTR ((void *)0xBADC0DE)

#define CHECK_BACKEND_FN_NOT_TEST(backend, fn)                                                     \
    if ((backend).fn == TEST_FN_PTR) {                                                             \
        LOG_ERROR("Expected function \"%s\" not to be test ptr", xstr(fn));                        \
        return EXIT_FAILURE;                                                                       \
    }

/** This test is intended to test the ESYS command  Esys_SetCryptoCallbacks.
 *
 * The test checks whether callbacks are set as expected.
 *
 * Tested ESYS commands:
 *  - Esys_Hash() (M)
 *
 * @retval EXIT_FAILURE
 * @retval EXIT_SUCCESS
 */

static int init_called = 0;
static TSS2_RC
init(void *userdata) {
    init_called = 1;
    return (void *)0xDEADBEEF ? TSS2_RC_SUCCESS : TSS2_ESYS_RC_BAD_VALUE;
}

int
test_invoke_esys(ESYS_CONTEXT *esys_context) {
    /*peer into the internals, state should be as expected in esys context */
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hash_start);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hash_update);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hash_finish);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hash_abort);

    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hmac_start);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hmac_update);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hmac_finish);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hmac_abort);

    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, aes_decrypt);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, aes_encrypt);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, sm4_decrypt);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, sm4_encrypt);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, get_ecdh_point);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, get_random2b);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, rsa_pk_encrypt);

    ESYS_CRYPTO_CALLBACKS callbacks = { .aes_decrypt = TEST_FN_PTR,
                                        .aes_encrypt = TEST_FN_PTR,
                                        .sm4_decrypt = TEST_FN_PTR,
                                        .sm4_encrypt = TEST_FN_PTR,
                                        .get_ecdh_point = TEST_FN_PTR,
                                        .get_random2b = TEST_FN_PTR,
                                        .rsa_pk_encrypt = TEST_FN_PTR,
                                        .hash_abort = TEST_FN_PTR,
                                        .hash_finish = TEST_FN_PTR,
                                        .hash_start = TEST_FN_PTR,
                                        .hash_update = TEST_FN_PTR,
                                        .hmac_abort = TEST_FN_PTR,
                                        .hmac_finish = TEST_FN_PTR,
                                        .hmac_start = TEST_FN_PTR,
                                        .hmac_update = TEST_FN_PTR,
                                        .init = init,
                                        .userdata = (void *)0xDEADBEEF };

    TSS2_RC r = Esys_SetCryptoCallbacks(esys_context, &callbacks);
    if (r != TSS2_RC_SUCCESS) {
        LOG_ERROR("Esys_SetCryptoCallbacks failed: 0x%x", r);
        return EXIT_FAILURE;
    }

    if (!init_called) {
        LOG_ERROR("user callback for init not invoked");
        return EXIT_FAILURE;
    }

    /* peer back into internals and ensure state is correct */
    if (memcmp(&esys_context->crypto_backend, &callbacks, sizeof(callbacks)) != 0) {
        LOG_ERROR("ESYS_CONTEXT state for user callbacks not as expected");
        return EXIT_FAILURE;
    }

    /* reset state */
    r = Esys_SetCryptoCallbacks(esys_context, NULL);
    if (r != TSS2_RC_SUCCESS) {
        LOG_ERROR("Esys_SetCryptoCallbacks failed: 0x%x", r);
        return EXIT_FAILURE;
    }

    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hash_start);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hash_update);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hash_finish);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hash_abort);

    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hmac_start);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hmac_update);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hmac_finish);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, hmac_abort);

    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, aes_decrypt);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, aes_encrypt);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, sm4_decrypt);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, sm4_encrypt);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, get_ecdh_point);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, get_random2b);
    CHECK_BACKEND_FN_NOT_TEST(esys_context->crypto_backend, rsa_pk_encrypt);

    return EXIT_SUCCESS;
}
