/*
 * Copyright (C) 2020 Inria
 * Copyright (C) 2020 Koen Zandberg <koen@bergzand.net>
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>

#include "bpf.h"
#include "bpf/instruction.h"
#include "bpf/call.h"

#include <debug_progmem.h>

/**
 * This is a set of macros to easily implement the similar eBPF instructions
 */

/* Macro for the destination, source and immediate value */
#define DST regmap[instr.dst] /* DST is the register targeted by the instruction */
#define SRC regmap[instr.src] /* SRC is the source register from the instruction */
#define IMM instr.immediate   /* And this one matches the immediate value in the instruction */

/* Two macros that jump to the start of the instruction pipeline. */
#define CONT       { goto select_instr; } /* Continue execution with the next one */
#define CONT_JUMP  { goto jump_instr; } /* Execute the jump and continue */

/* Check if we implement 32 bit instructions */
#if (CONFIG_BPF_ENABLE_ALU32)

/* Generate the ALU instruction, based on the opcode name and the operation
 * itself. ALU(ADD, +) generates the 2 or 4 instructions implementing the add
 * instruction, using '+' in C. Generates both the DST += SRC and DST += IMM */
#define ALU(OPCODE, OP)         \
    ALU64_##OPCODE##_REG:         \
        DST = DST OP SRC;       \
        CONT;                   \
    ALU64_##OPCODE##_IMM:       \
        DST = DST OP IMM;       \
        CONT;                   \
    ALU32_##OPCODE##_REG:         \
        DST = (uint32_t) DST OP (uint32_t) SRC;   \
        CONT;                   \
    ALU32_##OPCODE##_IMM:           \
        DST = (uint32_t) DST OP (uint32_t) IMM;   \
        CONT;
#else
#define ALU(OPCODE, OP)         \
    ALU64_##OPCODE##_REG:         \
        DST = DST OP SRC;       \
        CONT;                   \
    ALU64_##OPCODE##_IMM:       \
        DST = DST OP IMM;       \
        CONT;
#endif

