#define _GNU_SOURCE
#include <net/if.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
#include <sgx_report.h>
#include <sgx_quote.h>
#include <sgx_key.h>
#ifndef OCCLUM_DISABLE_DCAP
#include <sgx_ql_quote.h>
#include <sgx_qve_header.h>
#endif
#include "test.h"

#define SGX_LEAF 0x12
#define SGX2_SHIFT 1

typedef struct t_cpuid {
    unsigned int eax;
    unsigned int ebx;
    unsigned int ecx;
    unsigned int edx;
} t_cpuid_t;

static inline void native_cpuid(int leaf, int subleaf, t_cpuid_t *p) {
    memset(p, 0, sizeof(*p));
    /* ecx is often an input as well as an output. */
    asm volatile("cpuid"
                 : "=a" (p->eax),
                 "=b" (p->ebx),
                 "=c" (p->ecx),
                 "=d" (p->edx)
                 : "a" (leaf), "c" (subleaf));
}

// check the hw is SGX1 or SGX2
int is_sgx2_supported() {
    t_cpuid_t cpu_info = {0, 0, 0, 0};
    native_cpuid(SGX_LEAF, 0, &cpu_info);
    if (!(cpu_info.eax & (1 << SGX2_SHIFT))) {
        return 0;
    }
    return 1;
}

// ============================================================================
// Test cases for TTY ioctls
// ============================================================================

int test_tty_ioctl_TIOCGWINSZ(void) {
    struct winsize winsize;
    if (isatty(STDOUT_FILENO)) {
        if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) < 0) {
            THROW_ERROR("failed to ioctl TIOCGWINSZ");
        }
    } else {
        // FIXME: /dev/tty should be opened. But it has not been implemented in Occlum yet.
        // So we just skip this test if STDOUT is redirected.
        printf("Warning: test_tty_ioctl_TIOCGWINSZ is skipped\n");
    }
    return 0;
}

int test_ioctl_TCGETS_TCSETS(void) {
    struct termios term;
    // FIXME: /dev/tty should be opened. But it has not been implemented in Occlum yet.
    // So we just skip this test if STDOUT is redirected.
    if (!isatty(STDOUT_FILENO)) {
        printf("Warning: test_tty_ioctl_TIOCGWINSZ is skipped\n");
        return 0;
    }

    if (ioctl(STDOUT_FILENO, TCGETS, &term) < 0) {
        THROW_ERROR("failed to ioctl TCGETS");
    }

    if (ioctl(STDOUT_FILENO, TCSETS, &term) < 0) {
        THROW_ERROR("failed to ioctl TCSETS");
    }

    const char *file_path = "/root/test_ioctl.txt";
    int flags = O_RDONLY | O_CREAT | O_TRUNC;
    int mode = 00666;
    int fd = open(file_path, flags, mode);
    if (fd < 0) {
        THROW_ERROR("failed to open test file");
    }

    int pipefds[2];
    int ret = pipe(pipefds);
    if (ret != 0) {
        THROW_ERROR("failed to create pipe");
    }

    ret = ioctl(fd, TCGETS, &term);
    if (ret != -1 || errno != ENOTTY) {
        THROW_ERROR("failed catch error");
    }

    ret = ioctl(pipefds[0], TCGETS, &term);
    if (ret != -1 || errno != ENOTTY) {
        THROW_ERROR("failed catch error");
    }

    close(fd);
    close(pipefds[0]);
    close(pipefds[1]);

    return 0;
}

// ============================================================================
// Test cases for SGX ioctls
// ============================================================================

typedef struct {
    sgx_report_data_t           report_data;        // input
    sgx_quote_sign_type_t       quote_type;         // input
    sgx_spid_t                  spid;               // input
    sgx_quote_nonce_t           nonce;              // input
    const uint8_t              *sigrl_ptr;          // input (optional)
    uint32_t                    sigrl_len;          // input (optional)
    uint32_t                    quote_buf_len;      // input
    union {
        uint8_t                *as_buf;
        sgx_quote_t            *as_quote;
    } quote;                                        // output
} sgxioc_gen_epid_quote_arg_t;

typedef struct {
    const sgx_target_info_t    *target_info;        // input (optinal)
    const sgx_report_data_t    *report_data;        // input (optional)
    sgx_report_t               *report;             // output
} sgxioc_create_report_arg_t;

