// calogNet.c -- calog network library (see calogNet.h). TCP + UDP for v1 over the shared
// typed handle table; every blocking call is an inline native, so it stalls only the
// calling script's context thread.

#define _GNU_SOURCE

#include "calogNet.h"

#include "calogHandle.h"
#include "calogInternal.h"

#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#include <enet/enet.h>

// Handle type tags, distinct across the whole registry so a stray handle of the wrong kind
// fails to resolve (e.g. a listener passed to tcpSend).
#define NET_TYPE_TCP        1u
#define NET_TYPE_TCP_LISTEN 2u
#define NET_TYPE_UDP        3u
#define NET_TYPE_ENET_HOST  4u
#define NET_TYPE_ENET_PEER  5u

// Upper bound on a single recv/recvFrom allocation, so a script cannot request an arbitrary
// buffer size.
#define NET_MAX_RECV (64 * 1024 * 1024)

#define NET_PORT_MAX 65535

typedef struct NetSocketT {
    int fd;
} NetSocketT;

// Process-wide network library state shared by every runtime that registers the natives.
typedef struct NetLibT {
    CalogHandleTableT *handles;
    int32_t            refCount;
} NetLibT;

// One row of the registration table below: native name paired with its implementation.
typedef struct NetNativeT {
    const char    *name;
    CalogNativeFnT fn;
} NetNativeT;

static pthread_mutex_t gNetLibMutex = PTHREAD_MUTEX_INITIALIZER;
static NetLibT        *gNetLib      = NULL;