/* Generate jump type instructions, similar to the ALU instructions */
#define COND_JMP(SIGN, OPCODE, CMP_OP)              \
    JMP_##OPCODE##_REG:                  \
        jump_cond = (SIGN##nt64_t) DST CMP_OP (SIGN##nt64_t)SRC; \
        CONT_JUMP;                           \
    JMP_##OPCODE##_IMM:                 \
        jump_cond = (SIGN##nt64_t) DST CMP_OP (SIGN##nt64_t)IMM; \
        CONT_JUMP;                           \

/* This generates values for the jumptable array. It combines the same opcode
 * name as used in the code generation macros and the numeric opcode value to
 * create a jumptable entry. Entries are generated for 64 and 32 bit types */
#if CONFIG_BPF_ENABLE_ALU32
#define ALU_OPCODE_REG(OPCODE, VALUE) \
    [VALUE | 0x0C ] = &&ALU32_##OPCODE##_REG, \
    [VALUE | 0x0F ] = &&ALU64_##OPCODE##_REG

#define ALU_OPCODE_IMM(OPCODE, VALUE)   \
    [VALUE | 0x04 ] = &&ALU32_##OPCODE##_IMM, \
    [VALUE | 0x07 ] = &&ALU64_##OPCODE##_IMM
#else
#define ALU_OPCODE_REG(OPCODE, VALUE) \
    [VALUE | 0x0F ] = &&ALU64_##OPCODE##_REG

#define ALU_OPCODE_IMM(OPCODE, VALUE)   \
    [VALUE | 0x07 ] = &&ALU64_##OPCODE##_IMM
#endif

/* And this macro generates a register and immediate type jumptable entry based
 * on the above macros */
#define ALU_OPCODE(OPCODE, VALUE) \
    ALU_OPCODE_REG(OPCODE, VALUE), \
    ALU_OPCODE_IMM(OPCODE, VALUE)

/* Jump-instruction specific jumptable generator, includes immediate and
 * register based code */
#define JMP_OPCODE(OPCODE, VALUE) \
    [VALUE | 0x05] = &&JMP_##OPCODE##_IMM, \
    [VALUE | 0x0D] = &&JMP_##OPCODE##_REG

/* And finally this generates the opcode entries for memory instructions. It
 * generates the full set of size types supported by eBPF instructions */
#define MEM_OPCODE(OPCODE, VALUE) \
    [VALUE | 0x10] = &&MEM_##OPCODE##_BYTE, \
    [VALUE | 0x08] = &&MEM_##OPCODE##_HALF, \
    [VALUE | 0x00] = &&MEM_##OPCODE##_WORD, \
    [VALUE | 0x18] = &&MEM_##OPCODE##_LONG \

int bpf_run(bpf_t *bpf, const void *ctx, int64_t *result)
{
    int res = BPF_OK;
    bpf->branches_remaining = CONFIG_BPF_BRANCHES_ALLOWED;
    uint64_t regmap[11] = { 0 };
    regmap[1] = (uint64_t)(uintptr_t)ctx;
    regmap[10] = (uint64_t)(uintptr_t)(bpf->stack + bpf->stack_size);


    const volatile bpf_instruction_t *pc = (const volatile bpf_instruction_t*)rbpf_text(bpf);
    bpf_instruction_t instr;
    bool jump_cond = false;
    void* memptr;

    res = bpf_verify_preflight(bpf);
    if (res < 0) {
        return res;
    }

    /* Create an instruction jumptable with calculated addresses for the goto */
    static const void * const _jumptable[256] PROGMEM = {
        [0 ... 255] = &&invalid_instruction,
        ALU_OPCODE(ADD, 0x00),
        ALU_OPCODE(SUB, 0x10),
        ALU_OPCODE(MUL, 0x20),
        ALU_OPCODE(DIV, 0x30),
        ALU_OPCODE(OR,  0x40),
        ALU_OPCODE(AND, 0x50),
        ALU_OPCODE(LSH, 0x60),
        ALU_OPCODE(RSH, 0x70),
        ALU_OPCODE_REG(NEG, 0x80),
        ALU_OPCODE(MOD, 0x90),
        ALU_OPCODE(XOR, 0xa0),
        ALU_OPCODE(MOV, 0xb0),
        ALU_OPCODE(ARSH, 0xc0),

        [0x05] = &&JUMP_ALWAYS,
        JMP_OPCODE(EQ, 0x10),
        JMP_OPCODE(GT, 0x20),
        JMP_OPCODE(GE, 0x30),
        JMP_OPCODE(SET, 0x40),
        JMP_OPCODE(NE, 0x50),
        JMP_OPCODE(SGT, 0x60),
        JMP_OPCODE(SGE, 0x70),
        JMP_OPCODE(LT, 0xA0),
        JMP_OPCODE(LE, 0xB0),
        JMP_OPCODE(SLT, 0xC0),
        JMP_OPCODE(SLE, 0xD0),

        [0x18] = &&MEM_LDDW_IMM,
        [0xB8] = &&MEM_LDDWD_IMM,
        [0xD8] = &&MEM_LDDWR_IMM,

        MEM_OPCODE(STX, 0x63),
        MEM_OPCODE(ST,  0x62),
        MEM_OPCODE(LDX, 0x61),


        [0x85] = &&OPCODE_CALL,
        [0x95] = &&OPCODE_RETURN,
    };

    goto bpf_start;

jump_instr:
    instr = GET_INSTRUCTION(pc);
    if (jump_cond) {
        pc += instr.offset;
        if ((!(bpf->flags & BPF_CONFIG_NO_RETURN)) &&
                bpf->branches_remaining-- == 0) {
            res = BPF_OUT_OF_BRANCHES;
            goto exit;
        }
    }

    /* Intentionally falls through to select_instr */
select_instr:
    pc++;
bpf_start:
    instr = GET_INSTRUCTION(pc);
    //bpf->instruction_count++;
    goto *_jumptable[instr.opcode];

/* Macros implementing the instruction code for the simple ALU based operations */
    ALU(ADD,  +)
    ALU(SUB,  -)
    ALU(AND,  &)
    ALU(OR,   |)
    ALU(LSH, <<)
    ALU(RSH, >>)
    ALU(XOR,  ^)
    ALU(MUL,  *)

ALU64_MOD_REG:
    if (SRC == 0) {
        res = BPF_ILLEGAL_DIV;
        goto exit;
    }
    DST = DST % SRC;
    CONT;
ALU64_MOD_IMM:
    if (IMM == 0) {
        res = BPF_ILLEGAL_DIV;
        goto exit;
    }
    DST = DST % IMM;
    CONT;
#if (CONFIG_BPF_ENABLE_ALU32)
ALU32_MOD_REG:
    if (SRC == 0) {
        res = BPF_ILLEGAL_DIV;
        goto exit;
    }
    DST = (uint32_t)DST % (uint32_t)SRC;
    CONT;
ALU32_MOD_IMM:
    if (IMM == 0) {
        res = BPF_ILLEGAL_DIV;
        goto exit;
    }
    DST = (uint32_t)DST % (uint32_t)IMM;
    CONT;
#endif

ALU64_DIV_REG:
    if (SRC == 0) {
        res = BPF_ILLEGAL_DIV;
        goto exit;
    }
    DST = DST / SRC;
    CONT;
ALU64_DIV_IMM:
    if (IMM == 0) {
        res = BPF_ILLEGAL_DIV;
        goto exit;
    }
    DST = DST / IMM;
    CONT;
#if (CONFIG_BPF_ENABLE_ALU32)
ALU32_DIV_REG:
    if (SRC == 0) {
        res = BPF_ILLEGAL_DIV;
        goto exit;
    }
    DST = (uint32_t)DST / (uint32_t)SRC;
    CONT;
ALU32_DIV_IMM:
    if (IMM == 0) {
        res = BPF_ILLEGAL_DIV;
        goto exit;
    }
    DST = (uint32_t)DST / (uint32_t)IMM;
    CONT;
#endif

ALU64_NEG_REG:
    DST = -(int64_t)DST;
    CONT;

#if (CONFIG_BPF_ENABLE_ALU32)
ALU32_NEG_REG:
    DST = -(int32_t)DST;
    CONT;

    /* MOV */
ALU32_MOV_IMM:
    DST = (uint32_t)IMM;
    CONT;
ALU32_MOV_REG:
    DST = (uint32_t)SRC;
    CONT;
#endif
ALU64_MOV_IMM:
    DST = (uint32_t)IMM;
    CONT;
ALU64_MOV_REG:
    DST = (uint32_t)SRC;
    CONT;

    /* Arithmetic shift */
ALU64_ARSH_REG:
    (*(int64_t*) &DST) >>= SRC;
    CONT;
ALU64_ARSH_IMM:
    (*(int64_t*) &DST) >>= IMM;
    CONT;
#if (CONFIG_BPF_ENABLE_ALU32)
ALU32_ARSH_REG:
    DST = (int32_t)DST >> SRC;
    CONT;
ALU32_ARSH_IMM:
    DST =  (int32_t)DST >> IMM;
    CONT;
#endif

MEM_LDDW_IMM:
    DST = (uint64_t)instr.immediate;
    DST |= (uint64_t)(GET_INSTRUCTION(pc + 1).immediate) << 32;
    pc++;
    CONT;

MEM_LDDWD_IMM:
    DST = (intptr_t)rbpf_data(bpf);
    DST += (uint64_t)instr.immediate;
    DST += (uint64_t)(GET_INSTRUCTION(pc + 1).immediate) << 32;
    pc++;
    CONT;

MEM_LDDWR_IMM:
    DST = (intptr_t)rbpf_rodata(bpf);
    DST += (uint64_t)instr.immediate;
    DST += (uint64_t)(GET_INSTRUCTION(pc + 1).immediate) << 32;
    pc++;
    CONT;

#define MEM(SIZEOP, SIZE)                     \
      MEM_STX_##SIZEOP:                       \
          memptr = bpf_get_mem(bpf, sizeof(SIZE), DST + instr.offset, BPF_MEM_REGION_WRITE); \
          if (memptr == NULL) { \
              goto mem_error; \
          } \
          *(SIZE*)memptr = SRC; \
          CONT;                               \
      MEM_ST_##SIZEOP:                        \
          memptr = bpf_get_mem(bpf, sizeof(SIZE), DST + instr.offset, BPF_MEM_REGION_WRITE); \
          if (memptr == NULL) { \
              goto mem_error; \
          } \
          *(SIZE*)memptr = IMM; \
          CONT;                               \
      MEM_LDX_##SIZEOP:                       \
          memptr = bpf_get_mem(bpf, sizeof(SIZE), SRC + instr.offset, BPF_MEM_REGION_READ); \
          if (memptr == NULL) { \
              goto mem_error; \
          } \
          DST = *(const SIZE*)memptr; \
          CONT;

      MEM(BYTE, uint8_t)
      MEM(HALF, uint16_t)
      MEM(WORD, uint32_t)
      MEM(LONG, uint64_t)
#undef LDST


JUMP_ALWAYS:
    jump_cond = 1;
    CONT_JUMP;
    COND_JMP(ui, EQ, ==)
    COND_JMP(ui, GT, >)
    COND_JMP(ui, GE, >=)
    COND_JMP(ui, LT, <)
    COND_JMP(ui, LE, <=)
    COND_JMP(ui, SET, &)
    COND_JMP(ui, NE, !=)
    COND_JMP(i, SGT, >)
    COND_JMP(i, SGE, >=)
    COND_JMP(i, SLT, <)
    COND_JMP(i, SLE, <=)
OPCODE_CALL:
    {
        bpf_call_t call = bpf_get_call(instr.immediate);
        if (call) {
            regmap[0] = (*(call))(bpf,
                                  regmap[1],
                                  regmap[2],
                                  regmap[3],
                                  regmap[4],
                                  regmap[5]);
            CONT;
        }
        else {
            res = BPF_ILLEGAL_CALL;
            goto exit;
        }
    }
OPCODE_RETURN:
    goto exit;

invalid_instruction:
    res = BPF_ILLEGAL_INSTRUCTION;
    goto exit;

mem_error:
    res = BPF_ILLEGAL_MEM;

exit:

    *result = regmap[0];
    return res;
}

