/*
 * Copyright (C) 2018-2021 Western Digital Corporation or its affiliates
 * Copyright (C) 2017-2018 Wearable, Inc.
 * Copyright (C) 2000-2012 leJOS Contributors
 * Copyright (C) 2000 Jose H. Solorzano and TinyVM Contributors
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 * 
 * This Source Code Form is “Incompatible With Secondary Licenses”,
 * as defined by the Mozilla Public License, v. 2.0.
 */

#ifdef __APPLE_CC__
#define _BSD_SOURCE
#else
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/mman.h>
#ifdef __APPLE_CC__
// Apple does not allow ATA Pass-through
#else
#define HAVE_SCSI_ATA_PASSTHROUGH
#include <scsi/sg.h>
#include <linux/nvme_ioctl.h>
#endif
#include <sys/types.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include "types.h"
#include "constants.h"
#include "classes.h"
#include "threads.h"
#include "stack.h"
#include "specialclasses.h"
#include "specialsignatures.h"
#include "language.h"
#include "memory.h"
#include "interpreter.h"
#include "exceptions.h"
#include "load.h"
#include "trace.h"
#include "poll.h"
#include "platform_hooks.h"
#include "run.h"
#include <errno.h>
#include <limits.h>
#include "sb_hmac_drbg.h"

static const uint32_t bridge_delay = 10000000; // nanoseconds
static const uint32_t bridge_max_write = 32; // bytes

static int random_fd;
static int bridge_fd;
static char* bridge_file;

void bridge_open(void)
{
#define CC(call)                                \
            if ((call) < 0) {                   \
                perror(bridge_file);            \
                abort();                        \
            }
    CC(bridge_fd = open(bridge_file, O_RDWR | O_NOCTTY | O_NONBLOCK));
    int flags;
    CC(flags = fcntl(bridge_fd, F_GETFL, 0));
    CC(fcntl(bridge_fd, F_SETFL, flags & ~O_NONBLOCK));
    struct termios options;
    memset(&options, 0, sizeof(options));
    CC(tcgetattr(bridge_fd, &options));
    cfmakeraw(&options);
    options.c_cflag |= (CS8 | CLOCAL | CRTSCTS);
    CC(cfsetspeed(&options, 9600));
    CC(tcsetattr(bridge_fd, TCSANOW, &options));

    platform_check_event(1);

    CC(cfsetspeed(&options, 115200));
    CC(tcsetattr(bridge_fd, TCSANOW, &options));
}

JINT platform_check_event(JINT type)
{
    if (type == 1) {
        if (bridge_fd) {
            fd_set set;
            struct timespec t;
            FD_ZERO(&set);
            FD_SET(bridge_fd, &set);
            memset(&t, 0, sizeof(t));
            do {
                if (pselect(bridge_fd + 1, &set, NULL, NULL, &t, NULL) < 0) {
                    if (errno == EINTR) {
                        continue;
                    }
                    perror(NULL);
                    abort();
                }
                break;
            } while (1);
            if (FD_ISSET(bridge_fd, &set)) {
                return 1;
            }
        }
    }
    return 0;
}

void platform_wait_events(TIMESTAMP until)
{
    fd_set set;
    FD_ZERO(&set);
    struct timespec t;
    int maxfd = 0;
    if (bridge_fd) {
        FD_SET(bridge_fd, &set);
        maxfd = bridge_fd + 1;
    }
    // ((TIMESTAMP) ts.tv_sec) * 1000 + ((TIMESTAMP) ts.tv_nsec) / 1000000

    // prevent overflow
    if (until >= (TIMESTAMP) INT_MAX) {
        until = 0;
    }

    t.tv_sec = until / 1000;
    t.tv_nsec = (until % 1000) * 1000000;

    // for long waits
    do {
        if (pselect(maxfd, &set, NULL, NULL, ((until > 0) ? &t : NULL), NULL) < 0) {
            if (errno == EINTR) {
                // it's ok; we'll come back here
                break;
            }
            perror(NULL);
            abort();
        }
        break;
    } while (1);
}

