/*
 * Copyright (c) 2015, Google Inc. All rights reserved
 *
 * 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.
 */

#include <assert.h>
#include <err.h>
#include <kernel/usercopy.h>
#include <lib/unittest/unittest.h>
#include <lk/init.h>
#include <stdio.h>
#include <string.h>
#include <trusty/string.h>
#ifdef ARCH_ARM64
#include <arch/safecopy.h>
#endif

#define PORT_NAME "com.android.kernel.usercopy-unittest"

#define TEST_BUF_SIZE (16)
#define TEST_BUF1_SIZE (TEST_BUF_SIZE / 2)
#define TEST_BUF2_SIZE (TEST_BUF_SIZE - TEST_BUF1_SIZE)
#define TEST_BUF_COPY_START (1)
#define TEST_BUF_COPY_SIZE (TEST_BUF_SIZE - TEST_BUF_COPY_START - 1)
#define TEST_BUF1_COPY_SIZE (TEST_BUF1_SIZE - TEST_BUF_COPY_START)
#define TEST_BUF2_COPY_SIZE (TEST_BUF_COPY_SIZE - TEST_BUF1_COPY_SIZE)
#define TEST_BUF_COPY_LAST (TEST_BUF_SIZE - 1 - 1)
#define TEST_BUF2_COPY_LAST (TEST_BUF_COPY_LAST - TEST_BUF1_SIZE)

#define SRC_DATA (0x22)
#define DEST_DATA (0x11)

#define FLAGS_NO_PAGE (ARCH_MMU_FLAG_INVALID)
#define FLAGS_NO_USER (0u)
#define FLAGS_RO_USER (ARCH_MMU_FLAG_PERM_USER | ARCH_MMU_FLAG_PERM_RO)
#define FLAGS_RW_USER (ARCH_MMU_FLAG_PERM_USER)

#define STACK_ADDR_IDX (0)
#define HEAP_ADDR_IDX (1)
#define GLOBAL_ADDR_IDX (2)

#define START_PAGE_ADDR ((void*)(PAGE_SIZE * 0x10))
#define TEST_BUF_ADDR \
    ((user_addr_t)((uintptr_t)(START_PAGE_ADDR + PAGE_SIZE - TEST_BUF1_SIZE)))

static inline user_addr_t get_addr_param(void) {
    const void* const* param_arr = GetParam();
    const user_addr_t* addr = param_arr[0];
    return *addr;
}

static inline uint32_t get_start_flags_param(void) {
    const void* const* param_arr = GetParam();
    const uint32_t* start_flags = param_arr[1];
    return *start_flags;
}

static inline uint32_t get_end_flags_param(void) {
    const void* const* param_arr = GetParam();
    const uint32_t* end_flags = param_arr[2];
    return *end_flags;
}

static int checkbuf(const char* buf, char c, size_t size) {
    int error_count = 0;
    for (size_t i = 0; i < size; i++) {
        if (buf[i] != c) {
            error_count++;
        }
    }
    return error_count;
}

static void usercopy_test_init_buf(char* kbuf1,
                                   char* kbuf2,
                                   uint8_t val,
                                   int null_offset) {
    if (kbuf1) {
        memset(kbuf1, val, TEST_BUF1_SIZE);
        if (null_offset >= 0 && null_offset < TEST_BUF1_SIZE) {
            kbuf1[null_offset] = '\0';
        }
    }
    if (kbuf2) {
        memset(kbuf2, val, TEST_BUF2_SIZE);
        if (null_offset >= TEST_BUF1_SIZE && null_offset < TEST_BUF_SIZE) {
            kbuf2[null_offset - TEST_BUF1_SIZE] = '\0';
        }
    }
}

typedef struct {
    struct vmm_aspace* aspace;
} usercopytest_t;