static int32_t enetClose(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t enetConnect(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t enetDisconnect(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t enetHost(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t enetSend(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t enetService(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void    netCloser(uint32_t type, void *resource);
static int32_t netOpenBound(uint16_t port, int socktype, bool doListen, CalogValueT *result, int *fdOut);
static bool    netPortOk(int64_t port);
static int     netResolve(const char *host, uint16_t port, int socktype, bool passive, struct addrinfo **out);
static int32_t netSocketClose(NetLibT *lib, int64_t handleId, uint32_t type1, uint32_t type2, CalogValueT *result, const char *message);
static void    netSocketFree(NetSocketT *sock);
static int32_t netStore(NetLibT *lib, int fd, uint32_t type, CalogValueT *result);
static int32_t tcpAccept(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t tcpClose(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t tcpConnect(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t tcpListen(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t tcpRecv(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t tcpSend(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t udpClose(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t udpOpen(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t udpRecvFrom(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t udpSendTo(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);

// Table-driven registration so a failed calogRegisterInline call can be detected (and the
// whole batch unwound) instead of the return value being silently discarded per call.
static const NetNativeT gNetNatives[] = {
    { "tcpConnect",     tcpConnect     },
    { "tcpListen",      tcpListen      },
    { "tcpAccept",      tcpAccept      },
    { "tcpSend",        tcpSend        },
    { "tcpRecv",        tcpRecv        },
    { "tcpClose",       tcpClose       },
    { "udpOpen",        udpOpen        },
    { "udpSendTo",      udpSendTo      },
    { "udpRecvFrom",    udpRecvFrom    },
    { "udpClose",       udpClose       },
    { "enetHost",       enetHost       },
    { "enetConnect",    enetConnect    },
    { "enetService",    enetService    },
    { "enetSend",       enetSend       },
    { "enetDisconnect", enetDisconnect },
    { "enetClose",      enetClose      },
};


int32_t calogNetRegister(CalogT *calog) {
    NetLibT *lib;
    int32_t  status;
    size_t   nativeIndex;

    pthread_mutex_lock(&gNetLibMutex);
    if (gNetLib == NULL) {
        NetLibT *newLib;
        newLib = (NetLibT *)calloc(1, sizeof(*newLib));
        if (newLib == NULL) {
            pthread_mutex_unlock(&gNetLibMutex);
            return calogErrOomE;
        }
        newLib->handles = calogHandleTableCreate();
        if (newLib->handles == NULL) {
            free(newLib);
            pthread_mutex_unlock(&gNetLibMutex);
            return calogErrOomE;
        }
        if (enet_initialize() != 0) {
            calogHandleTableDestroy(newLib->handles, NULL);
            free(newLib);
            pthread_mutex_unlock(&gNetLibMutex);
            return calogErrOomE;
        }
        gNetLib = newLib;
    }
    gNetLib->refCount++;
    lib = gNetLib;
    pthread_mutex_unlock(&gNetLibMutex);
    status = calogOkE;
    for (nativeIndex = 0; nativeIndex < sizeof(gNetNatives) / sizeof(gNetNatives[0]); nativeIndex++) {
        status = calogRegisterInline(calog, gNetNatives[nativeIndex].name, gNetNatives[nativeIndex].fn, lib);
        if (status != calogOkE) {
            break;
        }
    }
    if (status != calogOkE) {
        // Roll back the refcount bump (and, if we were the sole holder, the whole registry)
        // so a partially-registered runtime does not leave a phantom reference behind.
        calogNetShutdown();
        return status;
    }
    return calogOkE;
}


void calogNetShutdown(void) {
    pthread_mutex_lock(&gNetLibMutex);
    if (gNetLib == NULL) {
        pthread_mutex_unlock(&gNetLibMutex);
        return;
    }
    gNetLib->refCount--;
    if (gNetLib->refCount <= 0) {
        calogHandleTableDestroy(gNetLib->handles, netCloser);
        enet_deinitialize();
        free(gNetLib);
        gNetLib = NULL;
    }
    pthread_mutex_unlock(&gNetLibMutex);
}


static void netCloser(uint32_t type, void *resource) {
    switch (type) {
        case NET_TYPE_TCP:
        case NET_TYPE_TCP_LISTEN:
        case NET_TYPE_UDP:
            netSocketFree((NetSocketT *)resource);
            break;
        case NET_TYPE_ENET_HOST:
            enet_host_destroy((ENetHost *)resource);
            break;
        case NET_TYPE_ENET_PEER:
            // Peers are owned by their host; enet_host_destroy frees them.
            break;
        default:
            break;
    }
}


// Create a socket bound to the given local port (0 = ephemeral), optionally listening.
// Returns calogOkE with *fdOut set, or an error with result populated.
static int32_t netOpenBound(uint16_t port, int socktype, bool doListen, CalogValueT *result, int *fdOut) {
    struct addrinfo *res;
    struct addrinfo *rp;
    int              fd;
    int              rc;
    int              yes;

    *fdOut = -1;
    yes    = 1;
    rc     = netResolve(NULL, port, socktype, true, &res);
    if (rc != 0) {
        return calogFail(result, calogErrArgE, gai_strerror(rc));
    }
    fd = -1;
    for (rp = res; rp != NULL; rp = rp->ai_next) {
        fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (fd < 0) {
            continue;
        }
        setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
        if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
            break;
        }
        close(fd);
        fd = -1;
    }
    freeaddrinfo(res);
    if (fd < 0) {
        return calogFail(result, calogErrArgE, "could not bind the requested port");
    }
    if (doListen && listen(fd, SOMAXCONN) != 0) {
        int32_t status;
        status = calogFail(result, calogErrArgE, strerror(errno));
        close(fd);
        return status;
    }
    *fdOut = fd;
    return calogOkE;
}


// True if port is a valid IPv4/IPv6 port number (0 = ephemeral is allowed by callers that
// permit it; this only checks the range).
static bool netPortOk(int64_t port) {
    return port >= 0 && port <= NET_PORT_MAX;
}


static int netResolve(const char *host, uint16_t port, int socktype, bool passive, struct addrinfo **out) {
    struct addrinfo hints;
    char            portBuffer[8];

    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_INET;
    hints.ai_socktype = socktype;
    if (passive) {
        hints.ai_flags = AI_PASSIVE;
    }
    snprintf(portBuffer, sizeof(portBuffer), "%u", (unsigned int)port);
    return getaddrinfo(host, portBuffer, &hints, out);
}


// Remove and close a socket handle, trying type1 then (if non-zero) type2. Used by tcpClose
// (TCP + TCP_LISTEN) and udpClose (UDP alone).
static int32_t netSocketClose(NetLibT *lib, int64_t handleId, uint32_t type1, uint32_t type2, CalogValueT *result, const char *message) {
    NetSocketT *sock;

    sock = (NetSocketT *)calogHandleRemove(lib->handles, handleId, type1);
    if (sock == NULL && type2 != 0) {
        sock = (NetSocketT *)calogHandleRemove(lib->handles, handleId, type2);
    }
    if (sock == NULL) {
        return calogFail(result, calogErrArgE, message);
    }
    netSocketFree(sock);
    return calogOkE;
}


// Close the fd and release the NetSocketT. Shared by netSocketClose and the handle-table
// teardown path (netCloser).
static void netSocketFree(NetSocketT *sock) {
    close(sock->fd);
    free(sock);
}


// Wrap an open fd in a handle-table entry, transferring ownership. On failure the fd is
// closed. Sets result to the new integer handle on success.
static int32_t netStore(NetLibT *lib, int fd, uint32_t type, CalogValueT *result) {
    NetSocketT *sock;
    int64_t     handle;

    sock = (NetSocketT *)malloc(sizeof(*sock));
    if (sock == NULL) {
        close(fd);
        return calogFail(result, calogErrOomE, "out of memory");
    }
    sock->fd = fd;
    handle   = calogHandleAdd(lib->handles, type, sock);
    if (handle == 0) {
        close(fd);
        free(sock);
        return calogFail(result, calogErrOomE, "out of memory");
    }
    calogValueInt(result, handle);
    return calogOkE;
}


static int32_t tcpAccept(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT    *lib;
    NetSocketT *listener;
    int         fd;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 1 || args[0].type != calogIntE) {
        return calogFail(result, calogErrArgE, "tcpAccept expects (listenerHandle)");
    }
    listener = (NetSocketT *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_TCP_LISTEN);
    if (listener == NULL) {
        return calogFail(result, calogErrArgE, "tcpAccept: invalid listener handle");
    }
    fd = accept(listener->fd, NULL, NULL);
    if (fd < 0) {
        return calogFail(result, calogErrArgE, strerror(errno));
    }
    return netStore(lib, fd, NET_TYPE_TCP, result);
}


static int32_t tcpClose(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT *lib;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 1 || args[0].type != calogIntE) {
        return calogFail(result, calogErrArgE, "tcpClose expects (handle)");
    }
    return netSocketClose(lib, args[0].as.i, NET_TYPE_TCP, NET_TYPE_TCP_LISTEN, result, "tcpClose: invalid handle");
}


static int32_t tcpConnect(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT         *lib;
    struct addrinfo *res;
    struct addrinfo *rp;
    int              fd;
    int              rc;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 2 || args[0].type != calogStringE || args[1].type != calogIntE) {
        return calogFail(result, calogErrArgE, "tcpConnect expects (host, port)");
    }
    if (!netPortOk(args[1].as.i)) {
        return calogFail(result, calogErrArgE, "tcpConnect: port out of range");
    }
    rc = netResolve(args[0].as.s.bytes, (uint16_t)args[1].as.i, SOCK_STREAM, false, &res);
    if (rc != 0) {
        return calogFail(result, calogErrArgE, gai_strerror(rc));
    }
    fd = -1;
    for (rp = res; rp != NULL; rp = rp->ai_next) {
        fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (fd < 0) {
            continue;
        }
        if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
            break;
        }
        close(fd);
        fd = -1;
    }
    freeaddrinfo(res);
    if (fd < 0) {
        return calogFail(result, calogErrArgE, "tcpConnect: could not connect");
    }
    return netStore(lib, fd, NET_TYPE_TCP, result);
}


static int32_t tcpListen(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT *lib;
    int      fd;
    int32_t  status;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 1 || args[0].type != calogIntE) {
        return calogFail(result, calogErrArgE, "tcpListen expects (port)");
    }
    if (!netPortOk(args[0].as.i)) {
        return calogFail(result, calogErrArgE, "tcpListen: port out of range");
    }
    status = netOpenBound((uint16_t)args[0].as.i, SOCK_STREAM, true, result, &fd);
    if (status != calogOkE) {
        return status;
    }
    return netStore(lib, fd, NET_TYPE_TCP_LISTEN, result);
}


static int32_t tcpRecv(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT    *lib;
    NetSocketT *sock;
    char       *buffer;
    ssize_t     received;
    int32_t     status;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 2 || args[0].type != calogIntE || args[1].type != calogIntE) {
        return calogFail(result, calogErrArgE, "tcpRecv expects (handle, maxBytes)");
    }
    if (args[1].as.i < 1 || args[1].as.i > NET_MAX_RECV) {
        return calogFail(result, calogErrArgE, "tcpRecv: maxBytes out of range");
    }
    sock = (NetSocketT *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_TCP);
    if (sock == NULL) {
        return calogFail(result, calogErrArgE, "tcpRecv: invalid handle");
    }
    buffer = (char *)malloc((size_t)args[1].as.i);
    if (buffer == NULL) {
        return calogFail(result, calogErrOomE, "tcpRecv: out of memory");
    }
    received = recv(sock->fd, buffer, (size_t)args[1].as.i, 0);
    if (received < 0) {
        status = calogFail(result, calogErrArgE, strerror(errno));
        free(buffer);
        return status;
    }
    if (received == 0) {
        // Peer closed the connection: nil signals end of stream.
        free(buffer);
        return calogOkE;
    }
    status = calogValueString(result, buffer, (int64_t)received);
    free(buffer);
    return status;
}


static int32_t tcpSend(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT    *lib;
    NetSocketT *sock;
    int64_t     total;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 2 || args[0].type != calogIntE || args[1].type != calogStringE) {
        return calogFail(result, calogErrArgE, "tcpSend expects (handle, data)");
    }
    sock = (NetSocketT *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_TCP);
    if (sock == NULL) {
        return calogFail(result, calogErrArgE, "tcpSend: invalid handle");
    }
    total = 0;
    while (total < args[1].as.s.length) {
        ssize_t sent;
        sent = send(sock->fd, args[1].as.s.bytes + total, (size_t)(args[1].as.s.length - total), MSG_NOSIGNAL);
        if (sent < 0) {
            return calogFail(result, calogErrArgE, strerror(errno));
        }
        total += sent;
    }
    calogValueInt(result, total);
    return calogOkE;
}


static int32_t udpClose(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT *lib;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 1 || args[0].type != calogIntE) {
        return calogFail(result, calogErrArgE, "udpClose expects (handle)");
    }
    return netSocketClose(lib, args[0].as.i, NET_TYPE_UDP, 0, result, "udpClose: invalid handle");
}


static int32_t udpOpen(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT *lib;
    int      fd;
    int32_t  status;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 1 || args[0].type != calogIntE) {
        return calogFail(result, calogErrArgE, "udpOpen expects (port)");
    }
    if (!netPortOk(args[0].as.i)) {
        return calogFail(result, calogErrArgE, "udpOpen: port out of range");
    }
    status = netOpenBound((uint16_t)args[0].as.i, SOCK_DGRAM, false, result, &fd);
    if (status != calogOkE) {
        return status;
    }
    return netStore(lib, fd, NET_TYPE_UDP, result);
}


static int32_t udpRecvFrom(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT           *lib;
    NetSocketT        *sock;
    CalogAggT         *map;
    char              *buffer;
    struct sockaddr_in from;
    socklen_t          fromLength;
    ssize_t            received;
    char               hostBuffer[INET_ADDRSTRLEN];
    int32_t            status;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 2 || args[0].type != calogIntE || args[1].type != calogIntE) {
        return calogFail(result, calogErrArgE, "udpRecvFrom expects (handle, maxBytes)");
    }
    if (args[1].as.i < 1 || args[1].as.i > NET_MAX_RECV) {
        return calogFail(result, calogErrArgE, "udpRecvFrom: maxBytes out of range");
    }
    sock = (NetSocketT *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_UDP);
    if (sock == NULL) {
        return calogFail(result, calogErrArgE, "udpRecvFrom: invalid handle");
    }
    buffer = (char *)malloc((size_t)args[1].as.i);
    if (buffer == NULL) {
        return calogFail(result, calogErrOomE, "udpRecvFrom: out of memory");
    }
    fromLength = sizeof(from);
    received   = recvfrom(sock->fd, buffer, (size_t)args[1].as.i, 0, (struct sockaddr *)&from, &fromLength);
    if (received < 0) {
        status = calogFail(result, calogErrArgE, strerror(errno));
        free(buffer);
        return status;
    }
    if (inet_ntop(AF_INET, &from.sin_addr, hostBuffer, sizeof(hostBuffer)) == NULL) {
        hostBuffer[0] = '\0';
    }
    status = calogAggCreate(&map, calogMapE);
    if (status != calogOkE) {
        free(buffer);
        return calogFail(result, status, "udpRecvFrom: out of memory");
    }
    status = calogMapSetStr(map, "data", buffer, (int64_t)received);
    free(buffer);
    if (status == calogOkE) {
        status = calogMapSetStr(map, "host", hostBuffer, (int64_t)strlen(hostBuffer));
    }
    if (status == calogOkE) {
        status = calogMapSetInt(map, "port", (int64_t)ntohs(from.sin_port));
    }
    if (status != calogOkE) {
        calogAggFree(map);
        return calogFail(result, status, "udpRecvFrom: failed to build the result");
    }
    calogValueAgg(result, map);
    return calogOkE;
}


static int32_t udpSendTo(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT         *lib;
    NetSocketT      *sock;
    struct addrinfo *res;
    ssize_t          sent;
    int              rc;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 4 || args[0].type != calogIntE || args[1].type != calogStringE || args[2].type != calogIntE || args[3].type != calogStringE) {
        return calogFail(result, calogErrArgE, "udpSendTo expects (handle, host, port, data)");
    }
    if (!netPortOk(args[2].as.i)) {
        return calogFail(result, calogErrArgE, "udpSendTo: port out of range");
    }
    sock = (NetSocketT *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_UDP);
    if (sock == NULL) {
        return calogFail(result, calogErrArgE, "udpSendTo: invalid handle");
    }
    rc = netResolve(args[1].as.s.bytes, (uint16_t)args[2].as.i, SOCK_DGRAM, false, &res);
    if (rc != 0) {
        return calogFail(result, calogErrArgE, gai_strerror(rc));
    }
    sent = sendto(sock->fd, args[3].as.s.bytes, (size_t)args[3].as.s.length, 0, res->ai_addr, res->ai_addrlen);
    freeaddrinfo(res);
    if (sent < 0) {
        return calogFail(result, calogErrArgE, strerror(errno));
    }
    calogValueInt(result, (int64_t)sent);
    return calogOkE;
}


static int32_t enetClose(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT  *lib;
    ENetHost *host;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 1 || args[0].type != calogIntE) {
        return calogFail(result, calogErrArgE, "enetClose expects (hostHandle)");
    }
    host = (ENetHost *)calogHandleRemove(lib->handles, args[0].as.i, NET_TYPE_ENET_HOST);
    if (host == NULL) {
        return calogFail(result, calogErrArgE, "enetClose: invalid host handle");
    }
    // enet_host_destroy frees the peer array, so drop every outstanding peer handle for this
    // host first -- otherwise those handles would resolve to freed memory (use-after-free).
    {
        size_t peerIndex;
        for (peerIndex = 0; peerIndex < host->peerCount; peerIndex++) {
            ENetPeer *peer;
            int64_t   peerHandle;
            peer       = &host->peers[peerIndex];
            peerHandle = (int64_t)(intptr_t)peer->data;
            if (peerHandle != 0) {
                calogHandleRemove(lib->handles, peerHandle, NET_TYPE_ENET_PEER);
                peer->data = NULL;
            }
        }
    }
    enet_host_destroy(host);
    return calogOkE;
}


static int32_t enetConnect(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT    *lib;
    ENetHost   *host;
    ENetPeer   *peer;
    ENetAddress address;
    int64_t     handle;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 4 || args[0].type != calogIntE || args[1].type != calogStringE || args[2].type != calogIntE || args[3].type != calogIntE) {
        return calogFail(result, calogErrArgE, "enetConnect expects (hostHandle, host, port, channels)");
    }
    if (!netPortOk(args[2].as.i)) {
        return calogFail(result, calogErrArgE, "enetConnect: port out of range");
    }
    if (args[3].as.i < 1) {
        return calogFail(result, calogErrArgE, "enetConnect: channels must be positive");
    }
    host = (ENetHost *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_ENET_HOST);
    if (host == NULL) {
        return calogFail(result, calogErrArgE, "enetConnect: invalid host handle");
    }
    if (enet_address_set_host(&address, args[1].as.s.bytes) != 0) {
        return calogFail(result, calogErrArgE, "enetConnect: could not resolve host");
    }
    address.port = (enet_uint16)args[2].as.i;
    peer         = enet_host_connect(host, &address, (size_t)args[3].as.i, 0);
    if (peer == NULL) {
        return calogFail(result, calogErrArgE, "enetConnect: no available peer slots");
    }
    handle = calogHandleAdd(lib->handles, NET_TYPE_ENET_PEER, peer);
    if (handle == 0) {
        enet_peer_reset(peer);
        return calogFail(result, calogErrOomE, "enetConnect: out of memory");
    }
    peer->data = (void *)(intptr_t)handle;
    calogValueInt(result, handle);
    return calogOkE;
}


static int32_t enetDisconnect(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT  *lib;
    ENetPeer *peer;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 1 || args[0].type != calogIntE) {
        return calogFail(result, calogErrArgE, "enetDisconnect expects (peerHandle)");
    }
    peer = (ENetPeer *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_ENET_PEER);
    if (peer == NULL) {
        return calogFail(result, calogErrArgE, "enetDisconnect: invalid peer handle");
    }
    // Graceful: the actual removal happens when the disconnect event is serviced.
    enet_peer_disconnect(peer, 0);
    return calogOkE;
}


