#include "test_TA_driver_client.h"

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <tee_internal_api.h>

#ifdef DEBUG
#include <errno.h>
#define LOG_PREFIX                      "DRIVER_CLIENT_DBG: "
#define print_info(format, args...)     printf(LOG_PREFIX format, ##args)
#else
#define print_info(format, args...)
#endif

enum cmd_drv_client
{
    CMD_OPEN,
    CMD_CLOSE,
    CMD_READ,
    CMD_WRITE,
    CMD_IOCTL,
    CMD_MAX
};

#define BUF_SIZE 128

enum cmd_ioctl
{
    CMD_IOCTL_NORMAL,
    CMD_IOCTL_PROCESS_DATA,
    CMD_IOCTL_COMPLEX_DATA,
    CMD_IOCTL_MAX
};

struct ref_array {
    char *str;
    uint64_t val;
} __attribute__((packed));

struct ref_data {
    uint32_t val;
    char *buf;
    uint32_t cnt0;
    uint32_t cnt1;
    struct ref_array arr0[3];
    uint32_t arr1[3];
} __attribute__((packed));

static const char test_data[BUF_SIZE] = "test_string_for_passing_to_driver";
static const char drv_path[] = "/dev/my_driver";
static int drv_fd = -1;

TEE_Result drv_client_open(void)
{
    int ret = TEE_SUCCESS;

    print_info("drv_client_open(): do open\n");
    drv_fd = open(drv_path, O_RDWR, 0);
    if (drv_fd < 0) {
        print_info("drv_client_open(): open error, errno = %d\n", errno);
        ret = TEE_ERROR_GENERIC;
    }
    print_info("drv_client_open(): done\n");
    return ret;
}

TEE_Result drv_client_close(void)
{
    int ret = TEE_SUCCESS;
    int drv_ret = 0;

    print_info("drv_client_close(): do close\n");
    drv_ret = close(drv_fd);
    if (drv_ret < 0) {
        print_info("drv_client_close(): close error, errno = %d\n", errno);
        ret = TEE_ERROR_GENERIC;
    }
    print_info("drv_client_close(): done\n");
    return ret;
}

TEE_Result drv_client_ioctl(void)
{
    int ret = TEE_SUCCESS;
    int drv_ret = 0;
    char data[16] = "Hello!!!";
    char str[3][4] = { "XXX\0", "YYY\0", "ZZZ\0" };

    struct ref_data ref_data = {
        .val = 0xAAAAAAAA,
        .buf = data,
        .cnt0 = 3,
        .cnt1 = 3,
        .arr0 = { { str[0], 0x11 }, { str[1], 0x22 }, { str[2], 0x33 } },
        .arr1 = { 0x38, 0x3838, 0x383838 },
    };

    print_info("drv_client_ioctl(): do ioctl, data = '%s'\n", data);
    drv_ret = ioctl(drv_fd, CMD_IOCTL_PROCESS_DATA, (unsigned long)data);
    if (drv_ret < 0) {
        print_info("drv_client_ioctl(): ioctl error, errno = %d\n", errno);
        ret = TEE_ERROR_GENERIC;
    }

    drv_ret = ioctl(drv_fd, CMD_IOCTL_COMPLEX_DATA, (unsigned long)&ref_data);
    if (!drv_ret) {
        /* Driver have to modify variables, check it here */
        print_info("ret val=%x\n", ref_data.val);
        if ((ref_data.val & 0xF0000000) != 0xF0000000) {
            print_info("check failed\n");
            drv_ret--;
        }
        for (unsigned int i = 0; i < ref_data.cnt0; i++) {
#if defined(__arm__)
            print_info("ref_array[%u]: {%s, %#llx}\n",
                   i,
                   ref_data.arr0[i].str,
                   ref_data.arr0[i].val);
#elif defined(__aarch64__)
            print_info("ref_array[%u]: {%s, %#lx}\n",
                   i,
                   ref_data.arr0[i].str,
                   ref_data.arr0[i].val);
#else
#error "Unsupported architecture"
#endif
            if ((ref_data.arr0[i].val & 0xF0000000) != 0xF0000000) {
                print_info("check failed\n");
                drv_ret--;
            }
        }
        for (unsigned int i = 0; i < ref_data.cnt1; i++) {
            print_info("arr1[%u]: {%#x}\n", i, ref_data.arr1[i]);
            if ((ref_data.arr1[i] & 0xF0000000) != 0xF0000000) {
                print_info("check failed\n");
                drv_ret--;
            }
        }
        if (strncmp(data, "World!!!", sizeof(data)) != 0) {
            print_info("data check failed\n");
            drv_ret--;
        }
    }
    if (drv_ret < 0) {
        print_info("drv_client_ioctl(): ioctl error, errno = %d\n", errno);
        ret = TEE_ERROR_GENERIC;
    }
    print_info("drv_client_ioctl(): result string = '%s'\n", data);

    return ret;
}

static char buf[BUF_SIZE] = {0};

TEE_Result drv_client_read(void)
{
    int ret = TEE_SUCCESS;
    int drv_ret = 0;

    print_info("drv_client_read(): do read\n");
    drv_ret = read(drv_fd, buf, BUF_SIZE);
    if (drv_ret < 0) {
        print_info("drv_client_read(): read error, errno = %d\n", errno);
        ret = TEE_ERROR_GENERIC;
    } else {
        print_info("drv_client_read(): message received from driver: %s\n", buf);
    }

    return ret;
}

TEE_Result drv_client_write(void)
{
    int ret = TEE_SUCCESS;
    int drv_ret = 0;

    print_info("drv_client_write(): write message to driver : %s\n", test_data);

    drv_ret = write(drv_fd, test_data, strnlen(test_data, BUF_SIZE));
    if (drv_ret < 0) {
        print_info("drv_client_write(): write error, errno = %d\n", errno);
        ret = TEE_ERROR_GENERIC;
    }

    print_info("drv_client_write(): done\n");
    return ret;
}

TEE_Result TA_CreateEntryPoint(void)
{
    return TEE_SUCCESS;
}

void TA_DestroyEntryPoint(void)
{
    return;
}

TEE_Result TA_OpenSessionEntryPoint(uint32_t paramTypes,
                                    TEE_Param params[4], void **sessionContext)
{
    (void) paramTypes;
    (void) params;
    (void) sessionContext;

    return TEE_SUCCESS;
}

void TA_CloseSessionEntryPoint(void *sessionContext)
{
    (void) sessionContext;
    return;
}

TEE_Result TA_InvokeCommandEntryPoint(void *sessionContext,
                                      uint32_t commandID, uint32_t paramTypes,
                                      TEE_Param params[4])
{
    TEE_Result ret = TEE_SUCCESS;
    (void) sessionContext;
    (void) paramTypes;
    (void) params;

    switch (commandID) {
        case CMD_OPEN:
            ret = drv_client_open();
            break;
        case CMD_READ:
            ret = drv_client_read();
            break;
        case CMD_WRITE:
            ret = drv_client_write();
            break;
        case CMD_IOCTL:
            ret = drv_client_ioctl();
            break;
        case CMD_CLOSE:
            ret = drv_client_close();
            break;
        default:
             break;
    }
    return ret;
}
