/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Copyright (c) 2023 Infineon Technologies AG
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include <setjmp.h>
#include <cmocka.h>

#include "tss2_tcti.h"
#include "tss2_tcti_i2c_ftdi.h"

#include "tss2-tcti/tcti-common.h"
#include "tss2-tcti/tcti-i2c-ftdi.h"
#include "tss2-tcti/tcti-i2c-helper.h"
#include "util/key-value-parse.h"

typedef enum {
    I2C_IDLE = 0,
    /* Read sequence */
    R_I2C_START_0,
    R_I2C_WRITE_0,
    R_I2C_GET_ACK_0,
    R_I2C_STOP_0,
    R_I2C_START_1,
    R_I2C_WRITE_1,
    R_I2C_GET_ACK_1,
    R_I2C_SEND_ACKS,
    R_I2C_READ_0,
    R_I2C_SEND_NACKS,
    R_I2C_READ_1,
    R_I2C_STOP_1,
    R_I2C_LAST,
    /* Write sequence */
    W_I2C_START,
    W_I2C_WRITE_0,
    W_I2C_GET_ACK,
    W_I2C_WRITE_1,
    W_I2C_STOP,
    W_I2C_LAST
} i2c_state_t;

typedef enum {
    /* Tss2_Tcti_I2c_Ftdi_Init () */
    R_TPM_DID_VID = 0,
    R_TPM_INTERFACE_CAP,
    R_TPM_ACCESS,
    R_TPM_CSUM_ENABLE,
    W_TPM_CSUM_ENABLE,
    R_TPM_STS_00,
    R_TPM_RID,
    /* TSS2_TCTI_TRANSMIT () */
    W_TPM_STS_00,
    R_TPM_STS_01,
    R_TPM_STS_02,
    W_TPM_FIFO,
    R_TPM_CSUM_00,
    W_TPM_STS_01,
    /* TSS2_TCTI_RECEIVE () */
    R_TPM_STS_03,
    R_TPM_STS_04,
    R_TPM_FIFO_00,
    R_TPM_STS_05,
    R_TPM_FIFO_01,
    R_TPM_STS_06,
    R_TPM_FIFO_02,
    R_TPM_STS_07,
    R_TPM_CSUM_01,
    W_TPM_STS_02,
} tpm_state_t;

static struct m_state_t {
    tpm_state_t tpm;
    i2c_state_t i2c;
} m_state = {R_TPM_DID_VID, I2C_IDLE};

static const uint8_t R_TPM_DID_VID_DATA[] = {0xd1, 0x15, 0x1b, 0x00};
static const uint8_t R_TPM_INTERFACE_CAP_DATA[] = {0x82, 0x00, 0xe0, 0x1a};
static const uint8_t R_TPM_ACCESS_DATA[] = {0xa1};
static const uint8_t R_TPM_CSUM_ENABLE_DATA[] = {0x00};
static const uint8_t R_TPM_RID_DATA[] = {0x00};
static const uint8_t R_TPM_STS_00_01_DATA[] = {TCTI_I2C_HELPER_TPM_STS_COMMAND_READY, 0x00, 0x00, 0x00};
static const uint8_t R_TPM_STS_02_05_DATA[] = {0x00, 0x40, 0x00, 0x00};
static const uint8_t R_TPM_STS_04_06_DATA[] = {TCTI_I2C_HELPER_TPM_STS_VALID | TCTI_I2C_HELPER_TPM_STS_DATA_AVAIL,
                                               0x00, 0x00, 0x00};
static const uint8_t R_TPM_CSUM_DATA[] = {0xf7, 0x4b}; /* CRC-16 (KERMIT) of RW_TPM_FIFO_DATA */
static const uint8_t R_TPM_STS_03_07_DATA[] = {TCTI_I2C_HELPER_TPM_STS_VALID, 0x00, 0x00, 0x00};
static const uint8_t W_TPM_STS_00_02_DATA[] = {TCTI_I2C_HELPER_TPM_STS_COMMAND_READY, 0x00, 0x00, 0x00};
static const uint8_t W_TPM_STS_01_DATA[] = {TCTI_I2C_HELPER_TPM_STS_GO, 0x00, 0x00, 0x00};
static const uint8_t W_TPM_CSUM_ENABLE_DATA[] = {0x01};
static const uint8_t RW_TPM_FIFO_DATA[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xde, 0xad, 0xbe, 0xef};