inline void instruction_hook(nvm_state_t* pState)
{
#if FAST_DISPATCH
    pState->dispatch_table = pState->dispatch_check_event;
#else
    pState->scheduler_make_request = true;
#endif
}

void oom_hook(nvm_state_t* pState)
{

}

void gc_start_hook(nvm_state_t* pState)
{

}

void gc_end_hook(nvm_state_t* pState)
{

}

void assert_hook(boolean aCond, int aCode)
{
    if (aCond) {
        return;
    }
    printf ("Assertion violation: %d\n", aCode);
    exit (aCode);
}

static TIMESTAMP diff_nanotime(TIMESTAMP old_sec, TIMESTAMP old_nsec, TIMESTAMP new_sec, TIMESTAMP new_nsec)
{
    if (new_sec == old_sec + 1) {
        new_sec--;
        new_nsec += 1000000000ull;
    }

    if (new_sec == old_sec) {
        return new_nsec - old_nsec;
    } else {
        return (TIMESTAMP) -1;
    }
}

static void sleep_ns(TIMESTAMP ns)
{
    do {
        struct timespec t = {.tv_sec = 0, .tv_nsec = (long) ns};
        struct timespec rm;
        if (nanosleep(&t, &rm) == -1) {
            if (errno == EINTR) {
                continue;
            } else {
                perror(NULL);
                abort();
            }
        }
        break;
    } while (1);
}

void realFlush(void)
{
    fflush(stdout);
}

#ifdef HAVE_SCSI_ATA_PASSTHROUGH
enum scb_protocol {
    scbp_ATA_hardware_reset = 0,  // ATA Hardware reset
    scbp_SRST = 1,		// ATA Software reset
    scbp_non_data = 3,
    scbp_PIO_DATA_IN = 4,
    scbp_PIO_DATA_OUT = 5,
    scbp_DMA = 6,
    scbp_DMA_Queued = 7,
    scbp_Diagnostic = 8,
    scbp_DEVICE_RESET = 9,
    scbp_UDMA_DATA_IN = 10,
    scbp_UDMA_DATA_OUT = 11,
    scbp_FPDMA = 12,
    scbp_return_response_info = 15,
};

typedef struct {
    uint8_t op_code; // 0x85 is ATA pass-through (16) command
    uint8_t cnt_protocol_extend;
    uint8_t flags;
    uint8_t features;
    uint8_t sector_count;
    uint8_t lba_low;
    uint8_t lba_mid;
    uint8_t lba_high;
    uint8_t device;
    uint8_t command;
    uint8_t reserved;
    uint8_t control;
} ata_cmd12_t;

#define OP_ATA_PASSTHROUGH16 0x85
#define OP_ATA_PASSTHROUGH12 0xA1

#define SCB_FLAG_LEN_0	0x00	    // No data to transfer
#define SCB_FLAG_LEN_IN_FEAT 0x01   // length is in features field
#define SCB_FLAG_LEN_IN_SCNT 0x02   // length is in sector_count field.
#define SCB_FLAG_LEN_IN_TPSIU 0x03  // length is stored somewhere else...
#define SCB_FLAG_LEN_IS_BLOCKS 0x04 // length is in # of 512 byte blocks
#define SCB_FLAG_TDIR_FROM_DEV 0x08 // data from device
#define SCB_FLAG_TDIR_TO_DEV   0x00 // data to device
#define SCB_FLAG_CK_COND    0x20    // always check condition (even success)
#define SCB_FLAG_OFF_LINE_0 0x00    // Don't wait for registers to be valid
#define SCB_FLAG_OFF_LINE_2 0x40    // Wait 2 seconds for status regs
#define SCB_FLAG_OFF_LINE_6 0x80    // Wait 6 seconds for status regs
#define SCB_FLAG_OFF_LINE_14 0xC0   // Wait 14 seconds for status regs

#define OpalTrustedRecvPIO 0x5C
#define OpalTrustedRecvDMA 0x5D
#define OpalTrustedSendPIO 0x5E
#define OpalTrustedSendDMA 0x5F

#define SCB_CPE_COUNT_SHIFT  5
#define SCB_CPE_PROTOCOL_SHIFT  1
#define SCB_CPE_EXTENDED_SHIFT  0