static int32_t enetHost(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT     *lib;
    ENetHost    *host;
    ENetAddress  address;
    ENetAddress *addressPtr;
    int64_t      handle;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 2 || args[0].type != calogIntE || args[1].type != calogIntE) {
        return calogFail(result, calogErrArgE, "enetHost expects (port, maxPeers)");
    }
    if (!netPortOk(args[0].as.i)) {
        return calogFail(result, calogErrArgE, "enetHost: port out of range");
    }
    if (args[1].as.i < 1) {
        return calogFail(result, calogErrArgE, "enetHost: maxPeers must be positive");
    }
    // port 0 -> a client host (no bind); port > 0 -> a server host bound to that port.
    addressPtr = NULL;
    if (args[0].as.i > 0) {
        address.host = ENET_HOST_ANY;
        address.port = (enet_uint16)args[0].as.i;
        addressPtr   = &address;
    }
    host = enet_host_create(addressPtr, (size_t)args[1].as.i, 0, 0, 0);
    if (host == NULL) {
        return calogFail(result, calogErrArgE, "enetHost: could not create host");
    }
    handle = calogHandleAdd(lib->handles, NET_TYPE_ENET_HOST, host);
    if (handle == 0) {
        enet_host_destroy(host);
        return calogFail(result, calogErrOomE, "enetHost: out of memory");
    }
    calogValueInt(result, handle);
    return calogOkE;
}