static struct mpsse_context *_mpsse;

static void state_machine (void)
{
    while (true) {
        switch (m_state.tpm) {
        case R_TPM_DID_VID:
        case R_TPM_INTERFACE_CAP:
        case R_TPM_CSUM_ENABLE:
        case R_TPM_CSUM_00:
        case R_TPM_CSUM_01:
        case R_TPM_ACCESS:
        case R_TPM_STS_00:
        case R_TPM_STS_01:
        case R_TPM_STS_02:
        case R_TPM_STS_03:
        case R_TPM_STS_04:
        case R_TPM_STS_05:
        case R_TPM_STS_06:
        case R_TPM_STS_07:
        case R_TPM_RID:
        case R_TPM_FIFO_00:
        case R_TPM_FIFO_01:
        case R_TPM_FIFO_02:
            if (m_state.i2c == I2C_IDLE) {
                /* Start a new read sequence */
                m_state.i2c = R_I2C_START_0;
                goto exit;
            } else if (m_state.i2c == R_I2C_LAST - 1) {
                /* Read sequence has ended */
                m_state.i2c = I2C_IDLE;
                m_state.tpm++;
            } else {
                /* Amidst a read sequence */
                m_state.i2c++;
                goto exit;
            }
            break;
        case W_TPM_FIFO:
        case W_TPM_CSUM_ENABLE:
        case W_TPM_STS_00:
        case W_TPM_STS_01:
        case W_TPM_STS_02:
            if (m_state.i2c == I2C_IDLE) {
                /* Start a new write sequence */
                m_state.i2c = W_I2C_START;
                goto exit;
            } else if (m_state.i2c == W_I2C_LAST - 1) {
                /* Write sequence has ended */
                m_state.i2c = I2C_IDLE;
                m_state.tpm++;
            } else {
                /* Amidst a write sequence */
                m_state.i2c++;
                goto exit;
            }
            break;
        default:
            assert_true (false);
            return;
        }
    }

exit:
    return;
}

/*
 * Mock function select
 */
int __wrap_select (int nfds, fd_set *readfds,
                   fd_set *writefds,
                   fd_set *exceptfds,
                   struct timeval *timeout)
{

    assert_int_equal (nfds, 0);
    assert_null (readfds);
    assert_null (writefds);
    assert_null (exceptfds);
    assert_non_null (timeout);

    return 0;
}

/*
 * Mock function gettimeofday
 */
int __wrap_gettimeofday (struct timeval *tv,
                         struct timezone *tz)
{
    assert_null (tz);
    assert_non_null (tv);

    tv->tv_sec = 0;
    tv->tv_usec = 0;

    return 0;
}

/*
 * Mock function MPSSE
 */
struct mpsse_context *__wrap_MPSSE (enum modes mode, int freq, int endianess)
{
    assert_int_equal (mode, I2C);
    assert_int_equal (freq, ONE_HUNDRED_KHZ);
    assert_int_equal (endianess, MSB);

    _mpsse = malloc (sizeof (struct mpsse_context));

    return _mpsse;
}

/*
 * Mock function Start
 */
int __wrap_Start (struct mpsse_context *mpsse)
{
    assert_ptr_equal (mpsse, _mpsse);
    state_machine ();
    assert_true ((m_state.i2c == R_I2C_START_0) ||
                 (m_state.i2c == R_I2C_START_1) ||
                 (m_state.i2c == W_I2C_START));
    return MPSSE_OK;
}

/*
 * Mock function Stop
 */
int __wrap_Stop (struct mpsse_context *mpsse)
{
    assert_ptr_equal (mpsse, _mpsse);
    state_machine ();
    assert_true ((m_state.i2c == R_I2C_STOP_0) ||
                 (m_state.i2c == R_I2C_STOP_1) ||
                 (m_state.i2c == W_I2C_STOP));
    return MPSSE_OK;
}

/*
 * Mock function Close
 */
void __wrap_Close (struct mpsse_context *mpsse)
{
    assert_ptr_equal (mpsse, _mpsse);
    free (_mpsse);
    _mpsse = NULL;
}

/*
 * Mock function Read
 */
