/* SPDX-License-Identifier: BSD-2-Clause */
/***********************************************************************
 * Copyright (c) 2017-2018, Intel Corporation
 *
 * All rights reserved.
 ***********************************************************************/
#ifdef HAVE_CONFIG_H
#include "config.h" // IWYU pragma: keep
#endif

#include <errno.h>    // for errno, EACCES, ENOENT
#include <inttypes.h> // for uint8_t
#include <poll.h>     // for pollfd, nfds_t
#include <stdio.h>    // for NULL, size_t, ssize_t
#include <stdlib.h>   // for calloc, free
#include <string.h>   // for memcpy, strlen, strncmp

#include "../helper/cmocka_all.h"  // for will_return, assert_true, assert_...
#include "tss2-tcti/tcti-common.h" // for tcti_common_context_cast, TSS2_TC...
#include "tss2_common.h"           // for TSS2_RC_SUCCESS, TSS2_RC, TSS2_TC...
#include "tss2_tcti.h"             // for TSS2_TCTI_CONTEXT, TSS2_TCTI_TIME...
#include "tss2_tcti_device.h"      // for Tss2_Tcti_Device_Init

#define LOGMODULE tests
#include "util/log.h"

#define EXIT_SKIP 77

/*
 * Size of the TPM2 buffer used in these tests. In some cases this will be
 * the command sent (transmit tests) and in others it's used as the response
 * buffer returned by the TCTI. The only field used by the TCTI is the size
 * field.
 */
#define BUF_SIZE 20
static uint8_t tpm2_buf[BUF_SIZE] = { 0x80, 0x02,             /* TAG */
                                      0x00, 0x00, 0x00, 0x14, /* size (BUF_SIZE) */
                                      0x00, 0x00, 0x00, 0x00, /* rc (success) */
                                      0xde, 0xad, 0xbe, 0xef, /* junk data */
                                      0xca, 0xfe, 0xba, 0xbe, 0xfe, 0xef };
int            __real_open(const char *pathname, int flags, ...);
/* wrap function for open required to test init */
int
__wrap_open(const char *pathname, int flags, ...) {
    const char *pathname_prefix_dev = "/dev";
    if (strncmp(pathname, pathname_prefix_dev, strlen(pathname_prefix_dev)) == 0) {
        return mock_type(int);
    } else {
        /* only mock opening of device files as the open() syscall is needed
           for code coverage reports as well */
        return __real_open(pathname, flags);
    }
}
/**
 * When passed all NULL values ensure that we get back the expected RC
 * indicating bad values.
 */
static void
tcti_device_init_all_null_test(void **state) {
    TSS2_RC rc;

    rc = Tss2_Tcti_Device_Init(NULL, NULL, NULL);
    assert_int_equal(rc, TSS2_TCTI_RC_BAD_VALUE);
}
/* Determine the size of a TCTI context structure. Requires calling the
 * initialization function for the device TCTI with the first parameter
 * (the TCTI context) NULL.
 */
static void
tcti_device_init_size_test(void **state) {
    size_t  tcti_size = 0;
    TSS2_RC ret = TSS2_RC_SUCCESS;

    ret = Tss2_Tcti_Device_Init(NULL, &tcti_size, NULL);
    assert_int_equal(ret, TSS2_RC_SUCCESS);
}
/* Test the failure of opening a specified device file */
static void
tcti_device_init_conf_fail(void **state) {
    size_t             tcti_size = 0;
    TSS2_RC            ret = TSS2_RC_SUCCESS;
    TSS2_TCTI_CONTEXT *ctx = NULL;

    ret = Tss2_Tcti_Device_Init(NULL, &tcti_size, NULL);
    assert_true(ret == TSS2_RC_SUCCESS);
    ctx = calloc(1, tcti_size);
    assert_non_null(ctx);
    errno = ENOENT; /* No such file or directory */
    will_return(__wrap_open, -1);
    ret = Tss2_Tcti_Device_Init(ctx, &tcti_size, "/dev/nonexistent");
    assert_true(ret == TSS2_TCTI_RC_IO_ERROR);

    free(ctx);
}
/* Test the device file recognition if no config string was specified */
static void
tcti_device_init_conf_default_fail(void **state) {
    size_t             tcti_size = 0;
    TSS2_RC            ret = TSS2_RC_SUCCESS;
    TSS2_TCTI_CONTEXT *ctx = NULL;

    ret = Tss2_Tcti_Device_Init(NULL, &tcti_size, NULL);
    assert_true(ret == TSS2_RC_SUCCESS);
    ctx = calloc(1, tcti_size);
    assert_non_null(ctx);
    errno = EACCES; /* Permission denied */
    will_return(__wrap_open, -1);
    will_return(__wrap_open, -1);
    will_return(__wrap_open, -1);
    ret = Tss2_Tcti_Device_Init(ctx, &tcti_size, NULL);
    assert_true(ret == TSS2_TCTI_RC_IO_ERROR);

    free(ctx);
}