typedef struct {
    const sgx_key_request_t    *key_request;       // Input
    sgx_key_128bit_t           *key;              // Output
} sgxioc_get_key_arg_t;

#ifndef OCCLUM_DISABLE_DCAP
typedef struct {
    sgx_report_data_t      *report_data; // input
    uint32_t               *quote_len;   // input/output
    uint8_t                *quote_buf;   // output
} sgxioc_gen_dcap_quote_arg_t;

typedef struct {
    const uint8_t                 *quote_buf;                    // input
    uint32_t                      quote_size;                    // input
    uint32_t                      *collateral_expiration_status; // output
    sgx_ql_qv_result_t            *quote_verification_result;    // output
    uint32_t                      supplemental_data_size;        // input
    uint8_t                       *supplemental_data;            // output
} sgxioc_ver_dcap_quote_arg_t;
#endif

#define SGXIOC_IS_EDMM_SUPPORTED          _IOR('s', 0, int)
#define SGXIOC_GET_EPID_GROUP_ID          _IOR('s', 1, sgx_epid_group_id_t)
#define SGXIOC_GEN_EPID_QUOTE             _IOWR('s', 2, sgxioc_gen_epid_quote_arg_t)
#define SGXIOC_SELF_TARGET                _IOR('s', 3, sgx_target_info_t)
#define SGXIOC_CREATE_REPORT              _IOWR('s', 4, sgxioc_create_report_arg_t)
#define SGXIOC_VERIFY_REPORT              _IOW('s', 5, sgx_report_t)
#define SGXIOC_DETECT_DCAP_DRIVER         _IOR('s', 6, int)

#ifndef OCCLUM_DISABLE_DCAP
#define SGXIOC_GET_DCAP_QUOTE_SIZE        _IOR('s', 7, uint32_t)
#define SGXIOC_GEN_DCAP_QUOTE             _IOWR('s', 8, sgxioc_gen_dcap_quote_arg_t)
#define SGXIOC_GET_DCAP_SUPPLEMENTAL_SIZE _IOR('s', 9, uint32_t)
#define SGXIOC_VER_DCAP_QUOTE             _IOWR('s', 10, sgxioc_ver_dcap_quote_arg_t)
#endif

#define SGXIOC_GET_KEY                    _IOWR('s', 11, sgxioc_get_key_arg_t)

// The max number of retries if ioctl returns EBUSY
#define IOCTL_MAX_RETRIES       20

typedef int(*sgx_ioctl_test_body_t)(int sgx_fd);

static int do_SGXIOC_IS_EDMM_SUPPORTED(int sgx_fd) {
    int is_edmm_supported = 0;
    if (ioctl(sgx_fd, SGXIOC_IS_EDMM_SUPPORTED, &is_edmm_supported) < 0) {
        THROW_ERROR("failed to ioctl /dev/sgx");
    }

    printf("    SGX EDMM support: %d\n", is_edmm_supported);
    return 0;
}

static int do_SGXIOC_GET_EPID_GROUP_ID(int sgx_fd) {
    int nretries = 0;
    while (nretries < IOCTL_MAX_RETRIES) {
        sgx_epid_group_id_t epid_group_id = { 0 };
        int ret = ioctl(sgx_fd, SGXIOC_GET_EPID_GROUP_ID, &epid_group_id);
        if (ret == 0) {
            break;
        } else if (errno != EBUSY) {
            THROW_ERROR("failed to ioctl /dev/sgx");
        }

        printf("WARN: /dev/sgx is temporarily busy. Try again after 1 second.");
        sleep(1);
        nretries++;
    }
    if (nretries == IOCTL_MAX_RETRIES) {
        THROW_ERROR("failed to ioctl /dev/sgx due to timeout");
    }
    return 0;
}