TEST_F_SETUP(usercopytest) {
    int ret;
    void* addr = START_PAGE_ADDR;
    uint32_t start_flags = get_start_flags_param();
    uint32_t end_flags = get_end_flags_param();

    _state->aspace = NULL;

    ret = vmm_create_aspace(&_state->aspace, "usercopy_test", 0);
    ASSERT_EQ(NO_ERROR, ret);

    if (start_flags != FLAGS_NO_PAGE) {
        ret = vmm_alloc(_state->aspace, "start-page", PAGE_SIZE, &addr, 0,
                        VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_END_GUARD,
                        start_flags | ARCH_MMU_FLAG_PERM_NO_EXECUTE);
        ASSERT_EQ(NO_ERROR, ret);
        ASSERT_EQ(START_PAGE_ADDR, addr);
    }

    addr += PAGE_SIZE;

    if (end_flags != FLAGS_NO_PAGE) {
        ret = vmm_alloc(_state->aspace, "end-page", PAGE_SIZE, &addr, 0,
                        VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_START_GUARD,
                        end_flags | ARCH_MMU_FLAG_PERM_NO_EXECUTE);
        ASSERT_EQ(NO_ERROR, ret);
        ASSERT_EQ(START_PAGE_ADDR + PAGE_SIZE, addr);
    }

    vmm_set_active_aspace(_state->aspace);

test_abort:
    return;
}

TEST_F_TEARDOWN(usercopytest) {
    vmm_set_active_aspace(NULL);

    if (_state->aspace) {
        vmm_free_aspace(_state->aspace);
    }
}

TEST_P(usercopytest, copy_to_user) {
    user_addr_t addr = get_addr_param();
    uint32_t arch_mmu_flags_start = get_start_flags_param();
    uint32_t arch_mmu_flags_end = get_end_flags_param();
    int ret;
    char src_buf[TEST_BUF_SIZE];
    char* dest_kbuf1;
    char* dest_kbuf2;
    char expect1;
    char expect2;

    dest_kbuf1 = paddr_to_kvaddr(vaddr_to_paddr((void*)(uintptr_t)addr));
    dest_kbuf2 = paddr_to_kvaddr(
            vaddr_to_paddr((void*)(uintptr_t)addr + TEST_BUF1_SIZE));

    /* dest buffs should be NULL iff their flags are FLAGS_NO_PAGE */
    EXPECT_EQ((dest_kbuf1 == NULL), (arch_mmu_flags_start == FLAGS_NO_PAGE));
    EXPECT_EQ((dest_kbuf2 == NULL), (arch_mmu_flags_end == FLAGS_NO_PAGE));

    usercopy_test_init_buf(dest_kbuf1, dest_kbuf2, DEST_DATA, -1);
    memset(src_buf, SRC_DATA, sizeof(src_buf));

    /* Zero-length copy should always succeed */
    ret = copy_to_user(addr + TEST_BUF_COPY_START, NULL, 0);
    EXPECT_EQ(0, ret);

    /* Dest buffer should be untouched after zero-length copy */
    if (dest_kbuf1) {
        EXPECT_EQ(0, checkbuf(dest_kbuf1, DEST_DATA, TEST_BUF1_SIZE));
    }
    if (dest_kbuf2) {
        EXPECT_EQ(0, checkbuf(dest_kbuf2, DEST_DATA, TEST_BUF2_SIZE));
    }

    /* Perform non-zero length copy */
    ret = copy_to_user(addr + TEST_BUF_COPY_START,
                       src_buf + TEST_BUF_COPY_START, TEST_BUF_COPY_SIZE);

    /*
     * If both pages are writeable copy_to_user should succeed otherwise it
     * should return ERR_FAULT.
     */
    if (arch_mmu_flags_start == ARCH_MMU_FLAG_PERM_USER &&
        arch_mmu_flags_end == ARCH_MMU_FLAG_PERM_USER) {
        /*
         * If both pages are writeable from user-space copy_to_user should
         * return success and every byte should be copied to dest_buf.
         */
        EXPECT_EQ(0, ret);
        expect1 = SRC_DATA;
        expect2 = SRC_DATA;
    } else {
        /*
         * If one of the pages is not writeable from user-space copy_to_user
         * should return ERR_FAULT. If only the first page is writeable everying
         * should be copied in the first page or nothing should be copied in the
         * first page. If the first page is not writeable, nothing should be
         * copied to either page. If the second page is not writeable, no data
         * should be copied to it, even if the first page was written to.
         */
        EXPECT_EQ(ERR_FAULT, ret);
        if (arch_mmu_flags_start == ARCH_MMU_FLAG_PERM_USER &&
            dest_kbuf1[TEST_BUF_COPY_START] == SRC_DATA) {
            expect1 = SRC_DATA;
        } else {
            expect1 = DEST_DATA;
        }
        expect2 = DEST_DATA;
    }

    /* copy_to_user should not modify src_buf at all */
    EXPECT_EQ(0, checkbuf(src_buf, SRC_DATA, TEST_BUF_SIZE));

    if (dest_kbuf1) {
        /* Dest byte before copied region should be untouched */
        EXPECT_EQ(DEST_DATA, dest_kbuf1[0]);

        /* Check that copied region match expected value we selected above */
        EXPECT_EQ(0, checkbuf(dest_kbuf1 + TEST_BUF_COPY_START, expect1,
                              TEST_BUF1_COPY_SIZE));
    }

    if (dest_kbuf2) {
        /* Check that copied region match expected value we selected above */
        EXPECT_EQ(0, checkbuf(dest_kbuf2, expect2, TEST_BUF2_COPY_SIZE));

        /* Dest byte after copied region should be untouched */
        EXPECT_EQ(DEST_DATA, dest_kbuf2[TEST_BUF2_SIZE - 1]);
    }
}