/* Test the device file recognition if no config string was specified */
static void
tcti_device_init_conf_default_success(void **state) {
    size_t             tcti_size = 0;
    TSS2_RC            ret = TSS2_RC_SUCCESS;
    TSS2_TCTI_CONTEXT *ctx = NULL;

    ret = Tss2_Tcti_Device_Init(NULL, &tcti_size, NULL);
    assert_true(ret == TSS2_RC_SUCCESS);
    ctx = calloc(1, tcti_size);
    assert_non_null(ctx);
    will_return(__wrap_open, 3);
    will_return(__wrap_write, 12);
    will_return(__wrap_write, tpm2_buf);
    will_return(__wrap_poll, 1);
    will_return(__wrap_read, 10);
    will_return(__wrap_read, tpm2_buf);
    will_return(__wrap_poll, 1);
    will_return(__wrap_read, 8);
    will_return(__wrap_read, tpm2_buf);
    ret = Tss2_Tcti_Device_Init(ctx, &tcti_size, NULL);
    assert_true(ret == TSS2_RC_SUCCESS);

    free(ctx);
}

/* wrap functions for read & write required to test receive / transmit */
ssize_t
__wrap_read(int fd, void *buf, size_t count) {
    ssize_t  ret = mock_type(ssize_t);
    uint8_t *buf_in = mock_type(uint8_t *);

    memcpy(buf, buf_in, ret);
    return ret;
}
ssize_t
__wrap_write(int fd, const void *buffer, size_t buffer_size) {
    ssize_t  ret = mock_type(ssize_t);
    uint8_t *buf_out = mock_type(uint8_t *);

    memcpy(buf_out, buffer, ret);
    return ret;
}

int
__wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
    int ret = mock_type(int);

    fds->revents = fds->events;
    return ret;
}

/* Setup functions to create the context for the device TCTI */
static int
tcti_device_setup(void **state) {
    size_t             tcti_size = 0;
    TSS2_RC            ret = TSS2_RC_SUCCESS;
    TSS2_TCTI_CONTEXT *ctx = NULL;

    ret = Tss2_Tcti_Device_Init(NULL, &tcti_size, NULL);
    assert_true(ret == TSS2_RC_SUCCESS);
    ctx = calloc(1, tcti_size);
    assert_non_null(ctx);
    will_return(__wrap_open, 3);
    will_return(__wrap_write, 12);
    will_return(__wrap_write, tpm2_buf);
    will_return(__wrap_poll, 1);
    will_return(__wrap_read, 10);
    will_return(__wrap_read, tpm2_buf);
    will_return(__wrap_poll, 1);
    will_return(__wrap_read, 0);
    will_return(__wrap_read, tpm2_buf);
    will_return(__wrap_open, 3);
    ret = Tss2_Tcti_Device_Init(ctx, &tcti_size, "/dev/null");
    assert_true(ret == TSS2_RC_SUCCESS);

    *state = ctx;
    return 0;
}

static int
tcti_device_teardown(void **state) {
    TSS2_TCTI_CONTEXT *ctx = (TSS2_TCTI_CONTEXT *)*state;

    Tss2_Tcti_Finalize(ctx);
    free(ctx);

    return 0;
}
/*
 * This test ensures that the GetPollHandles function in the device TCTI
 * returns the expected value. Since this TCTI does not support async I/O
 * on account of limitations in the kernel it just returns the
 * NOT_IMPLEMENTED response code.
 */
static void
tcti_device_get_poll_handles_test(void **state) {
    TSS2_TCTI_CONTEXT    *ctx = (TSS2_TCTI_CONTEXT *)*state;
    size_t                num_handles = 5;
    TSS2_TCTI_POLL_HANDLE handles[5] = { 0 };
    TSS2_RC               rc;

    rc = Tss2_Tcti_GetPollHandles(ctx, handles, &num_handles);
    assert_int_equal(rc, TSS2_RC_SUCCESS);
    assert_int_equal(num_handles, 1);
}
/*
 */