static int do_SGXIOC_GEN_QUOTE(int sgx_fd) {
    uint8_t quote_buf[2048] = { 0 };
    sgxioc_gen_epid_quote_arg_t gen_quote_arg = {
        .report_data = { { 0 } },                       // input (empty is ok)
        .quote_type = SGX_LINKABLE_SIGNATURE,           // input
        .spid = { { 0 } },                              // input (empty is ok)
        .nonce = { { 0 } },                             // input (empty is ok)
        .sigrl_ptr = NULL,                              // input (optional)
        .sigrl_len = 0,                                 // input (optional)
        .quote_buf_len = sizeof(quote_buf),             // input
        .quote = { .as_buf = (uint8_t *) quote_buf }    // output
    };
    int nretries = 0;
    while (nretries < IOCTL_MAX_RETRIES) {
        int ret = ioctl(sgx_fd, SGXIOC_GEN_EPID_QUOTE, &gen_quote_arg);
        if (ret == 0) {
            break;
        } else if (errno != EBUSY) {
            THROW_ERROR("failed to ioctl /dev/sgx");
        }

        printf("WARN: /dev/sgx is temporarily busy. Try again after 1 second.");
        sleep(1);
        nretries++;
    }
    if (nretries == IOCTL_MAX_RETRIES) {
        THROW_ERROR("failed to ioctl /dev/sgx due to timeout");
    }

    sgx_quote_t *quote = (sgx_quote_t *)quote_buf;
#ifndef SGX_MODE_HYPER
    if (quote->sign_type != SGX_LINKABLE_SIGNATURE) {
        THROW_ERROR("invalid quote: wrong sign type");
    }
#endif
    if (quote->signature_len == 0) {
        THROW_ERROR("invalid quote: zero-length signature");
    }
    if (memcmp(&gen_quote_arg.report_data, &quote->report_body.report_data,
               sizeof(sgx_report_data_t)) != 0) {
        THROW_ERROR("invalid quote: wrong report data");
    }
    return 0;
}

static int do_sgx_ioctl_test(sgx_ioctl_test_body_t test_body) {
    // Init test
    int sgx_fd;
    if ((sgx_fd = open("/dev/sgx", O_RDONLY)) < 0) {
        THROW_ERROR("failed to open /dev/sgx ");
    }

    // Do test
    int ret = test_body(sgx_fd);

    // Clean up test
    close(sgx_fd);
    return ret;
}

static int do_SGXIOC_SELF_TARGET(int sgx_fd) {
    sgx_target_info_t target_info;
    if (ioctl(sgx_fd, SGXIOC_SELF_TARGET, &target_info) < 0) {
        THROW_ERROR("failed to ioctl /dev/sgx");
    }
    return 0;
}

static int do_SGXIOC_CREATE_AND_VERIFY_REPORT(int sgx_fd) {
    sgx_target_info_t target_info;
    if (ioctl(sgx_fd, SGXIOC_SELF_TARGET, &target_info) < 0) {
        THROW_ERROR("failed to ioctl /dev/sgx");
    }
    sgx_report_data_t report_data;
    sgx_report_t report;

    sgxioc_create_report_arg_t args[] = {
        {
            .target_info = (const sgx_target_info_t *) &target_info,
            .report_data = NULL,
            .report = &report
        },
        {
            .target_info = (const sgx_target_info_t *) &target_info,
            .report_data = (const sgx_report_data_t *) &report_data,
            .report = &report
        }
    };
    for (int arg_i = 0; arg_i < ARRAY_SIZE(args); arg_i++) {
        memset(&report, 0, sizeof(report));
        sgxioc_create_report_arg_t *arg = &args[arg_i];
        if (ioctl(sgx_fd, SGXIOC_CREATE_REPORT, arg) < 0) {
            THROW_ERROR("failed to create report");
        }
        if (ioctl(sgx_fd, SGXIOC_VERIFY_REPORT, &report) < 0) {
            THROW_ERROR("failed to verify report");
        }
    }
    return 0;
}

static int do_SGXIOC_GET_KEY(int sgx_fd) {
    sgx_key_request_t key_request = { 0 };
    sgx_key_128bit_t key = { 0 };

    key_request.key_name = SGX_KEYSELECT_SEAL; // SGX_KEYSELECT_REPORT
    key_request.key_policy = SGX_KEYPOLICY_MRENCLAVE; // SGX_KEYPOLICY_MRSIGNER

    sgxioc_get_key_arg_t args = {
        .key_request = (const sgx_key_request_t *) &key_request,
        .key = &key,
    };
    if (ioctl(sgx_fd, SGXIOC_GET_KEY, &args) < 0) {
        THROW_ERROR("failed to ioctl /dev/sgx");
    }

    printf("key: \n");
    for (int i = 0; i < 16; i++) {
        printf("%x ", key[i]);
    }
    printf("\n");

    return 0;
}