TEST_P(usercopytest, copy_from_user) {
    user_addr_t addr = get_addr_param();
    uint32_t arch_mmu_flags_start = get_start_flags_param();
    uint32_t arch_mmu_flags_end = get_end_flags_param();
    int ret;
    char dest_buf[TEST_BUF_SIZE];
    char* src_kbuf1;
    char* src_kbuf2;
    char expect1;
    char expect2;

    memset(dest_buf, DEST_DATA, sizeof(dest_buf));
    src_kbuf1 = paddr_to_kvaddr(vaddr_to_paddr((void*)(uintptr_t)addr));
    src_kbuf2 = paddr_to_kvaddr(
            vaddr_to_paddr((void*)(uintptr_t)addr + TEST_BUF1_SIZE));

    /* src buffs should be NULL iff their flags are FLAGS_NO_PAGE */
    EXPECT_EQ((src_kbuf1 == NULL), (arch_mmu_flags_start == FLAGS_NO_PAGE));
    EXPECT_EQ((src_kbuf2 == NULL), (arch_mmu_flags_end == FLAGS_NO_PAGE));

    usercopy_test_init_buf(src_kbuf1, src_kbuf2, SRC_DATA, -1);

    /* Zero-length copy should always succeed */
    ret = copy_from_user(NULL, addr + TEST_BUF_COPY_START, 0);
    EXPECT_EQ(0, ret);

    /* Dest buffer should be untouched after zero-length copy */
    EXPECT_EQ(0, checkbuf(dest_buf, DEST_DATA, TEST_BUF_SIZE));

    /* Perform non-zero length copy */
    ret = copy_from_user(dest_buf + TEST_BUF_COPY_START,
                         addr + TEST_BUF_COPY_START, TEST_BUF_COPY_SIZE);
    if (arch_mmu_flags_start & arch_mmu_flags_end & ARCH_MMU_FLAG_PERM_USER) {
        /*
         * If both pages are readable from user-space copy_from_user should
         * return success and every byte should be copied to dest_buf.
         */
        EXPECT_EQ(0, ret);
        expect1 = SRC_DATA;
        expect2 = SRC_DATA;
    } else {
        /*
         * If one of the pages is not readable from user-space copy_from_user
         * should return ERR_FAULT, and the parts of dest_buf that could not be
         * copied into should be set to 0.
         * The destination kernel buffer should always be written so
         * potentially uninitialized kernel data does not leak.
         */
        EXPECT_EQ(ERR_FAULT, ret);
        if (!(arch_mmu_flags_start & ARCH_MMU_FLAG_PERM_USER) ||
            !dest_buf[TEST_BUF_COPY_START]) {
            expect1 = 0;
        } else {
            expect1 = SRC_DATA;
        }
        expect2 = 0;
    }

    EXPECT_EQ(0, checkbuf(dest_buf + TEST_BUF_COPY_START, expect1,
                          TEST_BUF1_COPY_SIZE));
    EXPECT_EQ(0, checkbuf(dest_buf + TEST_BUF1_SIZE, expect2,
                          TEST_BUF2_COPY_SIZE));

    /* Dest bytes before and after copied region should be untouched */
    EXPECT_EQ(DEST_DATA, dest_buf[0]);
    EXPECT_EQ(DEST_DATA, dest_buf[TEST_BUF_SIZE - 1]);

    /* Src buffer should not be modified */
    if (src_kbuf1) {
        EXPECT_EQ(0, checkbuf(src_kbuf1, SRC_DATA, TEST_BUF1_SIZE));
    }
    if (src_kbuf2) {
        EXPECT_EQ(0, checkbuf(src_kbuf2, SRC_DATA, TEST_BUF2_SIZE));
    }
}