static void dump_buffer(const void *buffer, unsigned max_length, const char *name)
{
    printf("%s:", name ? name : "buffer");
    uint8_t *p = (uint8_t*)buffer;
    for (unsigned i=0; i<max_length; i++) {
        if ((i % 16) == 0) {
            printf("\n%03x:  ", i);
        }
        if ((i & 3) == 0) {
            printf(" ");
        }
        printf("%02x", p[i]);
    }
    printf("\n");
}

static void block_sigalarm()
{
    int status;
    sigset_t newset;
    sigset_t oldset;
    sigemptyset(&newset);
    sigaddset(&newset, SIGALRM);
    status = sigprocmask(SIG_BLOCK, &newset, &oldset);
    if (status < 0) {
        printf("Problem blocking sigalrm");
        fflush(stdout);
        perror("block_sigalarm");
    }
}

static void unblock_sigalarm()
{
    int status;
    sigset_t newset;
    sigset_t oldset;
    sigemptyset(&newset);
    sigaddset(&newset, SIGALRM);
    status = sigprocmask(SIG_UNBLOCK, &newset, &oldset);
    if (status < 0) {
        printf("Problem ublocking sigalrm");
        fflush(stdout);
        perror("unblock_sigalarm");
    }
}
#endif // HAVE_SCSI_ATA_PASSTHROUGH

