 /*
 * 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.
 *
 *          VDE networking for 86box
 *          See https://wiki.virtualsquare.org/#!tutorials/vdebasics.md 
 *          for basic information about VDE. You can browse the source
 *          code at https://github.com/virtualsquare/vde-2
 *
 *          VDE support is only available in Linux and MacOS. It _should_
 *          be available in BSD*, and if someday there is a BSD version of
 *          86box this _could_ work out of the box.
 *
 * Authors: jguillaumes <jguillaumes@gmail.com>
 *          Copyright 2023 jguillaumes.
 * 
 * See the COPYING file at the top of the 86box for license details.
 * TL;DR: GPL version 2.
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>

#if !defined(_WIN32)
#include <poll.h>
#include <unistd.h>
#else
#error VDE is not supported under windows
#endif

#include <libvdeplug.h>

#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/plat.h>
#include <86box/plat_dynld.h>
#include <86box/thread.h>
#include <86box/timer.h>
#include <86box/network.h>
#include <86box/net_event.h>

#define VDE_PKT_BATCH NET_QUEUE_LEN
#define VDE_DESCRIPTION "86Box virtual card"

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

static volatile void *libvde_handle = NULL;

//+
// VDE connection structure
//-
typedef struct net_vde_t {
    void      *vdeconn;             // VDEPLUG Connection
    netcard_t *card;                // NIC linked to
    thread_t  *poll_tid;            // Polling thread
    net_evt_t  tx_event;            // Packets to transmit event
    net_evt_t  stop_event;          // Stop thread event
    netpkt_t   pkt;                 // Packet read/sent
    netpkt_t   pktv[VDE_PKT_BATCH]; // Packet queue
    uint8_t    mac_addr[6];         // MAC Address
} net_vde_t;

//+
// VDE libvdeplug function pointers
//-
static VDECONN *(*f_vde_open)(char *, char *, int, struct vde_open_args *); // This is vde_open_real()
static void (*f_vde_close)(VDECONN *);
static int  (*f_vde_datafd)(VDECONN *);             // Get the data (read/write) handle
static int  (*f_vde_ctlfd)(VDECONN *);              // Get the control handle
static ssize_t (*f_vde_recv)(VDECONN *, void *, size_t, int);           // Receive a packet
static ssize_t (*f_vde_send)(VDECONN *, const void *, size_t, int);     // Send a packet

//+
// VDE libvdeplug function table (for import)
//-
static dllimp_t vde_imports[] = {
    {"vde_open_real",   &f_vde_open},
    {"vde_close",       &f_vde_close},
    {"vde_datafd",      &f_vde_datafd},
    {"vde_ctlfd",       &f_vde_ctlfd},
    {"vde_recv",        &f_vde_recv},
    {"vde_send",        &f_vde_send},
    { NULL,             NULL}
};

#ifdef ENABLE_VDE_LOG
#include <stdarg.h>
int vde_do_log = ENABLE_VDE_LOG;

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

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

#ifdef _WIN32
#error VDE networking is not supported under windows
#else 

//+
// VDE thread
//-
static void net_vde_thread(void *priv) {
    net_vde_t *vde = (net_vde_t *) priv;
    vde_log("VDE: Polling started.\n");

    struct pollfd pfd[NET_EVENT_MAX];
    pfd[NET_EVENT_STOP].fd     = net_event_get_fd(&vde->stop_event);
    pfd[NET_EVENT_STOP].events = POLLIN | POLLPRI;

    pfd[NET_EVENT_TX].fd     = net_event_get_fd(&vde->tx_event);
    pfd[NET_EVENT_TX].events = POLLIN | POLLPRI;

    pfd[NET_EVENT_RX].fd     = f_vde_datafd(vde->vdeconn);
    pfd[NET_EVENT_RX].events = POLLIN;

    pfd[NET_EVENT_VDE].fd     = f_vde_ctlfd(vde->vdeconn);
    pfd[NET_EVENT_VDE].events = POLLIN;

    while(1) {
        poll(pfd, NET_EVENT_MAX, -1);

        // Acvity in the control handle means the link is closed
        // We send ourselves a STOP event
        if (pfd[NET_EVENT_VDE].revents & POLLIN) {
            net_event_set(&vde->stop_event);  
        }

        // There are packets queued to transmit
        if (pfd[NET_EVENT_TX].revents & POLLIN) {
            net_event_clear(&vde->tx_event);
            int packets = network_tx_popv(vde->card, vde->pktv, VDE_PKT_BATCH);
            if (!(net_cards_conf[vde->card->card_num].link_state & NET_LINK_DOWN)) {
                for (int i=0; i<packets; i++) {
                    int nc = f_vde_send(vde->vdeconn, vde->pktv[i].data,vde->pktv[i].len, 0 );
                    if (nc == 0) {
                        vde_log("VDE: Problem, no bytes sent.\n");
                    }
                }
            }
        }

        // Packets are available for reading. Read packet and queue it
        if (pfd[NET_EVENT_RX].revents & POLLIN) {
            int nc = f_vde_recv(vde->vdeconn, vde->pkt.data, NET_MAX_FRAME, 0);
            vde->pkt.len = nc;
            if (!(net_cards_conf[vde->card->card_num].link_state & NET_LINK_DOWN))
                network_rx_put_pkt(vde->card, &vde->pkt);
        }

        // We have been told to close
        if (pfd[NET_EVENT_STOP].revents & POLLIN) {
            net_event_clear(&vde->stop_event);
            break;
        }
    }
    vde_log("VDE: Polling stopped.\n");
}
#endif


//+
// Prepare the VDE libvdeplug interface.
// Load the dynamic library libvdeplug.
// Returns zero if the library has been loaded, -1 in case of error.
//-
int net_vde_prepare(void) {

#if defined(_WIN32)
    #error VDE is not supported in Windows
#elif defined(__APPLE__)
    libvde_handle = dynld_module("libvdeplug.dylib", vde_imports);
#else
    libvde_handle = dynld_module("libvdeplug.so", vde_imports);
#endif

    if (libvde_handle == NULL) {
        vde_log("VDE: error loading VDEPLUG module\n");
        return -1;
    } else {
        network_devmap.has_vde = 1;
    }
    return 0;
}

//+
// Close a VDE socket connection
//-
void net_vde_close(void *priv) {
    int i;

    if (!priv)  return;

    net_vde_t *vde = (net_vde_t *) priv;
    vde_log("VDE: closing.\n");
    net_event_set(&vde->stop_event);        // Tell the thread to finish
    vde_log("VDE: Waiting for the thread to finish...\n");
    thread_wait(vde->poll_tid);
    vde_log("VDE: Thread finished.\n");

    // Free all the mallocs!
    for(i=0;i<VDE_PKT_BATCH; i++) {
        free(vde->pktv[i].data);
    }
    free(vde->pkt.data);
    f_vde_close(vde->vdeconn);
    net_event_close(&vde->tx_event);
    net_event_close(&vde->stop_event);
    free(vde);
}

//+
// Signal packets are available to be transmitted
//-
void net_vde_in_available(void *priv) {
    net_vde_t *vde = (net_vde_t *) priv;
    net_event_set(&vde->tx_event);
}

//+
// Copy error message to the error buffer
// and log if enabled.
//-
void net_vde_error(char *errbuf, const char *message) {
    strncpy(errbuf, message, NET_DRV_ERRBUF_SIZE);
    vde_log("VDE: %s\n", message);
}

//+
// Initialize VDE for use
// At this point the vdeplug library is already loaded
// card: network card we are attaching
// mac_addr: MAC address we are using
// priv: Name of the VDE contol socket directory
//-
void *net_vde_init(const netcard_t *card, const uint8_t *mac_addr, void *priv, char *netdrv_errbuf) {
    struct vde_open_args vde_args;

    char *socket_name = (char *) priv;

    if (libvde_handle == NULL) {
        net_vde_error(netdrv_errbuf, "net_vde_init without library handle");
        return NULL;
    }

    if ((socket_name[0] == '\0') || !strcmp(socket_name, "none")) {
        net_vde_error(netdrv_errbuf, "No socket name configured");
        return NULL;
    }

    vde_log("VDE: Attaching to virtual switch at %s\n", socket_name);
    
    net_vde_t *vde = calloc(1, sizeof(net_vde_t));
    vde->card = (netcard_t *) card;
    memcpy(vde->mac_addr, mac_addr, sizeof(vde->mac_addr));

    vde_args.group = 0;
    vde_args.port  = 0;
    vde_args.mode  = 0700;  // Allow the switch to connect back to our socket if it is run by the same user

    // We are calling vde_open_real(), not the vde_open() macro...
    if ((vde->vdeconn = f_vde_open(socket_name, VDE_DESCRIPTION, 
                                        LIBVDEPLUG_INTERFACE_VERSION, &vde_args)) == NULL) {
        char buf[NET_DRV_ERRBUF_SIZE];
        snprintf(buf, NET_DRV_ERRBUF_SIZE, "Unable to open socket %s (%s)", socket_name, strerror(errno));
        net_vde_error(netdrv_errbuf, buf);
        free(vde);
        return NULL;
    }
    vde_log("VDE: Socket opened (%s).\n", socket_name);

    for(uint8_t i = 0; i < VDE_PKT_BATCH; i++) {
        vde->pktv[i].data = calloc(1, NET_MAX_FRAME);
    }
    vde->pkt.data = calloc(1,NET_MAX_FRAME);
    net_event_init(&vde->tx_event);
    net_event_init(&vde->stop_event);
    vde->poll_tid = thread_create(net_vde_thread, vde);     // Fire up the read-write thread!

    return vde;
}

//+
// VDE Driver structure
//-
const netdrv_t net_vde_drv =  {
    .notify_in = &net_vde_in_available,
    .init      = &net_vde_init,
    .close     = &net_vde_close,
    .priv      = NULL
};