static int32_t enetSend(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT    *lib;
    ENetPeer   *peer;
    ENetPacket *packet;
    enet_uint32 flags;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 4 || args[0].type != calogIntE || args[1].type != calogIntE || args[2].type != calogStringE || args[3].type != calogBoolE) {
        return calogFail(result, calogErrArgE, "enetSend expects (peerHandle, channel, data, reliable)");
    }
    if (args[1].as.i < 0 || args[1].as.i > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) {
        return calogFail(result, calogErrArgE, "enetSend: channel out of range");
    }
    peer = (ENetPeer *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_ENET_PEER);
    if (peer == NULL) {
        return calogFail(result, calogErrArgE, "enetSend: invalid peer handle");
    }
    flags = 0;
    if (args[3].as.b) {
        flags = (enet_uint32)ENET_PACKET_FLAG_RELIABLE;
    }
    packet = enet_packet_create(args[2].as.s.bytes, (size_t)args[2].as.s.length, flags);
    if (packet == NULL) {
        return calogFail(result, calogErrOomE, "enetSend: out of memory");
    }
    if (enet_peer_send(peer, (enet_uint8)args[1].as.i, packet) != 0) {
        enet_packet_destroy(packet);
        return calogFail(result, calogErrArgE, "enetSend: could not queue the packet");
    }
    return calogOkE;
}