int dispatch_platform(nvm_state_t* pState, uint16_t signature, STACKWORD * paramBase)
{
    {
        _Bool is_crypt = 0;
        int crypt_ret = dispatch_platform_crypt(pState, signature, paramBase, &is_crypt);
        if (is_crypt) {
            return crypt_ret;
        }
    }
    switch (signature) {
    case initRandom_4_1B_5I: {
        Object* drbg_obj = word2obj(paramBase[0]); 
        sb_hmac_drbg_state_t* drbg = (sb_hmac_drbg_state_t*) jbyte_array(drbg_obj);
        sb_error_t err = SB_ERROR_INSUFFICIENT_ENTROPY;
        if (!random_fd) {
            random_fd = open("/dev/urandom", O_RDONLY);
            if (random_fd < 0) {
                perror(NULL);
                abort();
            }

            uint8_t e[SB_HMAC_DRBG_MIN_ENTROPY_INPUT_LENGTH + SB_HMAC_DRBG_MIN_NONCE_LENGTH];
            size_t remaining = sizeof(e);
            uint8_t* data = e;
            do {
                int bytes;
                if ((bytes = read(random_fd, data, remaining)) < 0) {
                    perror(NULL);
                    abort();
                }
                remaining -= bytes;
                data += bytes;
            } while (remaining);

            err = sb_hmac_drbg_init(drbg, e, SB_HMAC_DRBG_MIN_ENTROPY_INPUT_LENGTH,
                                    e + SB_HMAC_DRBG_MIN_ENTROPY_INPUT_LENGTH, SB_HMAC_DRBG_MIN_NONCE_LENGTH, NULL, 0);
        }
        push_word_cur(pState, err);
        return EXEC_CONTINUE;                                    
    }    
    case fillWithRandom_4_1B_1B_5I: {
        Object* drbg_obj = word2obj(paramBase[0]); 
        sb_hmac_drbg_state_t* drbg = (sb_hmac_drbg_state_t*) jbyte_array(drbg_obj);

        Object* data_obj = word2obj(paramBase[1]);
        uint8_t* data = (uint8_t*) jbyte_array(data_obj);
        size_t size = nvm_array_size(data_obj);
        sb_error_t err = sb_hmac_drbg_generate(drbg, data, size);
        push_word_cur(pState, err);
        return EXEC_CONTINUE;
    }
    case fillWithRandom_4_1B_1B_1B_5I: {

        Object* drbg_obj = word2obj(paramBase[0]); 
        sb_hmac_drbg_state_t* drbg = (sb_hmac_drbg_state_t*) jbyte_array(drbg_obj);

        Object* data_obj = word2obj(paramBase[1]);
        uint8_t* data = (uint8_t*) jbyte_array(data_obj);
        size_t size = nvm_array_size(data_obj);

        Object* add_obj = word2obj(paramBase[2]);
        uint8_t* add_data = (uint8_t*) jbyte_array(add_obj);
        size_t add_size = nvm_array_size(add_obj);

        const sb_byte_t* vec_additional[SB_HMAC_DRBG_ADD_VECTOR_LEN] = { (unsigned char *)add_data, NULL, NULL };
        const size_t vec_additional_len[SB_HMAC_DRBG_ADD_VECTOR_LEN] = { add_size, 0, 0 };
        
        sb_error_t  err = sb_hmac_drbg_generate_additional_vec(drbg, data, size, vec_additional, vec_additional_len);
        push_word_cur(pState, err);
        return EXEC_CONTINUE;
    }
    case reseedRandom_4_1B_1B_5I: {

        Object* drbg_obj = word2obj(paramBase[0]); 
        sb_hmac_drbg_state_t* drbg = (sb_hmac_drbg_state_t*) jbyte_array(drbg_obj);

        uint8_t e[SB_HMAC_DRBG_MIN_ENTROPY_INPUT_LENGTH];
        size_t remaining = sizeof(e);
        uint8_t* data = e;
        do {
            int bytes;
            if ((bytes = read(random_fd, data, remaining)) < 0) {
                perror(NULL);
                abort();
            }
            remaining -= bytes;
            data += bytes;
        } while (remaining);

        Object* add_obj = (Object*) word2obj(paramBase[1]);
        uint8_t* add = NULL;
        size_t add_size = 0;
        if (add_obj != NULL) {
            add = (uint8_t*) jbyte_array(add_obj);
            add_size = nvm_array_size(add_obj);
        }
        sb_error_t  err = sb_hmac_drbg_reseed(drbg, e, SB_HMAC_DRBG_MIN_ENTROPY_INPUT_LENGTH, add, add_size);
        push_word_cur(pState, err);
        return EXEC_CONTINUE;
    }
#ifdef USE_BRIDGE    
    case bridge_0open_4_5V: {
        if (bridge_fd > 0) {
            return EXEC_CONTINUE;
        } else if (bridge_file != NULL) {
            bridge_open();
        } else {
            return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
        }
    }
    case bridge_0read_4_1B_5V: {
        if (bridge_fd > 0) {
            Object* data_obj = (Object*) word2obj(paramBase[0]);
            uint8_t* data = (uint8_t*) jbyte_array(data_obj);
            int remaining = nvm_array_size(data_obj);
            do {
                int bytes;
                if ((bytes = read(bridge_fd, data, remaining)) < 0) {
                    if (errno == EINTR) {
                        continue;
                    }
                    return nvm_throw_new_exception(pState, JAVA_LANG_RUNTIMEEXCEPTION);
                }
                remaining -= bytes;
                data += bytes;
            } while (remaining);
            //printf("read %d bytes\n", nvm_array_size(data_obj));
            //fflush(NULL);
            return EXEC_CONTINUE;
        } else {
            return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
        }
    }
    case bridge_0write_4_1B_5V: {
        if (bridge_fd > 0) {
            Object* data_obj = (Object*) word2obj(paramBase[0]);
            uint8_t* data = (uint8_t*) jbyte_array(data_obj);
            uint32_t remaining = nvm_array_size(data_obj);
            static TIMESTAMP old_sec, old_nsec;
            TIMESTAMP new_sec, new_nsec;
            do {
                int bytes;
                TIMESTAMP diff;
                get_nanotime(&new_sec, &new_nsec);
                diff = diff_nanotime(old_sec, old_nsec, new_sec, new_nsec);
                if (diff < bridge_delay) {
                    sleep_ns(diff);
                }
                old_sec = new_sec;
                old_nsec = new_nsec;
                if ((bytes = write(bridge_fd, data, (remaining > bridge_max_write ? bridge_max_write : remaining))) < 0) {
                    if (errno == EINTR) {
                        continue;
                    }
                    return nvm_throw_new_exception(pState, JAVA_LANG_RUNTIMEEXCEPTION);
                }
                remaining -= bytes;
                data += bytes;
            } while (remaining);
            //printf("wrote %d bytes\n", nvm_array_size(data_obj));
            //fflush(NULL);
            return EXEC_CONTINUE;
        } else {
            return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
        }
    }
#endif
#ifdef USE_OPAL    
    case openOpalDrive_4Ljava_3lang_3String_2_5I: {
        //fprintf(stderr, "Hello from open!\n");
#ifndef HAVE_SCSI_ATA_PASSTHROUGH
        fprintf(stderr, "Error: We do not have pass-through\n");
        return nvm_throw_new_exception(pState, JAVA_LANG_RUNTIMEEXCEPTION);
#else
        // Convert the String to a "C" string of 7-bit ASCII
        String *path = (String *)word2obj(paramBase[0]);
        Object *chars_obj = ref2obj(path->characters);
        JCHAR *jchars = jchar_array(chars_obj);
        uint32_t num_chars = nvm_array_size(chars_obj);
        char filename[32];
        if (num_chars < sizeof(filename)) {
            for (int i=0; i < num_chars; i++) {
                if (jchars[i] & ~0x7F) { // We only handle plain 7-bit ASCII.
                    fprintf(stderr, "openOpalDrive found an unexpected character in the filename: 0x%04x\n", jchars[i]);
                    return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
                }
                filename[i] = (char)(jchars[i] & 0x7F);
            }
            filename[num_chars] = '\0';

            int result = open(filename, O_RDWR);
            if (result < 0) {
                perror(filename);
                return nvm_throw_new_exception(pState, JAVA_LANG_RUNTIMEEXCEPTION);
            }
            //fprintf(stderr, "openOpalDrive got fd: %d\n", result);
            push_word_cur(pState, result);
            return EXEC_CONTINUE;
        } else {
            fprintf(stderr, "openOpalDrive the filename is too long.  length: %d\n", num_chars);
            return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
        }
#endif
    }
    case closeOpalDrive_4I_5I: {
#ifndef HAVE_SCSI_ATA_PASSTHROUGH
        return nvm_throw_new_exception(pState, JAVA_LANG_RUNTIMEEXCEPTION);
#else
        int32_t driveID = paramBase[0];
        int result = close(driveID);
        if (result < 0) {
            return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
        } else {
            push_word_cur(pState, result);
        }
        return EXEC_CONTINUE;
#endif
    }
    case nvmeIOOperation_4IZZIIIIIIIIII_1B_5I: {
#ifndef HAVE_SCSI_ATA_PASSTHROUGH
        return nvm_throw_new_exception(pState, JAVA_LANG_RUNTIMEEXCEPTION);
#else
        //int nvmeIOOperation(int drive, boolean isWrite, boolean isAdmin, int opcode, int flags, int nsid,
        //int cdw10, int cdw11, int cdw12, int cdw13, int cdw14, int cdw15, int timeout_ms, byte[] buffer) throws RuntimeException;

        int32_t drive = paramBase[0];
        //int32_t isWrite = paramBase[1];
        int32_t isAdmin = paramBase[2];
        int32_t opcode = paramBase[3];
        int32_t flags = paramBase[4];
        uint32_t nsid = paramBase[5];
        uint32_t cdw10 = paramBase[6];
        uint32_t cdw11 = paramBase[7];
        uint32_t cdw12 = paramBase[8];
        uint32_t cdw13 = paramBase[9];
        uint32_t cdw14 = paramBase[10];
        uint32_t cdw15 = paramBase[11];
        uint32_t timeoutMs = paramBase[12];
        Object *bufferObj = word2obj(paramBase[13]);
        JBYTE *bufferBytes = jbyte_array(bufferObj);
        uint32_t bufferLength = nvm_array_size(bufferObj);
        if ((bufferLength % 512) != 0) {
            // The buffer should be a multiple of 512 bytes.
            fprintf(stderr, "nvmeIOOperation: The buffer length was not a multiple of 512 bytes: %d\n", bufferLength);
            return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
        }

        // Do the command
        struct nvme_passthru_cmd nvme_cmd;
        memset(&nvme_cmd, 0, sizeof(nvme_cmd));
        nvme_cmd.opcode = (uint8_t)opcode;
        nvme_cmd.flags = (uint8_t)flags;
        nvme_cmd.nsid = nsid;
        nvme_cmd.addr = (__u64)bufferBytes;
        nvme_cmd.data_len = bufferLength;
        nvme_cmd.cdw10 = cdw10;
        nvme_cmd.cdw11 = cdw11;
        nvme_cmd.cdw12 = cdw12;
        nvme_cmd.cdw13 = cdw13;
        nvme_cmd.cdw14 = cdw14;
        nvme_cmd.cdw15 = cdw15;
        nvme_cmd.timeout_ms = timeoutMs;

#if 0
        dump_buffer(&nvme_cmd, sizeof(nvme_cmd), "nvme_cmd");
        printf("Sending to fd: %d  bufferLength: %d\n", drive, bufferLength);
#endif

        uint32_t result;
        int status;

        block_sigalarm();
        status = ioctl(drive, (isAdmin ? NVME_IOCTL_ADMIN_CMD : NVME_IOCTL_IO_CMD), &nvme_cmd);
        unblock_sigalarm();

        if (status < 0) {
            int err = errno;
            fflush(stdout);
            perror("nvme_io");
            fprintf(stderr, "errno: %d\n", err);
            result = 0xFFFFFFFF;
        } else {
            result = nvme_cmd.result;
        }
        //fprintf(stderr,"nvmeIOOperation: returning %08x ioctl status: %d\n", result, status);
        push_word_cur(pState, status);
        return EXEC_CONTINUE;
#endif
    }
    case scsiIOOperation_4IZ_1B_1B_1B_5Z: {
#ifndef HAVE_SCSI_ATA_PASSTHROUGH
        return nvm_throw_new_exception(pState, JAVA_LANG_RUNTIMEEXCEPTION);
#else
        int32_t drive = paramBase[0];
        int32_t isWrite = paramBase[1];
        Object *commandObj = word2obj(paramBase[2]);
        uint8_t *commandBytes = jbyte_array(commandObj);
        uint32_t commandLength = nvm_array_size(commandObj);
        Object *bufferObj = word2obj(paramBase[3]);
        uint8_t *bufferBytes = jbyte_array(bufferObj);
        uint32_t bufferLength = nvm_array_size(bufferObj);
        Object *senseObj = word2obj(paramBase[4]);
        uint8_t *senseBytes = jbyte_array(senseObj);
        uint32_t senseLength = nvm_array_size(senseObj);
        if ((bufferLength % 512) != 0) {
            // The buffer should be a multiple of 512 bytes.
            fprintf(stderr, "scsiIOOperation: The buffer length was not a multiple of 512 bytes: %d\n", bufferLength);
            return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
        }
        // Do the command
        struct sg_io_hdr sg_io;
        unsigned block_count = bufferLength / 512;
        memset(&sg_io, 0, sizeof(sg_io));
        sg_io.interface_id = 'S';
        sg_io.cmdp = commandBytes;
        sg_io.cmd_len = commandLength;
        sg_io.dxferp = bufferBytes;
        sg_io.dxfer_len = bufferLength;
        sg_io.dxfer_direction = isWrite ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
        sg_io.sbp = senseBytes;
        sg_io.mx_sb_len = senseLength;
        sg_io.timeout = 5000; // 5 seconds

#if 0
        dump_buffer(&sg_io, sizeof(sg_io), "sg_io");
        printf("Sending to fd: %d   command length: %d\n", drive, commandLength);
        printf("bufferLength: %d  block_count: %d  senseLength: %d  io_write: %d\n",
            bufferLength, block_count, senseLength, io_write);
        dump_buffer(commandBytes, commandLength, "Command Buffer");
#endif

        bool result;
        int status;

        block_sigalarm();
        status = ioctl(drive, SG_IO, &sg_io);
        unblock_sigalarm();

        if (status < 0) {
            int err = errno;
            fflush(stdout);
            perror("sg_io");
            fprintf(stderr, "errno: %d\n", err);
            result = false;
        } else if ((sg_io.info & SG_INFO_OK_MASK) == SG_INFO_OK) {
            //printf("info seems to say it went OK.\n");
            result = true;
        } else {
            result = false;
        }
        //fprintf(stderr,"scsiIOOperation: returning %d\n", result);
        push_word_cur(pState, result);
        return EXEC_CONTINUE;
#endif
    }
#endif

#ifdef USE_SIDE_CHANNEL
    case sideChannelConfigure_4III_5I:
    case sideChannelStartTransaction_4BB_1B_5I:
    case sideChannelResponseSize_4_5I:
    case sideChannelGetResponse_4_1B_5I: {
        int result = -5; /* Not supported */
        push_word_cur(pState, result);
        return EXEC_CONTINUE;
    }
#endif
    default:
        printf("Unknown command: %d\n", signature);
        return nvm_throw_new_exception(pState, JAVA_LANG_NOSUCHMETHODERROR);
    }
}