char *__wrap_Read (struct mpsse_context *mpsse, int size)
{
    const uint8_t *payload = NULL;
    int payload_size = 0;
    char *out = NULL;

    assert_ptr_equal (mpsse, _mpsse);
    state_machine ();

    switch (m_state.tpm) {
    case R_TPM_DID_VID:
        payload = R_TPM_DID_VID_DATA;
        payload_size = sizeof (R_TPM_DID_VID_DATA);
        break;
    case R_TPM_INTERFACE_CAP:
        payload = R_TPM_INTERFACE_CAP_DATA;
        payload_size = sizeof (R_TPM_INTERFACE_CAP_DATA);
        break;
    case R_TPM_CSUM_ENABLE:
        payload = R_TPM_CSUM_ENABLE_DATA;
        payload_size = sizeof (R_TPM_CSUM_ENABLE_DATA);
        break;
    case R_TPM_CSUM_00:
    case R_TPM_CSUM_01:
        payload = R_TPM_CSUM_DATA;
        payload_size = sizeof (R_TPM_CSUM_DATA);
        break;
    case R_TPM_ACCESS:
        payload = R_TPM_ACCESS_DATA;
        payload_size = sizeof (R_TPM_ACCESS_DATA);
        break;
    case R_TPM_STS_00:
    case R_TPM_STS_01:
        payload = R_TPM_STS_00_01_DATA;
        payload_size = sizeof (R_TPM_STS_00_01_DATA);
        break;
    case R_TPM_STS_02:
    case R_TPM_STS_05:
        payload = R_TPM_STS_02_05_DATA;
        payload_size = sizeof (R_TPM_STS_02_05_DATA);
        break;
    case R_TPM_STS_03:
    case R_TPM_STS_07:
        payload = R_TPM_STS_03_07_DATA;
        payload_size = sizeof (R_TPM_STS_03_07_DATA);
        break;
    case R_TPM_STS_04:
    case R_TPM_STS_06:
        payload = R_TPM_STS_04_06_DATA;
        payload_size = sizeof (R_TPM_STS_04_06_DATA);
        break;
    case R_TPM_RID:
        payload = R_TPM_RID_DATA;
        payload_size = sizeof (R_TPM_RID_DATA);
        break;
    case R_TPM_FIFO_00:
        payload = RW_TPM_FIFO_DATA;
        payload_size = TCTI_I2C_HELPER_RESP_HEADER_SIZE;
        break;
    case R_TPM_FIFO_01:
        payload = RW_TPM_FIFO_DATA + TCTI_I2C_HELPER_RESP_HEADER_SIZE;
        payload_size = sizeof (RW_TPM_FIFO_DATA) - 1 - TCTI_I2C_HELPER_RESP_HEADER_SIZE;
        break;
    case R_TPM_FIFO_02:
        payload = RW_TPM_FIFO_DATA + sizeof (RW_TPM_FIFO_DATA) - 1;
        payload_size = 1;
        break;
    default:
        assert_true (false);
    }

    switch (m_state.i2c) {
        case R_I2C_READ_0:
            assert_int_equal (size, payload_size - 1);
            out = malloc (size);
            assert_non_null (out);
            memcpy (out, payload, payload_size - 1);
            return out;
            break;
        case R_I2C_READ_1:
            assert_int_equal (size, 1);
            out = malloc (1);
            assert_non_null (out);
            memcpy (out, payload + payload_size - 1, 1);
            return out;
            break;
        default:
            assert_true (false);
    }

    return NULL;
}

/*
 * Mock function Write
 */