#ifndef OCCLUM_DISABLE_DCAP
#define REPORT_BODY_OFFSET 48
static int generate_and_verify_dcap_quote(int sgx_fd) {
    // get quote size
    uint32_t quote_size = 0;
    if (ioctl(sgx_fd, SGXIOC_GET_DCAP_QUOTE_SIZE, &quote_size) < 0) {
        THROW_ERROR("failed to get quote size");
    }

    // get quote
    uint8_t *quote_buffer = (uint8_t *)malloc(quote_size);
    if (NULL == quote_buffer) {
        THROW_ERROR("Couldn't allocate quote_buffer");
    }
    memset(quote_buffer, 0, quote_size);

    sgx_report_data_t report_data = { 0 };
    char *data = "ioctl DCAP report data example";
    memcpy(report_data.d, data, strlen(data));

    sgxioc_gen_dcap_quote_arg_t gen_quote_arg = {
        .report_data = &report_data,
        .quote_len = &quote_size,
        .quote_buf = quote_buffer
    };

    if (ioctl(sgx_fd, SGXIOC_GEN_DCAP_QUOTE, &gen_quote_arg) < 0) {
        THROW_ERROR("failed to get quote");
    }

    if (memcmp((void *) & ((sgx_report_body_t *)(quote_buffer +
                           REPORT_BODY_OFFSET))->report_data,
               (void *)&report_data, sizeof(sgx_report_data_t)) != 0) {
        THROW_ERROR("mismathced report data");
    }

    uint32_t collateral_expiration_status = 1;
    sgx_ql_qv_result_t quote_verification_result = SGX_QL_QV_RESULT_UNSPECIFIED;

    uint32_t supplemental_size = 0;
    if (ioctl(sgx_fd, SGXIOC_GET_DCAP_SUPPLEMENTAL_SIZE, &supplemental_size) < 0) {
        THROW_ERROR("failed to get supplemental data size");
    }
    uint8_t *supplemental_buffer = (uint8_t *)malloc(supplemental_size);
    if (NULL == supplemental_buffer) {
        THROW_ERROR("Couldn't allocate quote_buffer");
    }
    memset(supplemental_buffer, 0, supplemental_size);

    sgxioc_ver_dcap_quote_arg_t ver_quote_arg = {
        .quote_buf = quote_buffer,
        .quote_size = quote_size,
        .collateral_expiration_status = &collateral_expiration_status,
        .quote_verification_result = &quote_verification_result,
        .supplemental_data_size = supplemental_size,
        .supplemental_data = supplemental_buffer
    };

    if (ioctl(sgx_fd, SGXIOC_VER_DCAP_QUOTE, &ver_quote_arg) < 0) {
        THROW_ERROR("failed to verify quote");
    }

    switch (quote_verification_result) {
        case SGX_QL_QV_RESULT_OK:
            return 0;
        case SGX_QL_QV_RESULT_CONFIG_NEEDED:
        case SGX_QL_QV_RESULT_OUT_OF_DATE:
        case SGX_QL_QV_RESULT_OUT_OF_DATE_CONFIG_NEEDED:
        case SGX_QL_QV_RESULT_SW_HARDENING_NEEDED:
        case SGX_QL_QV_RESULT_CONFIG_AND_SW_HARDENING_NEEDED:
            printf("WARN: App: Verification completed with Non-terminal result: %x\n",
                   quote_verification_result);
            return 0;
        case SGX_QL_QV_RESULT_INVALID_SIGNATURE:
        case SGX_QL_QV_RESULT_REVOKED:
        case SGX_QL_QV_RESULT_UNSPECIFIED:
        default:
            THROW_ERROR("\tError: App: Verification completed with Terminal result: %x\n",
                        quote_verification_result);
    }
}