static void
tcti_device_receive_null_size_test(void **state) {
    TSS2_TCTI_CONTEXT        *ctx = (TSS2_TCTI_CONTEXT *)*state;
    TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_common_context_cast(ctx);
    TSS2_RC                   rc;

    /* Keep state machine check in `receive` from returning error. */
    tcti_common->state = TCTI_STATE_RECEIVE;
    rc = Tss2_Tcti_Receive(ctx, NULL, /* NULL 'size' parameter */
                           NULL, TSS2_TCTI_TIMEOUT_BLOCK);
    assert_int_equal(rc, TSS2_TCTI_RC_BAD_REFERENCE);
    rc = Tss2_Tcti_Receive(ctx, NULL,    /* NULL 'size' parameter */
                           (uint8_t *)1, /* non-NULL buffer */
                           TSS2_TCTI_TIMEOUT_BLOCK);
    assert_int_equal(rc, TSS2_TCTI_RC_BAD_REFERENCE);
}
/*
 * A test case for a successful call to the receive function. This requires
 * that the context and the command buffer be valid (including the size
 * field being set appropriately). The result should be an RC indicating
 * success and the size parameter be updated to reflect the size of the
 * data received.
 */
static void
tcti_device_receive_success(void **state) {
    TSS2_TCTI_CONTEXT        *ctx = (TSS2_TCTI_CONTEXT *)*state;
    TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_common_context_cast(ctx);
    TSS2_RC                   rc;
    /* output buffer for response */
    uint8_t buf_out[BUF_SIZE + 5] = { 0 };
    size_t  size = BUF_SIZE + 5;

    /* Keep state machine check in `receive` from returning error. */
    tcti_common->state = TCTI_STATE_RECEIVE;
    will_return(__wrap_poll, 1);
    will_return(__wrap_read, BUF_SIZE);
    will_return(__wrap_read, tpm2_buf);
    rc = Tss2_Tcti_Receive(ctx, &size, buf_out, TSS2_TCTI_TIMEOUT_BLOCK);
    assert_true(rc == TSS2_RC_SUCCESS);
    assert_int_equal(BUF_SIZE, size);
    assert_memory_equal(tpm2_buf, buf_out, size);
}
/*
 * Ensure that when the 'read' results in an EOF, we get a response code
 * indicating as much. EOF happens if / when the device driver kills our
 * connection.
 */
static void
tcti_device_receive_eof_test(void **state) {
    TSS2_TCTI_CONTEXT        *ctx = (TSS2_TCTI_CONTEXT *)*state;
    TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_common_context_cast(ctx);
    TSS2_RC                   rc;
    /* output buffer for response */
    uint8_t buf_out[BUF_SIZE + 5] = { 0 };
    size_t  size = BUF_SIZE + 5;

    /* Keep state machine check in `receive` from returning error. */
    tcti_common->state = TCTI_STATE_RECEIVE;
    will_return(__wrap_poll, 1);
    will_return(__wrap_read, 0);
    will_return(__wrap_read, tpm2_buf);
    rc = Tss2_Tcti_Receive(ctx, &size, buf_out, TSS2_TCTI_TIMEOUT_BLOCK);
    assert_int_equal(rc, TSS2_TCTI_RC_NO_CONNECTION);
}
/*
 * This is a weird test: The device TCTI can't read the header for the
 * response buffer separately from the body. This means it can't know the size
 * of the response before reading the whole thing. In the event that the caller
 * provides a buffer that isn't large enough to hold the full response the TCTI
 * will just read as much data as the buffer will hold. Subsequent interactions
 * with the kernel driver will likely result in an error.
 */
static void
tcti_device_receive_buffer_lt_response(void **state) {
    TSS2_TCTI_CONTEXT        *ctx = (TSS2_TCTI_CONTEXT *)*state;
    TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_common_context_cast(ctx);
    TSS2_RC                   rc;
    uint8_t                   buf_out[BUF_SIZE] = { 0 };
    /* set size to lt the size in the header of the TPM2 response buffer */
    size_t size = BUF_SIZE - 1;
    size_t small_size = TPM_HEADER_SIZE + 1;

    /* Keep state machine check in `receive` from returning error. */
    tcti_common->state = TCTI_STATE_RECEIVE;
    will_return(__wrap_poll, 1);
    will_return(__wrap_read, size);
    will_return(__wrap_read, tpm2_buf);
    rc = Tss2_Tcti_Receive(ctx, &small_size, buf_out, TSS2_TCTI_TIMEOUT_BLOCK);
    assert_int_equal(rc, TSS2_TCTI_RC_GENERAL_FAILURE);
}
/*
 * A test case for a successful call to the transmit function. This requires
 * that the context and the cmmand buffer be valid. The only indication of
 * success is the RC.
 */
static void
tcti_device_transmit_success(void **state) {
    TSS2_TCTI_CONTEXT *ctx = (TSS2_TCTI_CONTEXT *)*state;
    TSS2_RC            rc;
    /* output buffer for response */
    uint8_t buf_out[BUF_SIZE] = { 0 };

    will_return(__wrap_write, BUF_SIZE);
    will_return(__wrap_write, buf_out);
    rc = Tss2_Tcti_Transmit(ctx, BUF_SIZE, tpm2_buf);
    assert_true(rc == TSS2_RC_SUCCESS);
    assert_memory_equal(tpm2_buf, buf_out, BUF_SIZE);
}
/*
 * A test case for a successful poll
 */