int __wrap_Write (struct mpsse_context *mpsse, char *data, int size)
{
    const uint8_t *payload = NULL;
    int payload_size = 0;
    uint8_t addr[2] = {I2C_DEV_ADDR_DEFAULT << 1, 0x0};

    assert_ptr_equal (mpsse, _mpsse);
    state_machine ();

    switch (m_state.tpm) {
    case R_TPM_DID_VID:
        addr[1] = TCTI_I2C_HELPER_TPM_DID_VID_REG;
        break;
    case R_TPM_INTERFACE_CAP:
        addr[1] = TCTI_I2C_HELPER_TPM_INTERFACE_CAPABILITY_REG;
        break;
    case R_TPM_CSUM_ENABLE:
        addr[1] = TCTI_I2C_HELPER_TPM_DATA_CSUM_ENABLE_REG;
        break;
    case R_TPM_CSUM_00:
    case R_TPM_CSUM_01:
        addr[1] = TCTI_I2C_HELPER_TPM_DATA_CSUM_REG;
        break;
    case R_TPM_ACCESS:
        addr[1] = TCTI_I2C_HELPER_TPM_ACCESS_REG;
        break;
    case R_TPM_STS_00:
    case R_TPM_STS_01:
    case R_TPM_STS_02:
    case R_TPM_STS_03:
    case R_TPM_STS_04:
    case R_TPM_STS_05:
    case R_TPM_STS_06:
    case R_TPM_STS_07:
        addr[1] = TCTI_I2C_HELPER_TPM_STS_REG;
        break;
    case R_TPM_RID:
        addr[1] = TCTI_I2C_HELPER_TPM_RID_REG;
        break;
    case R_TPM_FIFO_00:
    case R_TPM_FIFO_01:
    case R_TPM_FIFO_02:
        addr[1] = TCTI_I2C_HELPER_TPM_DATA_FIFO_REG;
        break;
    case W_TPM_STS_00:
    case W_TPM_STS_02:
        addr[1] = TCTI_I2C_HELPER_TPM_STS_REG;
        payload = W_TPM_STS_00_02_DATA;
        payload_size = sizeof (W_TPM_STS_00_02_DATA);
        break;
    case W_TPM_STS_01:
        addr[1] = TCTI_I2C_HELPER_TPM_STS_REG;
        payload = W_TPM_STS_01_DATA;
        payload_size = sizeof (W_TPM_STS_01_DATA);
        break;
    case W_TPM_CSUM_ENABLE:
        addr[1] = TCTI_I2C_HELPER_TPM_DATA_CSUM_ENABLE_REG;
        payload = W_TPM_CSUM_ENABLE_DATA;
        payload_size = sizeof (W_TPM_CSUM_ENABLE_DATA);
        break;
    case W_TPM_FIFO:
        addr[1] = TCTI_I2C_HELPER_TPM_DATA_FIFO_REG;
        payload = RW_TPM_FIFO_DATA;
        payload_size = sizeof (RW_TPM_FIFO_DATA);
        break;
    default:
        assert_true (false);
    }

    switch (m_state.tpm) {
    case R_TPM_DID_VID:
    case R_TPM_INTERFACE_CAP:
    case R_TPM_CSUM_ENABLE:
    case R_TPM_CSUM_00:
    case R_TPM_CSUM_01:
    case R_TPM_ACCESS:
    case R_TPM_STS_00:
    case R_TPM_STS_01:
    case R_TPM_STS_02:
    case R_TPM_STS_03:
    case R_TPM_STS_04:
    case R_TPM_STS_05:
    case R_TPM_STS_06:
    case R_TPM_STS_07:
    case R_TPM_RID:
    case R_TPM_FIFO_00:
    case R_TPM_FIFO_01:
    case R_TPM_FIFO_02:
        switch (m_state.i2c) {
            case R_I2C_WRITE_0:
                assert_int_equal (size, 2);
                assert_int_equal (strncmp ((const void *)addr, data, 2), 0);
                break;
            case R_I2C_WRITE_1:
                assert_int_equal (size, 1);
                assert_int_equal ((uint8_t)data[0], addr[0] | 0x01);
                break;
            default:
                assert_true (false);
        }
        break;
    case W_TPM_STS_00:
    case W_TPM_STS_01:
    case W_TPM_STS_02:
    case W_TPM_FIFO:
    case W_TPM_CSUM_ENABLE:
        switch (m_state.i2c) {
            case W_I2C_WRITE_0:
                assert_int_equal (size, 2);
                assert_int_equal (strncmp ((const void *)addr, data, 2), 0);
                break;
            case W_I2C_WRITE_1:
                assert_int_equal (size, payload_size);
                assert_int_equal (strncmp ((const char *)payload, data, payload_size), 0);
                break;
            default:
                assert_true (false);
        }
        break;
    default:
        assert_true (false);
    }

    return 0;
}

/*
 * Mock function GetAck
 */