static int do_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE(int sgx_fd) {
    int is_dcap_driver_installed = 0;
    if (ioctl(sgx_fd, SGXIOC_DETECT_DCAP_DRIVER, &is_dcap_driver_installed) < 0) {
        THROW_ERROR("failed to detect DCAP driver");
    }

    if (is_dcap_driver_installed == 0) {
        printf("Warning: test_sgx_ioctl_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE is skipped\n");
        return 0;
    }

    int nretries = 0;
    while (nretries < IOCTL_MAX_RETRIES) {
        int ret = generate_and_verify_dcap_quote(sgx_fd);
        if (ret == 0) {
            break;
        } else if (errno != EBUSY) {
            THROW_ERROR("failed to ioctl /dev/sgx");
        }

        printf("WARN: /dev/sgx is temporarily busy. Try again after 1 second.");
        sleep(1);
        nretries++;
    }
    if (nretries == IOCTL_MAX_RETRIES) {
        THROW_ERROR("failed to ioctl /dev/sgx due to timeout");
    }

    return 0;
}

int test_sgx_ioctl_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE(void) {
    if (is_sgx2_supported()) {
        return do_sgx_ioctl_test(do_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE);
    } else {
        printf("Warning: test_sgx_ioctl_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE is skipped\n");
        return 0;
    }
}
#endif

int test_sgx_ioctl_SGXIOC_IS_EDMM_SUPPORTED(void) {
    return do_sgx_ioctl_test(do_SGXIOC_IS_EDMM_SUPPORTED);
}

int test_sgx_ioctl_SGXIOC_GET_EPID_GROUP_ID(void) {
    // skip the EPID test on SGX2 HW
    if (is_sgx2_supported()) {
        printf("Warning: test_sgx_ioctl_SGXIOC_GET_EPID_GROUP_ID is skipped\n");
        return 0;
    }
    return do_sgx_ioctl_test(do_SGXIOC_GET_EPID_GROUP_ID);
}

int test_sgx_ioctl_SGXIOC_GEN_EPID_QUOTE(void) {
    // skip the EPID test on SGX2 HW
    if (is_sgx2_supported()) {
        printf("Warning: test_sgx_ioctl_SGXIOC_GEN_EPID_QUOTE is skipped\n");
        return 0;
    }
    return do_sgx_ioctl_test(do_SGXIOC_GEN_QUOTE);
}

int test_sgx_ioctl_SGXIOC_SELF_TARGET(void) {
    return do_sgx_ioctl_test(do_SGXIOC_SELF_TARGET);
}

int test_sgx_ioctl_SGXIOC_CREATE_AND_VERIFY_REPORT(void) {
    return do_sgx_ioctl_test(do_SGXIOC_CREATE_AND_VERIFY_REPORT);
}

int test_sgx_ioctl_SGXIOC_GET_KEY(void) {
    return do_sgx_ioctl_test(do_SGXIOC_GET_KEY);
}

#define CONFIG_SIZE  512
int test_ioctl_SIOCGIFCONF(void) {
    struct ifreq *req;
    struct ifconf conf;
    char *buf = (char *)malloc(CONFIG_SIZE);
    if (buf == NULL) {
        THROW_ERROR("malloc failed");
    }
    memset(buf, 0, CONFIG_SIZE);

    int sock = socket(AF_INET, SOCK_STREAM, 0);

    conf.ifc_len = 0;
    conf.ifc_buf = buf;
    if (ioctl(sock, SIOCGIFCONF, &conf) < 0) {
        close(sock);
        THROW_ERROR("empty length ioctl failed");
    }

    if (conf.ifc_len != 0) {
        close(sock);
        THROW_ERROR("wrong returned length");
    }

    conf.ifc_len = CONFIG_SIZE;
    conf.ifc_buf = 0;
    if (ioctl(sock, SIOCGIFCONF, &conf) < 0) {
        close(sock);
        THROW_ERROR("empty buffer ioctl failed");
    }

    int ret_len = conf.ifc_len;

    // use a larger buffer when the original one is insufficient
    if (ret_len > CONFIG_SIZE) {
        free(buf);

        char *new_buf = (char *)malloc(ret_len);
        if (new_buf == NULL) {
            close(sock);
            THROW_ERROR("malloc failed");
        }
        buf = new_buf;
        memset(buf, 0, ret_len);
    } else {
        conf.ifc_len = CONFIG_SIZE;
    }

    conf.ifc_buf = buf;
    if (ioctl(sock, SIOCGIFCONF, &conf) < 0) {
        close(sock);
        THROW_ERROR("buffer passed ioctl failed");
    }

    if (conf.ifc_len != ret_len) {
        close(sock);
        THROW_ERROR("wrong return length");
    }

    close(sock);

    req = (struct ifreq *)buf;
    int num = conf.ifc_len / sizeof (struct ifreq);

    printf("    interface names got:\n");
    for (int i = 0; i < num; i++) {
        printf("    %d: %s\n", i + 1, req->ifr_name);
        req ++;
    }

    return 0;
}