#if ARCH_ARM64
#define ENABLED_ON_ARM64_NAME(name) name
#else
#define ENABLED_ON_ARM64_NAME(name) DISABLED_##name
#define copy_from_anywhere(dst, src, len) -1
#endif

TEST_P(usercopytest, ENABLED_ON_ARM64_NAME(copy_from_anywhere)) {
    user_addr_t addr = get_addr_param();
    uint32_t arch_mmu_flags_start = get_start_flags_param();
    uint32_t arch_mmu_flags_end = get_end_flags_param();
    int ret;
    char dest_buf[TEST_BUF_SIZE];
    char* src_kbuf1;
    char* src_kbuf2;
    char expect1;
    char expect2;

    memset(dest_buf, DEST_DATA, sizeof(dest_buf));
    src_kbuf1 = paddr_to_kvaddr(vaddr_to_paddr((void*)(uintptr_t)addr));
    src_kbuf2 = paddr_to_kvaddr(
            vaddr_to_paddr((void*)(uintptr_t)addr + TEST_BUF1_SIZE));

    /* src buffs should be NULL iff their flags are FLAGS_NO_PAGE */
    EXPECT_EQ((src_kbuf1 == NULL), (arch_mmu_flags_start == FLAGS_NO_PAGE));
    EXPECT_EQ((src_kbuf2 == NULL), (arch_mmu_flags_end == FLAGS_NO_PAGE));

    usercopy_test_init_buf(src_kbuf1, src_kbuf2, SRC_DATA, -1);

    /* Zero-length copy should always succeed */
    ret = copy_from_anywhere(NULL, addr + TEST_BUF_COPY_START, 0);
    EXPECT_EQ(0, ret);

    /* Dest buffer should be untouched after zero-length copy */
    EXPECT_EQ(0, checkbuf(dest_buf, DEST_DATA, TEST_BUF_SIZE));

    /* Perform non-zero length copy */
    ret = copy_from_anywhere(dest_buf + TEST_BUF_COPY_START,
                             addr + TEST_BUF_COPY_START, TEST_BUF_COPY_SIZE);
    if (arch_mmu_flags_start != FLAGS_NO_PAGE &&
        arch_mmu_flags_end != FLAGS_NO_PAGE) {
        /*
         * If both pages are readable, copy_from_anywhere should return
         * success and every byte should be copied to dest_buf.
         */
        EXPECT_EQ(0, ret);
        expect1 = SRC_DATA;
        expect2 = SRC_DATA;
    } else {
        /*
         * If one of the pages is not readable copy_from_anywhere should
         * return ERR_FAULT, and the parts of dest_buf that could not be
         * copied into should be set to 0.
         * The destination kernel buffer should always be written so
         * potentially uninitialized kernel data does not leak.
         */
        EXPECT_EQ(ERR_FAULT, ret);
        if (arch_mmu_flags_start == FLAGS_NO_PAGE) {
            expect1 = 0;
        } else {
            expect1 = SRC_DATA;
        }
        expect2 = 0;
    }

    EXPECT_EQ(0, checkbuf(dest_buf + TEST_BUF_COPY_START, expect1,
                          TEST_BUF1_COPY_SIZE));
    EXPECT_EQ(0, checkbuf(dest_buf + TEST_BUF1_SIZE, expect2,
                          TEST_BUF2_COPY_SIZE));

    /* Dest bytes before and after copied region should be untouched */
    EXPECT_EQ(DEST_DATA, dest_buf[0]);
    EXPECT_EQ(DEST_DATA, dest_buf[TEST_BUF_SIZE - 1]);

    /* Src buffer should not be modified */
    if (src_kbuf1) {
        EXPECT_EQ(0, checkbuf(src_kbuf1, SRC_DATA, TEST_BUF1_SIZE));
    }
    if (src_kbuf2) {
        EXPECT_EQ(0, checkbuf(src_kbuf2, SRC_DATA, TEST_BUF2_SIZE));
    }
}