void firmware_exception_handler(Throwable *exception, STACKWORD method, STACKWORD pc)
{
    // FIXME: steal some logic from the original NXT implementation of this
    printf ("*** UNCAUGHT FIRMWARE EXCEPTION/ERROR: \n");
    printf ("--  Exception class   : %u\n", (unsigned) nvm_object_class_index ((Object*) exception));
    printf ("--  Method            : %u\n", (unsigned) method);
    printf ("--  Bytecode offset   : %u\n", (unsigned) pc);
    abort();
}

void handle_uncaught_exception (nvm_state_t* pState, Object *exception,
                                const Thread *thread,
                                const MethodRecord *methodRecord,
                                const MethodRecord *rootMethod,
                                byte *pc)
{
    printf ("*** UNCAUGHT EXCEPTION/ERROR: \n");
    printf ("--  Exception class   : %u\n", (unsigned) nvm_object_class_index (exception));
    printf ("--  Thread            : %u\n", (unsigned) thread->threadId);
    printf ("--  Method signature  : %u\n", (unsigned) methodRecord->signatureId);
    printf ("--  Root method sig.  : %u\n", (unsigned) rootMethod->signatureId);
    printf ("--  Bytecode offset   : %u\n", (unsigned) pc - (int) get_code_ptr(pState, methodRecord));
}