static void
tcti_device_poll_success(void **state) {
    TSS2_TCTI_CONTEXT        *ctx = (TSS2_TCTI_CONTEXT *)*state;
    TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_common_context_cast(ctx);
    TSS2_RC                   rc;
    /* output buffer for response */
    uint8_t buf_out[BUF_SIZE] = { 0 };
    size_t  size = BUF_SIZE;

    /* Keep state machine check in `receive` from returning error. */
    tcti_common->state = TCTI_STATE_RECEIVE;
    will_return(__wrap_poll, 1);
    will_return(__wrap_read, BUF_SIZE);
    will_return(__wrap_read, tpm2_buf);

    rc = Tss2_Tcti_Receive(ctx, &size, buf_out, TSS2_TCTI_TIMEOUT_BLOCK);

    assert_true(rc == TSS2_RC_SUCCESS);
    assert_int_equal(BUF_SIZE, size);
    assert_memory_equal(tpm2_buf, buf_out, size);
}
/*
 * A test case for poll timeout
 */
static void
tcti_device_poll_timeout(void **state) {
    TSS2_TCTI_CONTEXT        *ctx = (TSS2_TCTI_CONTEXT *)*state;
    TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_common_context_cast(ctx);
    TSS2_RC                   rc;
    /* output buffer for response */
    uint8_t buf_out[BUF_SIZE] = { 0 };
    size_t  size = BUF_SIZE;

    /* Keep state machine check in `receive` from returning error. */
    tcti_common->state = TCTI_STATE_RECEIVE;
    will_return(__wrap_poll, 0);

    rc = Tss2_Tcti_Receive(ctx, &size, buf_out, TSS2_TCTI_TIMEOUT_BLOCK);

    assert_true(rc == TSS2_TCTI_RC_TRY_AGAIN);
}
/*
 * A test case for poll io-error
 */
static void
tcti_device_poll_io_error(void **state) {
    TSS2_TCTI_CONTEXT        *ctx = (TSS2_TCTI_CONTEXT *)*state;
    TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_common_context_cast(ctx);
    TSS2_RC                   rc;
    /* output buffer for response */
    uint8_t buf_out[BUF_SIZE] = { 0 };
    size_t  size = BUF_SIZE;

    /* Keep state machine check in `receive` from returning error. */
    tcti_common->state = TCTI_STATE_RECEIVE;
    will_return(__wrap_poll, -1);

    rc = Tss2_Tcti_Receive(ctx, &size, buf_out, TSS2_TCTI_TIMEOUT_BLOCK);

    assert_true(rc == TSS2_TCTI_RC_IO_ERROR);
}

int
main(int argc, char *argv[]) {
#if _FILE_OFFSET_BITS == 64
    // Would produce cmocka error
    LOG_WARNING("_FILE_OFFSET == 64 would produce cmocka errors.");
    return EXIT_SKIP;
#endif

    const struct CMUnitTest tests[] = {
        cmocka_unit_test(tcti_device_init_all_null_test),
        cmocka_unit_test(tcti_device_init_size_test),
        cmocka_unit_test(tcti_device_init_conf_fail),
        cmocka_unit_test(tcti_device_init_conf_default_fail),
        cmocka_unit_test(tcti_device_init_conf_default_success),
        cmocka_unit_test_setup_teardown(tcti_device_get_poll_handles_test, tcti_device_setup,
                                        tcti_device_teardown),
        cmocka_unit_test_setup_teardown(tcti_device_receive_null_size_test, tcti_device_setup,
                                        tcti_device_teardown),
        cmocka_unit_test_setup_teardown(tcti_device_receive_success, tcti_device_setup,
                                        tcti_device_teardown),
        cmocka_unit_test_setup_teardown(tcti_device_receive_eof_test, tcti_device_setup,
                                        tcti_device_teardown),
        cmocka_unit_test_setup_teardown(tcti_device_receive_buffer_lt_response, tcti_device_setup,
                                        tcti_device_teardown),
        cmocka_unit_test_setup_teardown(tcti_device_transmit_success, tcti_device_setup,
                                        tcti_device_teardown),
        cmocka_unit_test_setup_teardown(tcti_device_poll_success, tcti_device_setup,
                                        tcti_device_teardown),
        cmocka_unit_test_setup_teardown(tcti_device_poll_timeout, tcti_device_setup,
                                        tcti_device_teardown),
        cmocka_unit_test_setup_teardown(tcti_device_poll_io_error, tcti_device_setup,
                                        tcti_device_teardown),
    };
    return cmocka_run_group_tests(tests, NULL, NULL);
}