static void usercopy_test_strlcpy_from_user_inner(user_addr_t addr,
                                                  uint arch_mmu_flags_start,
                                                  uint arch_mmu_flags_end,
                                                  int copy_size,
                                                  int null_off) {
    int ret;
    char dest_buf[TEST_BUF_SIZE];
    char* src_kbuf1;
    char* src_kbuf2;
    size_t dest_len;
    int copy_len = copy_size ? copy_size - 1 : 0;

    memset(dest_buf, DEST_DATA, sizeof(dest_buf));
    src_kbuf1 = paddr_to_kvaddr(vaddr_to_paddr((void*)(uintptr_t)addr));
    src_kbuf2 = paddr_to_kvaddr(
            vaddr_to_paddr((void*)(uintptr_t)addr + TEST_BUF1_SIZE));

    /* src buffs should be NULL iff their flags are FLAGS_NO_PAGE */
    EXPECT_EQ((src_kbuf1 == NULL), (arch_mmu_flags_start == FLAGS_NO_PAGE));
    EXPECT_EQ((src_kbuf2 == NULL), (arch_mmu_flags_end == FLAGS_NO_PAGE));

    usercopy_test_init_buf(src_kbuf1, src_kbuf2, SRC_DATA, null_off);

    ret = strlcpy_from_user(dest_buf + TEST_BUF_COPY_START,
                            addr + TEST_BUF_COPY_START, copy_size);

    dest_len = strnlen(dest_buf + TEST_BUF_COPY_START, TEST_BUF_COPY_SIZE);
    if (copy_size) {
        /*
         * Kernel buffer should always be null terminated.
         */
        EXPECT_NE(TEST_BUF_COPY_SIZE, dest_len, "  null_off=%d, copy_size=%d\n",
                  null_off, copy_size);
    } else {
        /*
         * If copy_size is 0, then kernel buffer will not be null terminated.
         */
        EXPECT_EQ(TEST_BUF_COPY_SIZE, dest_len, "  null_off=%d, copy_size=%d\n",
                  null_off, copy_size);
        dest_len = 0;
    }

    /*
     * If the string in dest_buf is not empty it should only contain data from
     * the source string.
     */
    EXPECT_EQ(0, checkbuf(dest_buf + TEST_BUF_COPY_START, SRC_DATA, dest_len),
              "  null_off=%d, copy_size=%d\n", null_off, copy_size);

    if ((arch_mmu_flags_start & ARCH_MMU_FLAG_PERM_USER) &&
        ((arch_mmu_flags_end & ARCH_MMU_FLAG_PERM_USER) ||
         null_off < TEST_BUF1_SIZE)) {
        /*
         * If the pages readable from user-space contain a 0 terminated string,
         * strlcpy_from_user should return the length of that string and every
         * byte up to the 0 terminator that fits in dest_buf should be copied
         * there. dest_buf should always be 0 terminated.
         */
        EXPECT_EQ(null_off - TEST_BUF_COPY_START, ret,
                  "  wrong strlen returned, null_off=%d, copy_size=%d\n",
                  null_off, copy_size);
        EXPECT_EQ(MIN(null_off - TEST_BUF_COPY_START, copy_len), dest_len,
                  "  null_off=%d, copy_size=%d\n", null_off, copy_size);
    } else {
        /*
         * If one of the pages is not readable from user-space strlcpy_from_user
         * should return ERR_FAULT, and dest_buf should have a null terminator
         * at the start of the faulting page or at the start of the string.
         */
        EXPECT_EQ(ERR_FAULT, ret, "  null_off=%d, copy_size=%d\n", null_off,
                  copy_size);
        if (!(arch_mmu_flags_start & ARCH_MMU_FLAG_PERM_USER)) {
            EXPECT_EQ(0, dest_len, "  null_off=%d, copy_size=%d\n", null_off,
                      copy_size);
        } else if (dest_len) {
            EXPECT_EQ(MIN(TEST_BUF1_COPY_SIZE, copy_len), dest_len,
                      "  null_off=%d, copy_size=%d\n", null_off, copy_size);
        }
    }

    /* Src buffer should not be modified */
    if (src_kbuf1) {
        if (null_off < TEST_BUF1_SIZE) {
            EXPECT_EQ(0, checkbuf(src_kbuf1, SRC_DATA, null_off));
            EXPECT_EQ('\0', src_kbuf1[null_off]);
            EXPECT_EQ(0, checkbuf(src_kbuf1 + null_off + 1, SRC_DATA,
                                  TEST_BUF1_SIZE - null_off - 1));
        } else {
            EXPECT_EQ(0, checkbuf(src_kbuf1, SRC_DATA, TEST_BUF1_SIZE));
        }
    }
    if (src_kbuf2) {
        if (null_off >= TEST_BUF1_SIZE) {
            size_t null_off2 = null_off - TEST_BUF1_SIZE;
            EXPECT_EQ(0, checkbuf(src_kbuf2, SRC_DATA, null_off2));
            EXPECT_EQ('\0', src_kbuf2[null_off2]);
            EXPECT_EQ(0, checkbuf(src_kbuf2 + null_off2 + 1, SRC_DATA,
                                  TEST_BUF2_SIZE - null_off2 - 1));
        } else {
            EXPECT_EQ(0, checkbuf(src_kbuf2, SRC_DATA, TEST_BUF2_SIZE));
        }
    }

    /* Dest bytes before and after copied region should be untouched */
    EXPECT_EQ(DEST_DATA, dest_buf[0]);
    EXPECT_EQ(DEST_DATA, dest_buf[TEST_BUF_COPY_START + copy_size]);
    EXPECT_EQ(DEST_DATA, dest_buf[TEST_BUF_SIZE - 1]);
}

