/* Run the boot image. */

#include <assert.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bootutil/bootutil.h>
#include <bootutil/image.h>

#include <flash_map_backend/flash_map_backend.h>

#include "../../../boot/bootutil/src/bootutil_priv.h"
#include "bootsim.h"

#ifdef MCUBOOT_ENCRYPT_RSA
#include "mbedtls/rsa.h"
#include "mbedtls/asn1.h"
#endif

#ifdef MCUBOOT_ENCRYPT_KW
#include "mbedtls/nist_kw.h"
#endif

#define BOOT_LOG_LEVEL BOOT_LOG_LEVEL_ERROR
#include <bootutil/bootutil_log.h>
#include "bootutil/crypto/common.h"

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

struct area_desc;
extern struct area_desc *sim_get_flash_areas(void);
extern void sim_set_flash_areas(struct area_desc *areas);
extern void sim_reset_flash_areas(void);

struct sim_context;
extern struct sim_context *sim_get_context(void);
extern void sim_set_context(struct sim_context *ctx);
extern void sim_reset_context(void);

extern int sim_flash_erase(uint8_t flash_id, uint32_t offset, uint32_t size);
extern int sim_flash_read(uint8_t flash_id, uint32_t offset, uint8_t *dest,
        uint32_t size);
extern int sim_flash_write(uint8_t flash_id, uint32_t offset, const uint8_t *src,
        uint32_t size);
extern uint32_t sim_flash_align(uint8_t flash_id);
extern uint8_t sim_flash_erased_val(uint8_t flash_id);

struct sim_context {
    int flash_counter;
    int jumped;
    uint8_t c_asserts;
    uint8_t c_catch_asserts;
    jmp_buf boot_jmpbuf;
};