static void usage(const char *progname)
{
        printf("Usage: %s [-t tty] [-h heapsize] file.njb [program_arg ...]\n", progname);
        printf("The \"program_arg\" values are passed to the Java function: main.\n");
}

int main(int argc, char *argv[])
{
    char *file = argv[argc - 1];
    int option;
    int heap_size = 32;
    if (argc < 2) {
        usage(argv[0]);
        exit(1);
    }
    while ((option = getopt(argc, argv, "t:h:")) >= 0) {
        switch (option) {
        case 't': {
            bridge_file = optarg;
            continue;
        }
        case 'h': {
            heap_size = atoi(optarg);
            if (heap_size < 4) {
                printf("bad heap size: %d\n", heap_size);
            }
            continue;
        }
        default:
            printf("%s: unknown option %c\n", argv[0], option);
            usage(argv[0]);
            exit(1);
        }
    }
    if (optind >= argc) {
        printf("Missing .njb file\n");
        usage(argv[0]);
        exit(1);
    }
    file = argv[optind];
    optind++;
    int arglen = argc - optind;
    size_t blob_size = 0x1000000u; // 16MB should be enough for anyone...
    void* blob = malloc(blob_size);
    if (!blob) {
        perror(argv[0]);
        return 1;
    }

    nvm_state_t* pState = (nvm_state_t*) blob;
    blob = (void*) (((uintptr_t) blob) + sizeof(nvm_state_t));
    blob_size -= sizeof(nvm_state_t);

    void* heap = blob;
    blob = (void*) (((uintptr_t) blob) + (heap_size * 1024));
    blob_size -= (heap_size * 1024);

    void* binary = blob;
    if (readBinary(file, binary, blob_size) == 0) {
        return 1;
    }

    if (!nvm_run(pState, heap, heap_size * 1024, 0, binary, arglen, &argv[optind])) {
        printf("Error: invalid binary!\n");
        return 1;
    }
    return 0;
}


