/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Handle SLiRP library processing.
 *
 *          Some of the code was borrowed from libvdeslirp
 *          <https://github.com/virtualsquare/libvdeslirp>
 *
 *
 *
 * Authors: Fred N. van Kempen, <decwiz@yahoo.com>
 *          RichardG, <richardg867@gmail.com>
 *
 *          Copyright 2017-2019 Fred N. van Kempen.
 *          Copyright 2020 RichardG.
 */
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/plat.h>
#include <86box/thread.h>
#include <86box/timer.h>
#include <86box/network.h>
#include <86box/machine.h>
#include <86box/ini.h>
#include <86box/config.h>
#include <86box/video.h>
#include <86box/bswap.h>

#define _SSIZE_T_DEFINED
#include <slirp/libslirp.h>

#ifdef _WIN32
#    define WIN32_LEAN_AND_MEAN
#    include <windows.h>
#else
#    include <poll.h>
#endif
#include <86box/net_event.h>

#define SLIRP_PKT_BATCH NET_QUEUE_LEN

enum {
    NET_EVENT_STOP = 0,
    NET_EVENT_TX,
    NET_EVENT_RX,
    NET_EVENT_MAX
};

typedef struct net_slirp_t {
    Slirp     *slirp;
    uint8_t    mac_addr[6];
    netcard_t *card; /* netcard attached to us */
    thread_t  *poll_tid;
    net_evt_t  tx_event;
    net_evt_t  stop_event;
    netpkt_t   pkt;
    netpkt_t   pkt_tx_v[SLIRP_PKT_BATCH];
#ifdef _WIN32
    HANDLE     sock_event;
#else
    uint32_t       pfd_len;
    uint32_t       pfd_size;
    struct pollfd *pfd;
#endif
} net_slirp_t;

/* Pulled off from libslirp code. This is only needed for modem. */
#pragma pack(push, 1)
struct arphdr_local {
    unsigned char h_dest[6]; /* destination eth addr */
    unsigned char h_source[6]; /* source ether addr    */
    unsigned short h_proto; /* packet type ID field */

    unsigned short ar_hrd; /* format of hardware address */
    unsigned short ar_pro; /* format of protocol address */
    unsigned char ar_hln; /* length of hardware address */
    unsigned char ar_pln; /* length of protocol address */
    unsigned short ar_op; /* ARP opcode (command)       */

    /*
     *  Ethernet looks like this : This bit is variable sized however...
     */
    uint8_t ar_sha[6]; /* sender hardware address */
    uint32_t ar_sip; /* sender IP address       */
    uint8_t ar_tha[6]; /* target hardware address */
    uint32_t ar_tip; /* target IP address       */
};
#pragma pack(pop)

#ifdef ENABLE_SLIRP_LOG
int slirp_do_log = ENABLE_SLIRP_LOG;