#ifdef MCUBOOT_ENCRYPT_RSA
static int
parse_pubkey(mbedtls_rsa_context *ctx, uint8_t **p, uint8_t *end)
{
    int rc;
    size_t len;

    if ((rc = mbedtls_asn1_get_tag(p, end, &len,
                    MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != 0) {
        return -1;
    }

    if (*p + len != end) {
        return -2;
    }

    if ((rc = mbedtls_asn1_get_tag(p, end, &len,
                    MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != 0) {
        return -3;
    }

    *p += len;

    if ((rc = mbedtls_asn1_get_tag(p, end, &len, MBEDTLS_ASN1_BIT_STRING)) != 0) {
        return -4;
    }

    if (**p != MBEDTLS_ASN1_PRIMITIVE) {
        return -5;
    }

    *p += 1;

    if ((rc = mbedtls_asn1_get_tag(p, end, &len,
                    MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != 0) {
        return -6;
    }

    if (mbedtls_asn1_get_mpi(p, end, &ctx->MBEDTLS_CONTEXT_MEMBER(N)) != 0) {
        return -7;
    }

    if (mbedtls_asn1_get_mpi(p, end, &ctx->MBEDTLS_CONTEXT_MEMBER(E)) != 0) {
        return -8;
    }

    ctx->MBEDTLS_CONTEXT_MEMBER(len) = mbedtls_mpi_size(&ctx->MBEDTLS_CONTEXT_MEMBER(N));

    if (*p != end) {
        return -9;
    }

    if (mbedtls_rsa_check_pubkey(ctx) != 0) {
        return -10;
    }

    return 0;
}

static int
fake_rng(void *p_rng, unsigned char *output, size_t len)
{
    size_t i;

    (void)p_rng;
    for (i = 0; i < len; i++) {
        output[i] = (char)i;
    }

    return 0;
}
#endif

int mbedtls_platform_set_calloc_free(void * (*calloc_func)(size_t, size_t),
                                     void (*free_func)(void *));

int rsa_oaep_encrypt_(const uint8_t *pubkey, unsigned pubkey_len,
                      const uint8_t *seckey, unsigned seckey_len,
                      uint8_t *encbuf)
{
#ifdef MCUBOOT_ENCRYPT_RSA
    mbedtls_rsa_context ctx;
    uint8_t *cp;
    uint8_t *cpend;
    int rc;

    mbedtls_platform_set_calloc_free(calloc, free);

#if MBEDTLS_VERSION_NUMBER >= 0x03000000
    mbedtls_rsa_init(&ctx);
    mbedtls_rsa_set_padding(&ctx, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
#else
    mbedtls_rsa_init(&ctx, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
#endif

    cp = (uint8_t *)pubkey;
    cpend = cp + pubkey_len;

    rc = parse_pubkey(&ctx, &cp, cpend);
    if (rc) {
        goto done;
    }

#if MBEDTLS_VERSION_NUMBER >= 0x03000000
    rc = mbedtls_rsa_rsaes_oaep_encrypt(&ctx, fake_rng, NULL,
            NULL, 0, seckey_len, seckey, encbuf);
#else
    rc = mbedtls_rsa_rsaes_oaep_encrypt(&ctx, fake_rng, NULL, MBEDTLS_RSA_PUBLIC,
            NULL, 0, seckey_len, seckey, encbuf);
#endif
    if (rc) {
        goto done;
    }

done:
    mbedtls_rsa_free(&ctx);
    return rc;

#else
    (void)pubkey;
    (void)pubkey_len;
    (void)seckey;
    (void)seckey_len;
    (void)encbuf;
    return 0;
#endif
}

int kw_encrypt_(const uint8_t *kek, const uint8_t *seckey, uint8_t *encbuf)
{
#ifdef MCUBOOT_ENCRYPT_KW
#ifdef MCUBOOT_AES_256
    int key_len = 256;
    int out_size = 40;
    int in_len = 32;
#else
    int key_len = 128;
    int out_size = 24;
    int in_len = 16;
#endif
    mbedtls_nist_kw_context kw;
    size_t olen;
    int rc;

    mbedtls_platform_set_calloc_free(calloc, free);

    mbedtls_nist_kw_init(&kw);

    rc = mbedtls_nist_kw_setkey(&kw, MBEDTLS_CIPHER_ID_AES, kek, key_len, 1);
    if (rc) {
        goto done;
    }

    rc = mbedtls_nist_kw_wrap(&kw, MBEDTLS_KW_MODE_KW, seckey, in_len, encbuf,
            &olen, out_size);

done:
    mbedtls_nist_kw_free(&kw);
    return rc;

#else
    (void)kek;
    (void)seckey;
    (void)encbuf;
    return 0;
#endif
}

uint32_t flash_area_align(const struct flash_area *area)
{
    return sim_flash_align(area->fa_device_id);
}

uint8_t flash_area_erased_val(const struct flash_area *area)
{
    return sim_flash_erased_val(area->fa_device_id);
}

struct area {
    struct flash_area whole;
    struct flash_area *areas;
    uint32_t num_areas;
    uint8_t id;
};

struct area_desc {
    struct area slots[16];
    uint32_t num_slots;
};

int invoke_boot_go(struct sim_context *ctx, struct area_desc *adesc,
                   struct boot_rsp *rsp, int image_id)
{
    int res;
    struct boot_loader_state *state;

#if defined(MCUBOOT_SIGN_RSA) || \
    (defined(MCUBOOT_SIGN_EC256) && defined(MCUBOOT_USE_MBED_TLS)) ||\
    (defined(MCUBOOT_ENCRYPT_EC256) && defined(MCUBOOT_USE_MBED_TLS)) ||\
    (defined(MCUBOOT_ENCRYPT_X25519) && defined(MCUBOOT_USE_MBED_TLS))
    mbedtls_platform_set_calloc_free(calloc, free);
#endif

    state = malloc(sizeof(struct boot_loader_state));

    sim_set_flash_areas(adesc);
    sim_set_context(ctx);

    if (setjmp(ctx->boot_jmpbuf) == 0) {
        boot_state_clear(state);

#if BOOT_IMAGE_NUMBER > 1
        if (image_id >= 0) {
            memset(state->img_mask, 1, sizeof(state->img_mask));
            state->img_mask[image_id] = 0;
        }
#else
        (void) image_id;
#endif /* BOOT_IMAGE_NUMBER > 1 */

        res = context_boot_go(state, rsp);
        sim_reset_flash_areas();
        sim_reset_context();
        free(state);
        /* printf("boot_go off: %d (0x%08x)\n", res, rsp.br_image_off); */
        return res;
    } else {
        sim_reset_flash_areas();
        sim_reset_context();
        free(state);
        return -0x13579;
    }
}

void *os_malloc(size_t size)
{
    // printf("os_malloc 0x%x bytes\n", size);
    return malloc(size);
}

int flash_area_id_from_multi_image_slot(int image_index, int slot)
{
    switch (slot) {
    case 0: return FLASH_AREA_IMAGE_PRIMARY(image_index);
    case 1: return FLASH_AREA_IMAGE_SECONDARY(image_index);
    case 2: return FLASH_AREA_IMAGE_SCRATCH;
    }

    printf("Image flash area ID not found\n");
    return -1; /* flash_area_open will fail on that */
}

int flash_area_open(uint8_t id, const struct flash_area **area)
{
    uint32_t i;
    struct area_desc *flash_areas;

    flash_areas = sim_get_flash_areas();
    for (i = 0; i < flash_areas->num_slots; i++) {
        if (flash_areas->slots[i].id == id)
            break;
    }
    if (i == flash_areas->num_slots) {
        printf("Unsupported area\n");
        abort();
    }

    /* Unsure if this is right, just returning the first area. */
    *area = &flash_areas->slots[i].whole;
    return 0;
}

void flash_area_close(const struct flash_area *area)
{
    (void)area;
}

/*
 * Read/write/erase. Offset is relative from beginning of flash area.
 */
int flash_area_read(const struct flash_area *area, uint32_t off, void *dst,
                    uint32_t len)
{
    BOOT_LOG_SIM("%s: area=%d, off=%x, len=%x",
                 __func__, area->fa_id, off, len);
    return sim_flash_read(area->fa_device_id, area->fa_off + off, dst, len);
}

int flash_area_write(const struct flash_area *area, uint32_t off, const void *src,
                     uint32_t len)
{
    BOOT_LOG_SIM("%s: area=%d, off=%x, len=%x", __func__,
                 area->fa_id, off, len);
    struct sim_context *ctx = sim_get_context();
    if (--(ctx->flash_counter) == 0) {
        ctx->jumped++;
        longjmp(ctx->boot_jmpbuf, 1);
    }
    return sim_flash_write(area->fa_device_id, area->fa_off + off, src, len);
}

int flash_area_erase(const struct flash_area *area, uint32_t off, uint32_t len)
{
    BOOT_LOG_SIM("%s: area=%d, off=%x, len=%x", __func__,
                 area->fa_id, off, len);
    struct sim_context *ctx = sim_get_context();
    if (--(ctx->flash_counter) == 0) {
        ctx->jumped++;
        longjmp(ctx->boot_jmpbuf, 1);
    }
    return sim_flash_erase(area->fa_device_id, area->fa_off + off, len);
}

int flash_area_to_sectors(int idx, int *cnt, struct flash_area *ret)
{
    uint32_t i;
    struct area *slot;
    struct area_desc *flash_areas;

    flash_areas = sim_get_flash_areas();
    for (i = 0; i < flash_areas->num_slots; i++) {
        if (flash_areas->slots[i].id == idx)
            break;
    }
    if (i == flash_areas->num_slots) {
        printf("Unsupported area\n");
        abort();
    }

    slot = &flash_areas->slots[i];

    if (slot->num_areas > (uint32_t)*cnt) {
        printf("Too many areas in slot\n");
        abort();
    }

    *cnt = slot->num_areas;
    memcpy(ret, slot->areas, slot->num_areas * sizeof(struct flash_area));

    return 0;
}

int flash_area_get_sectors(int fa_id, uint32_t *count,
                           struct flash_sector *sectors)
{
    uint32_t i;
    struct area *slot;
    struct area_desc *flash_areas;

    flash_areas = sim_get_flash_areas();
    for (i = 0; i < flash_areas->num_slots; i++) {
        if (flash_areas->slots[i].id == fa_id)
            break;
    }
    if (i == flash_areas->num_slots) {
        printf("Unsupported area\n");
        abort();
    }

    slot = &flash_areas->slots[i];

    if (slot->num_areas > *count) {
        printf("Too many areas in slot\n");
        abort();
    }

    for (i = 0; i < slot->num_areas; i++) {
        sectors[i].fs_off = slot->areas[i].fa_off -
            slot->whole.fa_off;
        sectors[i].fs_size = slot->areas[i].fa_size;
    }
    *count = slot->num_areas;

    return 0;
}

int flash_area_id_to_multi_image_slot(int image_index, int area_id)
{
    if (area_id == FLASH_AREA_IMAGE_PRIMARY(image_index)) {
        return 0;
    }
    if (area_id == FLASH_AREA_IMAGE_SECONDARY(image_index)) {
        return 1;
    }

    printf("Unsupported image area ID\n");
    abort();
}

int flash_area_id_from_image_slot(int slot) {
    /* For single image cases, just use the first image. */
    return flash_area_id_from_multi_image_slot(0, slot);
}

int flash_area_sector_from_off(uint32_t off, struct flash_sector *sector)
{
    uint32_t i, sec_off, sec_size;
    struct area *slot;
    struct area_desc *flash_areas;

    flash_areas = sim_get_flash_areas();
    for (i = 0; i < flash_areas->num_slots; i++) {
        if (flash_areas->slots[i].id == FLASH_AREA_ID(image_0))
            break;
    }

    if (i == flash_areas->num_slots) {
        printf("Unsupported area\n");
        abort();
    }

    slot = &flash_areas->slots[i];

    for (i = 0; i < slot->num_areas; i++) {
        sec_off = slot->areas[i].fa_off - slot->whole.fa_off;
        sec_size = slot->areas[i].fa_size;

        if (off >= sec_off && off < (sec_off + sec_size)) {
            sector->fs_off = sec_off;
            sector->fs_size = sec_size;
            break;
        }
    }

    return (i < slot->num_areas) ? 0 : -1;
}

int flash_area_get_sector(const struct flash_area *fa, uint32_t off,
                          struct flash_sector *sector)
{
    uint32_t i, sec_off, sec_size;
    struct area *slot;
    struct area_desc *flash_areas;

    flash_areas = sim_get_flash_areas();
    for (i = 0; i < flash_areas->num_slots; i++) {
        if (&flash_areas->slots[i].whole == fa)
            break;
    }

    if (i == flash_areas->num_slots) {
        printf("Unsupported area\n");
        abort();
    }

    slot = &flash_areas->slots[i];

    for (i = 0; i < slot->num_areas; i++) {
        sec_off = slot->areas[i].fa_off - slot->whole.fa_off;
        sec_size = slot->areas[i].fa_size;

        if (off >= sec_off && off < (sec_off + sec_size)) {
            sector->fs_off = sec_off;
            sector->fs_size = sec_size;
            break;
        }
    }

    return (i < slot->num_areas) ? 0 : -1;
}

void sim_assert(int x, const char *assertion, const char *file, unsigned int line, const char *function)
{
    if (!(x)) {
        struct sim_context *ctx = sim_get_context();
        if (ctx->c_catch_asserts) {
            ctx->c_asserts++;
        } else {
            BOOT_LOG_ERR("%s:%d: %s: Assertion `%s' failed.", file, line, function, assertion);

            /* NOTE: if the assert below is triggered, the place where it was originally
             * asserted is printed by the message above...
             */
            assert(x);
        }
    }
}

uint32_t boot_max_align(void)
{
    return BOOT_MAX_ALIGN;
}

uint32_t boot_magic_sz(void)
{
    return BOOT_MAGIC_ALIGN_SIZE;
}