TEST_P(usercopytest, strlcpy_from_user) {
    user_addr_t addr = get_addr_param();
    uint32_t arch_mmu_flags_start = get_start_flags_param();
    uint32_t arch_mmu_flags_end = get_end_flags_param();
    size_t copy_sizes[] = {0, TEST_BUF1_COPY_SIZE, TEST_BUF_COPY_SIZE};
    size_t copy_sizes_index;
    int null_off;
    int copy_size;

    for (copy_sizes_index = 0; copy_sizes_index < countof(copy_sizes);
         copy_sizes_index++) {
        copy_size = copy_sizes[copy_sizes_index];
        for (null_off = TEST_BUF_COPY_START; null_off < TEST_BUF_SIZE;
             null_off++) {
            usercopy_test_strlcpy_from_user_inner(addr, arch_mmu_flags_start,
                                                  arch_mmu_flags_end, copy_size,
                                                  null_off);
        }
    }
}

static const char* flags_to_str(uint32_t flags) {
    switch (flags) {
    case FLAGS_NO_PAGE:
        return "--";
    case FLAGS_NO_USER:
        return "ko";
    case FLAGS_RO_USER:
        return "ro";
    case FLAGS_RW_USER:
        return "rw";
    default:
        return "??";
    }
}

static void user_param_to_string(const void* param,
                                 char* buf,
                                 size_t buf_size) {
    uint32_t start_flags = get_start_flags_param();
    uint32_t end_flags = get_end_flags_param();
    size_t count = 0;

    count = scnprintf(buf + count, buf_size - count, "%s",
                      flags_to_str(start_flags));
    scnprintf(buf + count, buf_size - count, "%s", flags_to_str(end_flags));
}