int __wrap_GetAck (struct mpsse_context *mpsse)
{
    assert_ptr_equal (mpsse, _mpsse);
    state_machine ();
    assert_true ((m_state.i2c == R_I2C_GET_ACK_0) ||
                 (m_state.i2c == R_I2C_GET_ACK_1) ||
                 (m_state.i2c == W_I2C_GET_ACK));
    return ACK;
}

/*
 * Mock function SendAcks
 */
void __wrap_SendAcks (struct mpsse_context *mpsse)
{
    assert_ptr_equal (mpsse, _mpsse);
    state_machine ();
    assert_true (m_state.i2c == R_I2C_SEND_ACKS);
}

/*
 * Mock function SendNacks
 */
void __wrap_SendNacks (struct mpsse_context *mpsse)
{
    assert_ptr_equal (mpsse, _mpsse);
    state_machine ();
    assert_true (m_state.i2c == R_I2C_SEND_NACKS);
}

/*
 * The test will call Tss2_Tcti_I2c_Ftdi_Init(),
 * which will perform several tasks including reading
 * the TPM_DID_VID, checking locality, reading TPM_STS,
 * and reading TPM_RID before exiting the Init function.
 * The TSS2_TCTI_CONTEXT core functions will be tested as well.
 * For testing purposes, the TPM responses are hardcoded.
 */
static void
tcti_i2c_generic_test (void **state)
{
    TSS2_RC rc;
    size_t size;
    uint8_t response[10] = {0};
    TSS2_TCTI_CONTEXT* tcti_ctx;

    m_state.tpm = 0;
    m_state.i2c = 0;

    /* Get requested TCTI context size */
    rc = Tss2_Tcti_I2c_Ftdi_Init (NULL, &size, NULL);
    assert_int_equal (rc, TSS2_RC_SUCCESS);

    /* Allocate TCTI context size */
    tcti_ctx = (TSS2_TCTI_CONTEXT*) calloc (1, size);
    assert_non_null (tcti_ctx);

    /* Initialize TCTI context */
    rc = Tss2_Tcti_I2c_Ftdi_Init (tcti_ctx, &size, NULL);
    assert_int_equal (rc, TSS2_RC_SUCCESS);

    /* Verify the TCTI core functions */
    assert_int_equal (TSS2_TCTI_MAGIC (tcti_ctx), TCTI_I2C_HELPER_MAGIC);
    assert_int_equal (TSS2_TCTI_VERSION (tcti_ctx), TCTI_VERSION);
    assert_int_equal (
        TSS2_TCTI_TRANSMIT (tcti_ctx) (
            tcti_ctx, sizeof (RW_TPM_FIFO_DATA), RW_TPM_FIFO_DATA
        ),
        TSS2_RC_SUCCESS
    );
    size = 0;
    assert_int_equal (
        TSS2_TCTI_RECEIVE (tcti_ctx) (
            tcti_ctx, &size, NULL, 200
        ),
        TSS2_RC_SUCCESS
    );
    assert_int_equal (size, sizeof (RW_TPM_FIFO_DATA));
    assert_int_equal (
        TSS2_TCTI_RECEIVE (tcti_ctx) (
            tcti_ctx, &size, response, 200
        ),
        TSS2_RC_SUCCESS
    );
    assert_int_equal (TSS2_TCTI_CANCEL (tcti_ctx) (NULL), TSS2_TCTI_RC_NOT_IMPLEMENTED);
    assert_int_equal (TSS2_TCTI_GET_POLL_HANDLES (tcti_ctx) (NULL, NULL, NULL), TSS2_TCTI_RC_NOT_IMPLEMENTED);
    assert_int_equal (TSS2_TCTI_SET_LOCALITY (tcti_ctx) (NULL, 0), TSS2_TCTI_RC_NOT_IMPLEMENTED);
    assert_int_equal (TSS2_TCTI_MAKE_STICKY (tcti_ctx) (NULL, NULL, 0), TSS2_TCTI_RC_NOT_IMPLEMENTED);

    /* Clean up */
    TSS2_TCTI_FINALIZE (tcti_ctx) (tcti_ctx);
    free (tcti_ctx);
}

int
main (int   argc,
      char *argv[])
{
    const struct CMUnitTest tests[] = {
        cmocka_unit_test (tcti_i2c_generic_test),
    };
    return cmocka_run_group_tests (tests, NULL, NULL);
}
