#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>

#include <capstone/capstone.h>


struct platform {
    cs_arch arch;
    cs_mode mode;
    char *comment;
};

FILE * outfile = NULL;

struct platform platforms[] = {
    {
        // item 0
        CS_ARCH_X86,
        CS_MODE_32,
        "X86 32 (Intel syntax)"
    },
    {
        // item 1
        CS_ARCH_X86,
        CS_MODE_64,
        "X86 64 (Intel syntax)"
    },
    {
        // item 2
        CS_ARCH_ARM,
        CS_MODE_ARM,
        "ARM"
    },
    {
        // item 3
        CS_ARCH_ARM,
        CS_MODE_THUMB,
        "THUMB"
    },
    {
        // item 4
        CS_ARCH_ARM,
        (cs_mode)(CS_MODE_ARM + CS_MODE_V8),
        "Arm-V8"
    },
    {
        // item 5
        CS_ARCH_ARM,
        (cs_mode)(CS_MODE_THUMB+CS_MODE_V8),
        "THUMB+V8"
    },
    {
        // item 6
        CS_ARCH_ARM,
        (cs_mode)(CS_MODE_THUMB + CS_MODE_MCLASS),
        "Thumb-MClass"
    },
    {
        // item 7
        CS_ARCH_ARM64,
        (cs_mode)0,
        "ARM-64"
    },
    {
        // item 8
        CS_ARCH_MIPS,
        (cs_mode)(CS_MODE_MIPS32 + CS_MODE_BIG_ENDIAN),
        "MIPS-32 (Big-endian)"
    },
    {
        // item 9
        CS_ARCH_MIPS,
        (cs_mode)(CS_MODE_MIPS32 + CS_MODE_MICRO),
        "MIPS-32 (micro)"
    },
    {
        //item 10
        CS_ARCH_MIPS,
        CS_MODE_MIPS64,
        "MIPS-64-EL (Little-endian)"
    },
    {
        //item 11
        CS_ARCH_MIPS,
        CS_MODE_MIPS32,
        "MIPS-32-EL (Little-endian)"
    },
    {
        //item 12
        CS_ARCH_MIPS,
        (cs_mode)(CS_MODE_MIPS64 + CS_MODE_BIG_ENDIAN),
        "MIPS-64 (Big-endian)"
    },
    {
        //item 13
        CS_ARCH_MIPS,
        (cs_mode)(CS_MODE_MIPS32 + CS_MODE_MICRO + CS_MODE_BIG_ENDIAN),
        "MIPS-32 | Micro (Big-endian)"
    },
    {
        //item 14
        CS_ARCH_PPC,
        CS_MODE_BIG_ENDIAN,
        "PPC-64"
    },
    {
        //item 15
        CS_ARCH_SPARC,
        CS_MODE_BIG_ENDIAN,
        "Sparc"
    },
    {
        //item 16
        CS_ARCH_SPARC,
        (cs_mode)(CS_MODE_BIG_ENDIAN + CS_MODE_V9),
        "SparcV9"
    },
    {
        //item 17
        CS_ARCH_SYSZ,
        (cs_mode)0,
        "SystemZ"
    },
    {
        //item 18
        CS_ARCH_XCORE,
        (cs_mode)0,
        "XCore"
    },
    {
        //item 19
        CS_ARCH_MIPS,
        (cs_mode)(CS_MODE_MIPS32R6 + CS_MODE_BIG_ENDIAN),
        "MIPS-32R6 (Big-endian)"
    },
    {
        //item 20
        CS_ARCH_MIPS,
        (cs_mode)(CS_MODE_MIPS32R6 + CS_MODE_MICRO + CS_MODE_BIG_ENDIAN),
        "MIPS-32R6 (Micro+Big-endian)"
    },
    {
        //item 21
        CS_ARCH_MIPS,
        CS_MODE_MIPS32R6,
        "MIPS-32R6 (Little-endian)"
    },
    {
        //item 22
        CS_ARCH_MIPS,
        (cs_mode)(CS_MODE_MIPS32R6 + CS_MODE_MICRO),
        "MIPS-32R6 (Micro+Little-endian)"
    },
    {
        //item 23
        CS_ARCH_M68K,
        (cs_mode)0,
        "M68K"
    },
    {
        //item 24
        CS_ARCH_M680X,
        (cs_mode)CS_MODE_M680X_6809,
        "M680X_M6809"
    },
    {
        //item 25
        CS_ARCH_EVM,
        (cs_mode)0,
        "EVM"
    },
};

void LLVMFuzzerInit();
int LLVMFuzzerReturnOneInput(const uint8_t *Data, size_t Size, char * AssemblyText);

int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
    csh handle;
    cs_insn *insn;
    cs_err err;
    const uint8_t **Datap = &Data;
    size_t * Sizep = &Size;
    uint64_t address = 0x1000;
    char LLVMAssemblyText[80];
    char CapstoneAssemblyText[80];

    if (Size < 1) {
        // 1 byte for arch choice
        return 0;
    } else if (Size > 0x1000) {
        //limit input to 4kb
        Size = 0x1000;
    }
    if (outfile == NULL) {
        // we compute the output
        outfile = fopen("/dev/null", "w");
        if (outfile == NULL) {
            return 0;
        }
        LLVMFuzzerInit();
    }

    if (Data[0] >= sizeof(platforms)/sizeof(platforms[0])) {
        return 0;
    }

    if (LLVMFuzzerReturnOneInput(Data, Size, LLVMAssemblyText) == 1) {
        return 0;
    }

    err = cs_open(platforms[Data[0]].arch, platforms[Data[0]].mode, &handle);
    if (err) {
        return 0;
    }

    insn = cs_malloc(handle);
    Data++;
    Size--;
    assert(insn);
        if (cs_disasm_iter(handle, Datap, Sizep, &address, insn)) {
            snprintf(CapstoneAssemblyText, 80, "\t%s\t%s", insn->mnemonic, insn->op_str);
            if (strcmp(CapstoneAssemblyText, LLVMAssemblyText) != 0) {
                printf("capstone %s != llvm %s", CapstoneAssemblyText, LLVMAssemblyText);
                abort();
            }
        } else {
            printf("capstone failed with llvm %s", LLVMAssemblyText);
            abort();
        }
    cs_free(insn, 1);
    cs_close(&handle);

    return 0;
}