INSTANTIATE_TEST_SUITE_P(UserCopyTestParams,
                         usercopytest,
                         testing_Combine(testing_Values(TEST_BUF_ADDR),
                                         testing_Values(FLAGS_NO_PAGE,
                                                        FLAGS_NO_USER,
                                                        FLAGS_RO_USER,
                                                        FLAGS_RW_USER),
                                         testing_Values(FLAGS_NO_PAGE,
                                                        FLAGS_NO_USER,
                                                        FLAGS_RO_USER,
                                                        FLAGS_RW_USER)),
                         user_param_to_string);

#if IS_64BIT && USER_32BIT
/*
 * Tests with Kernel addresses are not applicable to arm64u32 since kernel
 * addresses do not fit in a user_addr_t.
 */
static_assert(KERNEL_BASE > UINT32_MAX);

PORT_TEST(usercopy_tests, PORT_NAME)

#else
/* These are filled in before the tests are run */
static user_addr_t kernel_addrs[3];

static void kernel_param_to_string(const void* param,
                                   char* buf,
                                   size_t buf_size) {
    const void* const* kernel_param = param;
    size_t idx = ((user_addr_t*)kernel_param[0] - kernel_addrs);
    const char* str;

    switch (idx) {
    case STACK_ADDR_IDX:
        str = "kernel-stack";
        break;
    case HEAP_ADDR_IDX:
        str = "kernel-heap";
        break;
    case GLOBAL_ADDR_IDX:
        str = "kernel-global";
        break;
    default:
        str = "unknown-address-type";
    }

    scnprintf(buf, buf_size, "%s", str);
}

INSTANTIATE_TEST_SUITE_P(KernelUserCopyTestParams,
                         usercopytest,
                         testing_Combine(testing_ValuesIn(kernel_addrs),
                                         testing_Values(FLAGS_NO_USER),
                                         testing_Values(FLAGS_NO_USER)),
                         kernel_param_to_string);

static bool run_usercopy_test(struct unittest* test) {
    bool tests_passed;
    static uint8_t global_buf[TEST_BUF_SIZE];
    uint8_t stack_buf[TEST_BUF_SIZE];
    uint8_t* heap_buf = malloc(TEST_BUF_SIZE);

    ASSERT(heap_buf);

    kernel_addrs[STACK_ADDR_IDX] = (user_addr_t)stack_buf;
    kernel_addrs[HEAP_ADDR_IDX] = (user_addr_t)heap_buf;
    kernel_addrs[GLOBAL_ADDR_IDX] = (user_addr_t)global_buf;

    tests_passed = RUN_ALL_TESTS();

    free(heap_buf);

    return tests_passed;
}

static void usercopy_test_init(uint level) {
    static struct unittest usercopy_unittest = {
            .port_name = PORT_NAME,
            .run_test = run_usercopy_test,
    };

    unittest_add(&usercopy_unittest);
}

LK_INIT_HOOK(usercopy_test, usercopy_test_init, LK_INIT_LEVEL_APPS);
#endif  // !(IS_64BIT && USER_32BIT)