int test_ioctl_FIONBIO(void) {
    int test_sock[2], sock;
    test_sock[0] = socket(AF_INET, SOCK_STREAM, 0);
    test_sock[1] = socket(AF_UNIX, SOCK_STREAM, 0);

    for (int i = 0; i < 2; i++) {
        sock = test_sock[i];
        int on = 1;
        if (ioctl(sock, FIONBIO, &on) < 0) {
            close(sock);
            THROW_ERROR("ioctl FIONBIO failed");
        }

        int actual_flags = fcntl(sock, F_GETFL);
        if ((actual_flags & O_NONBLOCK) == 0) {
            close(sock);
            THROW_ERROR("failed to check the O_NONBLOCK flag after FIONBIO");
        }

        close(sock);
    }
    return 0;
}

int test_ioctl_FIOCLEX(void) {
    // Open a file with O_CLOEXEC (close-on-exec)
    char *tmp_file = "/tmp/test_fioclex";
    int fd = open(tmp_file, O_CREAT | O_CLOEXEC, 0666);
    if (fd < 0) {
        THROW_ERROR("failed to open the tmp file");
    }

    // change this fd to "no close-on-exec"
    int ret = ioctl(fd, FIONCLEX, NULL);
    if (ret != 0) {
        THROW_ERROR("ioctl FIONCLEX failed");
    }

    int pipefds[2];
    ret = pipe(pipefds);
    if (ret != 0) {
        THROW_ERROR("failed to create pipe");
    }

    // set close on exec on reader end
    ret = ioctl(pipefds[0], FIOCLEX, NULL);
    if (ret != 0) {
        THROW_ERROR("ioctl FIOCLEX failed");
    }

    // construct child process args
    int child_pid, status;
    int child_argc =
        6; // ./nauty_child -t fioclex regular_file_fd pipe_reader_fd pipe_writer_fd
    char **child_argv = calloc(1, sizeof(char *) * (child_argc + 1));
    child_argv[0] = strdup("naughty_child");
    child_argv[1] = strdup("-t");
    child_argv[2] = strdup("fioclex");
    if (asprintf(&child_argv[3], "%d", fd) < 0 ||
            asprintf(&child_argv[4], "%d", pipefds[0]) < 0 ||
            asprintf(&child_argv[5], "%d", pipefds[1]) < 0) {
        THROW_ERROR("failed to call asprintf");
    }

    ret = posix_spawn(&child_pid, "/bin/naughty_child", NULL, NULL, child_argv, NULL);
    if (ret != 0) {
        THROW_ERROR("failed to spawn a child process\n");
    }

    ret = waitpid(child_pid, &status, 0);
    if (ret < 0) {
        THROW_ERROR("failed to wait4 the child process");
    }
    printf("child process %d exit status = %d\n", child_pid, status);

    return 0;
}

// ============================================================================
// Test suite
// ============================================================================

static test_case_t test_cases[] = {
    TEST_CASE(test_tty_ioctl_TIOCGWINSZ),
    TEST_CASE(test_ioctl_TCGETS_TCSETS),
    TEST_CASE(test_sgx_ioctl_SGXIOC_IS_EDMM_SUPPORTED),
    TEST_CASE(test_sgx_ioctl_SGXIOC_GET_EPID_GROUP_ID),
    TEST_CASE(test_sgx_ioctl_SGXIOC_GEN_EPID_QUOTE),
    TEST_CASE(test_sgx_ioctl_SGXIOC_SELF_TARGET),
    TEST_CASE(test_sgx_ioctl_SGXIOC_CREATE_AND_VERIFY_REPORT),
    TEST_CASE(test_sgx_ioctl_SGXIOC_GET_KEY),
#ifndef OCCLUM_DISABLE_DCAP
    TEST_CASE(test_sgx_ioctl_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE),
#endif
    TEST_CASE(test_ioctl_SIOCGIFCONF),
    TEST_CASE(test_ioctl_FIONBIO),
    TEST_CASE(test_ioctl_FIOCLEX),
};

int main() {
    return test_suite_run(test_cases, ARRAY_SIZE(test_cases));
}