static void
slirp_log(const char *fmt, ...)
{
    va_list ap;

    if (slirp_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#    define slirp_log(fmt, ...)
#endif

static void
net_slirp_guest_error(UNUSED(const char *msg), UNUSED(void *opaque))
{
    slirp_log("SLiRP: guest_error(): %s\n", msg);
}

static int64_t
net_slirp_clock_get_ns(UNUSED(void *opaque))
{
    return (int64_t) ((double) tsc / cpuclock * 1000000000.0);
}

static void *
net_slirp_timer_new(SlirpTimerCb cb, void *cb_opaque, UNUSED(void *opaque))
{
    pc_timer_t *timer = malloc(sizeof(pc_timer_t));
    timer_add(timer, cb, cb_opaque, 0);
    return timer;
}

static void
net_slirp_timer_free(void *timer, UNUSED(void *opaque))
{
    timer_stop(timer);
    free(timer);
}

static void
net_slirp_timer_mod(void *timer, int64_t expire_timer, UNUSED(void *opaque))
{
    timer_on_auto(timer, expire_timer * 1000);
}

static void
net_slirp_register_poll_fd(int fd, void *opaque)
{
    (void) fd;
    (void) opaque;
}

static void
net_slirp_unregister_poll_fd(int fd, void *opaque)
{
    (void) fd;
    (void) opaque;
}

static void
net_slirp_notify(void *opaque)
{
    (void) opaque;
}

#if SLIRP_CHECK_VERSION(4, 8, 0)
slirp_ssize_t
#else
ssize_t
#endif
net_slirp_send_packet(const void *qp, size_t pkt_len, void *opaque)
{
    net_slirp_t *slirp = (net_slirp_t *) opaque;

    slirp_log("SLiRP: received %d-byte packet\n", pkt_len);

    memcpy(slirp->pkt.data, (uint8_t *) qp, pkt_len);
    slirp->pkt.len = pkt_len;
    network_rx_put_pkt(slirp->card, &slirp->pkt);

    return pkt_len;
}

#ifdef _WIN32
static int
net_slirp_add_poll(int fd, int events, void *opaque)
{
    net_slirp_t *slirp   = (net_slirp_t *) opaque;
    long         bitmask = 0;
    if (events & SLIRP_POLL_IN)
        bitmask |= FD_READ | FD_ACCEPT;
    if (events & SLIRP_POLL_OUT)
        bitmask |= FD_WRITE | FD_CONNECT;
    if (events & SLIRP_POLL_HUP)
        bitmask |= FD_CLOSE;
    if (events & SLIRP_POLL_PRI)
        bitmask |= FD_OOB;

    WSAEventSelect(fd, slirp->sock_event, bitmask);
    return fd;
}
#else
static int
net_slirp_add_poll(int fd, int events, void *opaque)
{
    net_slirp_t *slirp = (net_slirp_t *) opaque;

    if (slirp->pfd_len >= slirp->pfd_size) {
        int newsize = slirp->pfd_size + 16;
        struct pollfd *new = realloc(slirp->pfd, newsize * sizeof(struct pollfd));
        if (new) {
            slirp->pfd = new;
            slirp->pfd_size = newsize;
        }
    }
    if ((slirp->pfd_len < slirp->pfd_size)) {
        int idx = slirp->pfd_len++;
        slirp->pfd[idx].fd = fd;
        int pevents = 0;
        if (events & SLIRP_POLL_IN)
            pevents |= POLLIN;
        if (events & SLIRP_POLL_OUT)
            pevents |= POLLOUT;
        if (events & SLIRP_POLL_ERR)
            pevents |= POLLERR;
        if (events & SLIRP_POLL_PRI)
            pevents |= POLLPRI;
        if (events & SLIRP_POLL_HUP)
            pevents |= POLLHUP;
        slirp->pfd[idx].events = pevents;
        return idx;
    } else
        return -1;
}
#endif

#ifdef _WIN32
static int
net_slirp_get_revents(int idx, void *opaque)
{
    net_slirp_t     *slirp = (net_slirp_t *) opaque;
    int              ret   = 0;
    WSANETWORKEVENTS ev;
    if (WSAEnumNetworkEvents(idx, slirp->sock_event, &ev) != 0) {
        return ret;
    }

#    define WSA_TO_POLL(_wsaev, _pollev)                \
        do {                                            \
            if (ev.lNetworkEvents & (_wsaev)) {         \
                ret |= (_pollev);                       \
                if (ev.iErrorCode[_wsaev##_BIT] != 0) { \
                    ret |= SLIRP_POLL_ERR;              \
                }                                       \
            }                                           \
        } while (0)

    WSA_TO_POLL(FD_READ, SLIRP_POLL_IN);
    WSA_TO_POLL(FD_ACCEPT, SLIRP_POLL_IN);
    WSA_TO_POLL(FD_WRITE, SLIRP_POLL_OUT);
    WSA_TO_POLL(FD_CONNECT, SLIRP_POLL_OUT);
    WSA_TO_POLL(FD_OOB, SLIRP_POLL_PRI);
    WSA_TO_POLL(FD_CLOSE, SLIRP_POLL_HUP);

    return ret;
}
#else
static int
net_slirp_get_revents(int idx, void *opaque)
{
    net_slirp_t *slirp = (net_slirp_t *) opaque;
    int ret = 0;
    int events = slirp->pfd[idx].revents;
    if (events & POLLIN)
        ret |= SLIRP_POLL_IN;
    if (events & POLLOUT)
        ret |= SLIRP_POLL_OUT;
    if (events & POLLPRI)
        ret |= SLIRP_POLL_PRI;
    if (events & POLLERR)
        ret |= SLIRP_POLL_ERR;
    if (events & POLLHUP)
        ret |= SLIRP_POLL_HUP;
    return ret;
}
#endif

static const SlirpCb slirp_cb = {
    .send_packet        = net_slirp_send_packet,
    .guest_error        = net_slirp_guest_error,
    .clock_get_ns       = net_slirp_clock_get_ns,
    .timer_new          = net_slirp_timer_new,
    .timer_free         = net_slirp_timer_free,
    .timer_mod          = net_slirp_timer_mod,
    .register_poll_fd   = net_slirp_register_poll_fd,
    .unregister_poll_fd = net_slirp_unregister_poll_fd,
    .notify             = net_slirp_notify
};

/* Send a packet to the SLiRP interface. */
static void
net_slirp_in(net_slirp_t *slirp, uint8_t *pkt, int pkt_len)
{
    if (!slirp)
        return;

    slirp_log("SLiRP: sending %d-byte packet to host network\n", pkt_len);

    slirp_input(slirp->slirp, (const uint8_t *) pkt, pkt_len);
}

void
net_slirp_in_available(void *priv)
{
    net_slirp_t *slirp = (net_slirp_t *) priv;
    net_event_set(&slirp->tx_event);
}

#ifdef _WIN32
static void
net_slirp_thread(void *priv)
{
    net_slirp_t *slirp = (net_slirp_t *) priv;

    /* Start polling. */
    slirp_log("SLiRP: polling started.\n");

    HANDLE events[3];
    events[NET_EVENT_STOP] = net_event_get_handle(&slirp->stop_event);
    events[NET_EVENT_TX]   = net_event_get_handle(&slirp->tx_event);
    events[NET_EVENT_RX]   = slirp->sock_event;
    bool run               = true;
    while (run) {
        uint32_t timeout = -1;
        slirp_pollfds_fill(slirp->slirp, &timeout, net_slirp_add_poll, slirp);
        if (timeout < 0)
            timeout = INFINITE;

        int ret = WaitForMultipleObjects(3, events, FALSE, (DWORD) timeout);
        switch (ret - WAIT_OBJECT_0) {
            case NET_EVENT_STOP:
                run = false;
                break;

            case NET_EVENT_TX:
                {
                    int packets = network_tx_popv(slirp->card, slirp->pkt_tx_v, SLIRP_PKT_BATCH);
                    for (int i = 0; i < packets; i++) {
                        net_slirp_in(slirp, slirp->pkt_tx_v[i].data, slirp->pkt_tx_v[i].len);
                    }
                }
                break;

            default:
                slirp_pollfds_poll(slirp->slirp, ret == WAIT_FAILED, net_slirp_get_revents, slirp);
                break;
        }
    }

    slirp_log("SLiRP: polling stopped.\n");
}
#else
/* Handle the receiving of frames. */
static void
net_slirp_thread(void *priv)
{
    net_slirp_t *slirp = (net_slirp_t *) priv;

    /* Start polling. */
    slirp_log("SLiRP: polling started.\n");

    while (1) {
        uint32_t timeout = -1;

        slirp->pfd_len = 0;
        net_slirp_add_poll(net_event_get_fd(&slirp->stop_event), SLIRP_POLL_IN, slirp);
        net_slirp_add_poll(net_event_get_fd(&slirp->tx_event), SLIRP_POLL_IN, slirp);

        slirp_pollfds_fill(slirp->slirp, &timeout, net_slirp_add_poll, slirp);

        int ret = poll(slirp->pfd, slirp->pfd_len, timeout);

        slirp_pollfds_poll(slirp->slirp, (ret < 0), net_slirp_get_revents, slirp);

        if (slirp->pfd[NET_EVENT_STOP].revents & POLLIN) {
            net_event_clear(&slirp->stop_event);
            break;
        }

        if (slirp->pfd[NET_EVENT_TX].revents & POLLIN) {
            net_event_clear(&slirp->tx_event);

            int packets = network_tx_popv(slirp->card, slirp->pkt_tx_v, SLIRP_PKT_BATCH);
            for (int i = 0; i < packets; i++) {
                net_slirp_in(slirp, slirp->pkt_tx_v[i].data, slirp->pkt_tx_v[i].len);
            }
        }
    }

    slirp_log("SLiRP: polling stopped.\n");
}
#endif

static int slirp_card_num = 2;

/* Initialize SLiRP for use. */
void *
net_slirp_init(const netcard_t *card, const uint8_t *mac_addr, UNUSED(void *priv), char *netdrv_errbuf)
{
    slirp_log("SLiRP: initializing...\n");
    net_slirp_t *slirp = calloc(1, sizeof(net_slirp_t));
    memcpy(slirp->mac_addr, mac_addr, sizeof(slirp->mac_addr));
    slirp->card = (netcard_t *) card;

#ifndef _WIN32
    slirp->pfd_size = 16 * sizeof(struct pollfd);
    slirp->pfd      = malloc(slirp->pfd_size);
    memset(slirp->pfd, 0, slirp->pfd_size);
#endif

    /* Set the IP addresses to use. */
    struct in_addr  net        = { .s_addr = htonl(0x0a000000 | (slirp_card_num << 8)) }; /* 10.0.x.0 */
    struct in_addr  mask       = { .s_addr = htonl(0xffffff00) };                         /* 255.255.255.0 */
    struct in_addr  host       = { .s_addr = htonl(0x0a000002 | (slirp_card_num << 8)) }; /* 10.0.x.2 */
    struct in_addr  dhcp       = { .s_addr = htonl(0x0a00000f | (slirp_card_num << 8)) }; /* 10.0.x.15 */
    struct in_addr  dns        = { .s_addr = htonl(0x0a000003 | (slirp_card_num << 8)) }; /* 10.0.x.3 */
    struct in_addr  bind       = { .s_addr = htonl(0x00000000) };                         /* 0.0.0.0 */
    struct in6_addr ipv6_dummy = { 0 };                                                   /* contents don't matter; we're not using IPv6 */

    /* Initialize SLiRP. */
    slirp->slirp = slirp_init(0, 1, net, mask, host, 0, ipv6_dummy, 0, ipv6_dummy, NULL, NULL, NULL, NULL, dhcp, dns, ipv6_dummy, NULL, NULL, &slirp_cb, slirp);
    if (!slirp->slirp) {
        slirp_log("SLiRP: initialization failed\n");
        snprintf(netdrv_errbuf, NET_DRV_ERRBUF_SIZE, "SLiRP initialization failed");
        free(slirp);
        return NULL;
    }

    /* Set up port forwarding. */
    int  udp;
    int  i = 0;
    int  external;
    int  internal;
    char category[32];
    snprintf(category, sizeof(category), "SLiRP Port Forwarding #%d", card->card_num + 1);
    char key[20];
    while (1) {
        sprintf(key, "%d_protocol", i);
        udp = strcmp(config_get_string(category, key, "tcp"), "udp") == 0;
        sprintf(key, "%d_external", i);
        external = config_get_int(category, key, 0);
        sprintf(key, "%d_internal", i);
        internal = config_get_int(category, key, 0);
        if ((external <= 0) && (internal <= 0))
            break;
        else if (internal <= 0)
            internal = external;
        else if (external <= 0)
            external = internal;

        if (slirp_add_hostfwd(slirp->slirp, udp, bind, external, dhcp, internal) == 0)
            pclog("SLiRP: Forwarded %s port external:%d to internal:%d\n", udp ? "UDP" : "TCP", external, internal);
        else
            pclog("SLiRP: Failed to forward %s port external:%d to internal:%d\n", udp ? "UDP" : "TCP", external, internal);

        i++;
    }

    for (int i = 0; i < SLIRP_PKT_BATCH; i++) {
        slirp->pkt_tx_v[i].data = calloc(1, NET_MAX_FRAME);
    }
    slirp->pkt.data = calloc(1, NET_MAX_FRAME);
    net_event_init(&slirp->tx_event);
    net_event_init(&slirp->stop_event);
#ifdef _WIN32
    slirp->sock_event = CreateEvent(NULL, FALSE, FALSE, NULL);
#endif

    if (!strcmp(network_card_get_internal_name(net_cards_conf[net_card_current].device_num), "modem")) {
        /* Send a gratuitous ARP here to make SLiRP work properly with SLIP connections. */
        struct arphdr_local arphdr;
        /* ARP part. */
        arphdr.ar_hrd      = bswap16(1);
        arphdr.ar_pro      = bswap16(0x0800);
        arphdr.ar_hln      = 6;
        arphdr.ar_pln      = 4;
        arphdr.ar_op       = bswap16(1);
        memcpy(&arphdr.ar_sha, mac_addr, 6);
        memcpy(&arphdr.ar_tha, mac_addr, 6);
        arphdr.ar_sip      = dhcp.s_addr;
        arphdr.ar_tip      = dhcp.s_addr;

        /* Ethernet header part. */
        arphdr.h_proto     = bswap16(0x0806);
        memset(arphdr.h_dest, 0xff, 6);
        memset(arphdr.h_source, 0x52, 6);
        arphdr.h_source[2] = 0x0a;
        arphdr.h_source[3] = 0x00;
        arphdr.h_source[4] = slirp_card_num;
        arphdr.h_source[5] = 2;
        slirp_input(slirp->slirp, (const uint8_t *)&arphdr, sizeof(struct arphdr_local));
    }

    slirp_log("SLiRP: creating thread...\n");
    slirp->poll_tid = thread_create(net_slirp_thread, slirp);

    slirp_card_num++;
    return slirp;
}

void
net_slirp_close(void *priv)
{
    if (!priv)
        return;

    net_slirp_t *slirp = (net_slirp_t *) priv;

    slirp_log("SLiRP: closing\n");
    /* Tell the polling thread to shut down. */
    net_event_set(&slirp->stop_event);

    /* Wait for the thread to finish. */
    slirp_log("SLiRP: waiting for thread to end...\n");
    thread_wait(slirp->poll_tid);

    net_event_close(&slirp->tx_event);
    net_event_close(&slirp->stop_event);
    slirp_cleanup(slirp->slirp);
    for (int i = 0; i < SLIRP_PKT_BATCH; i++) {
        free(slirp->pkt_tx_v[i].data);
    }
    free(slirp->pkt.data);
    free(slirp);
    slirp_card_num--;
}

const netdrv_t net_slirp_drv = {
    &net_slirp_in_available,
    &net_slirp_init,
    &net_slirp_close
};