static int32_t enetService(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    NetLibT   *lib;
    ENetHost  *host;
    ENetEvent  event;
    CalogAggT *map;
    int64_t    peerHandle;
    int32_t    status;
    int        serviced;

    lib = (NetLibT *)userData;
    calogValueNil(result);
    if (argCount != 2 || args[0].type != calogIntE || args[1].type != calogIntE) {
        return calogFail(result, calogErrArgE, "enetService expects (hostHandle, timeoutMs)");
    }
    // enet_host_service takes the timeout as enet_uint32 milliseconds; reject anything that
    // would silently wrap (a value >= 2^32 would otherwise become a near-zero busy-poll).
    if (args[1].as.i < 0 || args[1].as.i > (int64_t)UINT32_MAX) {
        return calogFail(result, calogErrArgE, "enetService: timeout out of range");
    }
    host = (ENetHost *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_ENET_HOST);
    if (host == NULL) {
        return calogFail(result, calogErrArgE, "enetService: invalid host handle");
    }
    // Build the result map before consuming the event: on OOM here there is nothing yet to
    // leak or leave in a stale handle-table state (see the failure paths below, which used to
    // run after the event was already dequeued).
    status = calogAggCreate(&map, calogMapE);
    if (status != calogOkE) {
        return calogFail(result, status, "enetService: out of memory");
    }
    serviced = enet_host_service(host, &event, (enet_uint32)args[1].as.i);
    if (serviced < 0) {
        calogAggFree(map);
        return calogFail(result, calogErrArgE, "enetService: service failed");
    }
    if (serviced == 0 || event.type == ENET_EVENT_TYPE_NONE) {
        status = calogMapSetStr(map, "type", "none", (int64_t)strlen("none"));
        if (status != calogOkE) {
            calogAggFree(map);
            return calogFail(result, status, "enetService: out of memory");
        }
        calogValueAgg(result, map);
        return calogOkE;
    }
    // Every peer carries its stable handle in peer->data (0 = not yet assigned, e.g. a fresh
    // incoming connection on a server host).
    peerHandle = (int64_t)(intptr_t)event.peer->data;
    if (peerHandle == 0) {
        peerHandle = calogHandleAdd(lib->handles, NET_TYPE_ENET_PEER, event.peer);
        if (peerHandle == 0) {
            if (event.type == ENET_EVENT_TYPE_RECEIVE) {
                enet_packet_destroy(event.packet);
            }
            calogAggFree(map);
            return calogFail(result, calogErrOomE, "enetService: out of memory");
        }
        event.peer->data = (void *)(intptr_t)peerHandle;
    }
    switch (event.type) {
        case ENET_EVENT_TYPE_CONNECT:
            status = calogMapSetStr(map, "type", "connect", (int64_t)strlen("connect"));
            break;
        case ENET_EVENT_TYPE_RECEIVE:
            status = calogMapSetStr(map, "type", "receive", (int64_t)strlen("receive"));
            if (status == calogOkE) {
                status = calogMapSetInt(map, "channel", (int64_t)event.channelID);
            }
            if (status == calogOkE) {
                status = calogMapSetStr(map, "data", (const char *)event.packet->data, (int64_t)event.packet->dataLength);
            }
            enet_packet_destroy(event.packet);
            break;
        case ENET_EVENT_TYPE_DISCONNECT:
            status = calogMapSetStr(map, "type", "disconnect", (int64_t)strlen("disconnect"));
            // The peer is now invalid; drop its handle but still report it in this event.
            calogHandleRemove(lib->handles, peerHandle, NET_TYPE_ENET_PEER);
            event.peer->data = NULL;
            break;
        default:
            // Unreachable: ENET_EVENT_TYPE_NONE is handled above and ENetEventType has no
            // other values. Kept only to satisfy -Wswitch; a peer handle was possibly just
            // allocated above for this event, so this is not a safe fallback to "none".
            calogAggFree(map);
            return calogFail(result, calogErrArgE, "enetService: unexpected event type");
    }
    if (status == calogOkE) {
        status = calogMapSetInt(map, "peer", peerHandle);
    }
    if (status != calogOkE) {
        calogAggFree(map);
        return calogFail(result, status, "enetService: failed to build the event");
    }
    calogValueAgg(result, map);
    return calogOkE;
}
