1084 lines
41 KiB
C
1084 lines
41 KiB
C
// 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 "calogPlatform.h"
|
|
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <enet/enet.h>
|
|
|
|
#include <openssl/bio.h>
|
|
#include <openssl/ssl.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
|
|
|
|
// Upper bound (ms) on a TLS server handshake, so a client that opens the socket but never sends a
|
|
// ClientHello cannot pin the accepting thread forever.
|
|
#define NET_TLS_HANDSHAKE_MS 10000
|
|
|
|
typedef struct NetSocketT {
|
|
CalogSocketT fd;
|
|
SSL *ssl; // non-NULL: a TLS connection (accepted from a TLS listener)
|
|
SSL_CTX *tlsCtx; // non-NULL: a TLS listener, owning the server context
|
|
} 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, CalogSocketT *fdOut);
|
|
static bool netPortOk(int64_t port);
|
|
static int netResolve(const char *host, uint16_t port, int socktype, bool passive, struct addrinfo **out);
|
|
static const CalogValueT *netOptField(const CalogValueT *map, const char *name);
|
|
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, CalogSocketT fd, uint32_t type, CalogValueT *result);
|
|
static bool netTlsAccept(SSL *ssl, CalogSocketT fd, int timeoutMs);
|
|
static SSL_CTX *netTlsServerContext(const CalogValueT *opts, const char **errOut);
|
|
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;
|
|
|
|
// A TLS listener writes with SSL_write, whose OpenSSL socket BIO issues a bare write() with no
|
|
// MSG_NOSIGNAL; on Linux (where SO_NOSIGPIPE does not exist) a peer reset would raise SIGPIPE and
|
|
// kill the process. Ignore it process-wide (idempotent; the plain send() path uses MSG_NOSIGNAL).
|
|
#ifndef _WIN32
|
|
signal(SIGPIPE, SIG_IGN);
|
|
#endif
|
|
pthread_mutex_lock(&gNetLibMutex);
|
|
if (gNetLib == NULL) {
|
|
NetLibT *newLib;
|
|
// Windows requires WSAStartup before any socket use (no-op on POSIX).
|
|
if (calogPlatformNetInit() != 0) {
|
|
pthread_mutex_unlock(&gNetLibMutex);
|
|
return calogErrOomE;
|
|
}
|
|
newLib = (NetLibT *)calloc(1, sizeof(*newLib));
|
|
if (newLib == NULL) {
|
|
calogPlatformNetShutdown();
|
|
pthread_mutex_unlock(&gNetLibMutex);
|
|
return calogErrOomE;
|
|
}
|
|
newLib->handles = calogHandleTableCreate();
|
|
if (newLib->handles == NULL) {
|
|
free(newLib);
|
|
calogPlatformNetShutdown();
|
|
pthread_mutex_unlock(&gNetLibMutex);
|
|
return calogErrOomE;
|
|
}
|
|
if (enet_initialize() != 0) {
|
|
calogHandleTableDestroy(newLib->handles, NULL);
|
|
free(newLib);
|
|
calogPlatformNetShutdown();
|
|
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 calogAtDestroy(calog, calogNetShutdown, calogDestroyAfterContextsE);
|
|
}
|
|
|
|
|
|
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();
|
|
calogPlatformNetShutdown();
|
|
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, CalogSocketT *fdOut) {
|
|
struct addrinfo *res;
|
|
struct addrinfo *rp;
|
|
CalogSocketT fd;
|
|
int rc;
|
|
int yes;
|
|
|
|
*fdOut = CALOG_INVALID_SOCKET;
|
|
yes = 1;
|
|
rc = netResolve(NULL, port, socktype, true, &res);
|
|
if (rc != 0) {
|
|
return calogFail(result, calogErrArgE, gai_strerror(rc));
|
|
}
|
|
fd = CALOG_INVALID_SOCKET;
|
|
for (rp = res; rp != NULL; rp = rp->ai_next) {
|
|
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
if (fd == CALOG_INVALID_SOCKET) {
|
|
continue;
|
|
}
|
|
#if defined(_WIN32)
|
|
// On Windows SO_REUSEADDR lets an unrelated process bind (and hijack) a port already in
|
|
// use; SO_EXCLUSIVEADDRUSE is the correct hardening for a server listener. A client or UDP
|
|
// bind (doListen == false) needs neither, so it is left at the default.
|
|
if (doListen) {
|
|
setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char *)&yes, sizeof(yes));
|
|
}
|
|
#else
|
|
// POSIX SO_REUSEADDR only relaxes the TIME_WAIT restriction (fast listener restart) and
|
|
// carries no hijack hazard, so it is applied to every bind.
|
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));
|
|
#endif
|
|
if (bind(fd, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0) {
|
|
break;
|
|
}
|
|
calogSockClose(fd);
|
|
fd = CALOG_INVALID_SOCKET;
|
|
}
|
|
freeaddrinfo(res);
|
|
if (fd == CALOG_INVALID_SOCKET) {
|
|
return calogFail(result, calogErrArgE, "could not bind the requested port");
|
|
}
|
|
if (doListen && listen(fd, SOMAXCONN) != 0) {
|
|
int32_t status;
|
|
status = calogFail(result, calogErrArgE, calogSockErrStr());
|
|
calogSockClose(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;
|
|
}
|
|
|
|
|
|
// Drive the TLS server handshake to completion under a TOTAL wall-clock deadline (timeoutMs), on a
|
|
// non-blocking socket gated by calogPoll. This bounds every stall vector -- a silent client, a
|
|
// slow-drip client that dribbles bytes to keep resetting a per-read timeout, AND a client that stalls
|
|
// the server's own writes (a ServerHello/Certificate that never drains) -- none of which a per-recv
|
|
// SO_RCVTIMEO would catch. Restores blocking mode on success so tcpRecv/tcpSend behave normally.
|
|
// Returns false on timeout or a hard handshake error (the caller then closes the socket).
|
|
static bool netTlsAccept(SSL *ssl, CalogSocketT fd, int timeoutMs) {
|
|
int64_t deadline;
|
|
|
|
deadline = (int64_t)calogMonotonicMillis() + timeoutMs;
|
|
calogSockSetNonblock(fd, 1);
|
|
for (;;) {
|
|
struct pollfd pfd;
|
|
int64_t remaining;
|
|
int rc;
|
|
int err;
|
|
rc = SSL_accept(ssl);
|
|
if (rc == 1) {
|
|
calogSockSetNonblock(fd, 0);
|
|
return true;
|
|
}
|
|
err = SSL_get_error(ssl, rc);
|
|
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
|
|
return false;
|
|
}
|
|
remaining = deadline - (int64_t)calogMonotonicMillis();
|
|
if (remaining <= 0) {
|
|
return false;
|
|
}
|
|
pfd.fd = fd;
|
|
pfd.events = (short)((err == SSL_ERROR_WANT_WRITE) ? POLLOUT : POLLIN);
|
|
pfd.revents = 0;
|
|
if (calogPoll(&pfd, 1, (int)(remaining > INT32_MAX ? INT32_MAX : remaining)) <= 0) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Close the fd and release the NetSocketT. Shared by netSocketClose and the handle-table
|
|
// teardown path (netCloser).
|
|
static void netSocketFree(NetSocketT *sock) {
|
|
// The fd has a single owner: the socket BIO is set BIO_NOCLOSE in tcpAccept, so SSL_free never
|
|
// touches it and calogSockClose below is the one and only close. We deliberately do NOT call
|
|
// SSL_shutdown here -- writing the close_notify alert can block on an unresponsive peer with a
|
|
// full send buffer, stalling teardown; a truncating (dirty) close is acceptable for a server.
|
|
if (sock->ssl != NULL) {
|
|
SSL_free(sock->ssl);
|
|
}
|
|
if (sock->tlsCtx != NULL) {
|
|
SSL_CTX_free(sock->tlsCtx);
|
|
}
|
|
calogSockClose(sock->fd);
|
|
free(sock);
|
|
}
|
|
|
|
|
|
// Look up a string-keyed field in an opts map, or NULL (option names are ASCII).
|
|
static const CalogValueT *netOptField(const CalogValueT *map, const char *name) {
|
|
CalogValueT key;
|
|
CalogValueT *field;
|
|
|
|
if (calogValueString(&key, name, (int64_t)strlen(name)) != calogOkE) {
|
|
return NULL;
|
|
}
|
|
field = calogAggGet(map->as.agg, &key);
|
|
calogValueFree(&key);
|
|
return field;
|
|
}
|
|
|
|
|
|
// Build a server-side SSL_CTX from opts { cert = "chain.pem", key = "key.pem" }. On failure returns
|
|
// NULL and points *errOut at a static message.
|
|
static SSL_CTX *netTlsServerContext(const CalogValueT *opts, const char **errOut) {
|
|
SSL_CTX *ctx;
|
|
const CalogValueT *cert;
|
|
const CalogValueT *key;
|
|
|
|
cert = netOptField(opts, "cert");
|
|
key = netOptField(opts, "key");
|
|
if (cert == NULL || cert->type != calogStringE || key == NULL || key->type != calogStringE) {
|
|
*errOut = "tcpListen: tls requires cert and key file paths";
|
|
return NULL;
|
|
}
|
|
ctx = SSL_CTX_new(TLS_server_method());
|
|
if (ctx == NULL) {
|
|
*errOut = "tcpListen: could not create the TLS context";
|
|
return NULL;
|
|
}
|
|
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
|
if (SSL_CTX_use_certificate_chain_file(ctx, cert->as.s.bytes) != 1) {
|
|
SSL_CTX_free(ctx);
|
|
*errOut = "tcpListen: could not load the TLS certificate";
|
|
return NULL;
|
|
}
|
|
if (SSL_CTX_use_PrivateKey_file(ctx, key->as.s.bytes, SSL_FILETYPE_PEM) != 1) {
|
|
SSL_CTX_free(ctx);
|
|
*errOut = "tcpListen: could not load the TLS private key";
|
|
return NULL;
|
|
}
|
|
if (SSL_CTX_check_private_key(ctx) != 1) {
|
|
SSL_CTX_free(ctx);
|
|
*errOut = "tcpListen: certificate and key do not match";
|
|
return NULL;
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
|
|
// 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, CalogSocketT fd, uint32_t type, CalogValueT *result) {
|
|
NetSocketT *sock;
|
|
int64_t handle;
|
|
|
|
sock = (NetSocketT *)malloc(sizeof(*sock));
|
|
if (sock == NULL) {
|
|
calogSockClose(fd);
|
|
return calogFail(result, calogErrOomE, "out of memory");
|
|
}
|
|
sock->fd = fd;
|
|
sock->ssl = NULL;
|
|
sock->tlsCtx = NULL;
|
|
calogSockNoSigpipe(fd); // macOS: no MSG_NOSIGNAL, so guard broken-pipe writes per-socket
|
|
handle = calogHandleAdd(lib->handles, type, sock);
|
|
if (handle == 0) {
|
|
calogSockClose(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;
|
|
CalogSocketT fd;
|
|
|
|
lib = (NetLibT *)userData;
|
|
calogValueNil(result);
|
|
if (argCount < 1 || argCount > 2 || args[0].type != calogIntE || (argCount == 2 && args[1].type != calogIntE)) {
|
|
return calogFail(result, calogErrArgE, "tcpAccept expects (listenerHandle [, timeoutMs])");
|
|
}
|
|
listener = (NetSocketT *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_TCP_LISTEN);
|
|
if (listener == NULL) {
|
|
return calogFail(result, calogErrArgE, "tcpAccept: invalid listener handle");
|
|
}
|
|
// Optional timeout: poll-gate the accept so a script's accept loop wakes periodically to re-check
|
|
// its own stop condition (a blocking accept would pin the context thread until a connection lands).
|
|
if (argCount == 2) {
|
|
struct pollfd pfd;
|
|
int timeout;
|
|
int ready;
|
|
timeout = args[1].as.i < 0 ? 0 : (args[1].as.i > INT32_MAX ? INT32_MAX : (int)args[1].as.i);
|
|
pfd.fd = listener->fd;
|
|
pfd.events = POLLIN;
|
|
pfd.revents = 0;
|
|
ready = calogPoll(&pfd, 1, timeout);
|
|
if (ready <= 0) {
|
|
return calogOkE; // timeout (nil result) -- the script loop decides whether to keep going
|
|
}
|
|
}
|
|
fd = accept(listener->fd, NULL, NULL);
|
|
if (fd == CALOG_INVALID_SOCKET) {
|
|
return calogFail(result, calogErrArgE, calogSockErrStr());
|
|
}
|
|
if (listener->tlsCtx == NULL) {
|
|
return netStore(lib, fd, NET_TYPE_TCP, result);
|
|
}
|
|
// TLS listener: complete the server handshake, then attach the session to the stored connection.
|
|
// The socket BIO is BIO_NOCLOSE so the NetSocketT stays the single fd owner (see netSocketFree).
|
|
{
|
|
SSL *ssl;
|
|
NetSocketT *sock;
|
|
int32_t status;
|
|
ssl = SSL_new(listener->tlsCtx);
|
|
if (ssl == NULL) {
|
|
calogSockClose(fd);
|
|
return calogFail(result, calogErrOomE, "tcpAccept: could not create the TLS session");
|
|
}
|
|
SSL_set_fd(ssl, (int)fd);
|
|
(void)BIO_set_close(SSL_get_rbio(ssl), BIO_NOCLOSE);
|
|
// Bound the handshake by a TOTAL deadline on a non-blocking socket, so a stalled/slow/malicious
|
|
// client cannot pin this accepting thread (see netTlsAccept).
|
|
if (!netTlsAccept(ssl, fd, NET_TLS_HANDSHAKE_MS)) {
|
|
SSL_free(ssl);
|
|
calogSockClose(fd);
|
|
return calogFail(result, calogErrArgE, "tcpAccept: TLS handshake failed or timed out");
|
|
}
|
|
status = netStore(lib, fd, NET_TYPE_TCP, result);
|
|
if (status != calogOkE) {
|
|
SSL_free(ssl); // netStore closed fd; the NOCLOSE BIO leaves it untouched
|
|
return status;
|
|
}
|
|
sock = (NetSocketT *)calogHandleGet(lib->handles, result->as.i, NET_TYPE_TCP);
|
|
sock->ssl = ssl;
|
|
return calogOkE;
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
CalogSocketT 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 = CALOG_INVALID_SOCKET;
|
|
for (rp = res; rp != NULL; rp = rp->ai_next) {
|
|
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
if (fd == CALOG_INVALID_SOCKET) {
|
|
continue;
|
|
}
|
|
if (connect(fd, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0) {
|
|
break;
|
|
}
|
|
calogSockClose(fd);
|
|
fd = CALOG_INVALID_SOCKET;
|
|
}
|
|
freeaddrinfo(res);
|
|
if (fd == CALOG_INVALID_SOCKET) {
|
|
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;
|
|
const CalogValueT *opts;
|
|
SSL_CTX *ctx;
|
|
CalogSocketT fd;
|
|
int32_t status;
|
|
|
|
lib = (NetLibT *)userData;
|
|
calogValueNil(result);
|
|
if (argCount < 1 || argCount > 2 || args[0].type != calogIntE) {
|
|
return calogFail(result, calogErrArgE, "tcpListen expects (port [, opts])");
|
|
}
|
|
if (argCount == 2 && (args[1].type != calogAggE || !calogAggIsKeyed(args[1].as.agg))) {
|
|
return calogFail(result, calogErrArgE, "tcpListen: opts must be a map");
|
|
}
|
|
if (!netPortOk(args[0].as.i)) {
|
|
return calogFail(result, calogErrArgE, "tcpListen: port out of range");
|
|
}
|
|
// TLS is opt-in: { tls = true, cert = "...pem", key = "...pem" } builds a server SSL_CTX that the
|
|
// listener owns; tcpAccept then wraps each accepted connection in a TLS session.
|
|
opts = argCount == 2 ? &args[1] : NULL;
|
|
ctx = NULL;
|
|
if (opts != NULL) {
|
|
const CalogValueT *tls;
|
|
const CalogValueT *cert;
|
|
tls = netOptField(opts, "tls");
|
|
cert = netOptField(opts, "cert");
|
|
if ((tls != NULL && tls->type == calogBoolE && tls->as.b) || cert != NULL) {
|
|
const char *err;
|
|
err = NULL;
|
|
ctx = netTlsServerContext(opts, &err);
|
|
if (ctx == NULL) {
|
|
return calogFail(result, calogErrArgE, err != NULL ? err : "tcpListen: TLS setup failed");
|
|
}
|
|
}
|
|
}
|
|
status = netOpenBound((uint16_t)args[0].as.i, SOCK_STREAM, true, result, &fd);
|
|
if (status != calogOkE) {
|
|
if (ctx != NULL) {
|
|
SSL_CTX_free(ctx);
|
|
}
|
|
return status;
|
|
}
|
|
status = netStore(lib, fd, NET_TYPE_TCP_LISTEN, result);
|
|
if (status != calogOkE) {
|
|
if (ctx != NULL) {
|
|
SSL_CTX_free(ctx); // netStore already closed fd
|
|
}
|
|
return status;
|
|
}
|
|
if (ctx != NULL) {
|
|
NetSocketT *listener;
|
|
listener = (NetSocketT *)calogHandleGet(lib->handles, result->as.i, NET_TYPE_TCP_LISTEN);
|
|
listener->tlsCtx = ctx;
|
|
}
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
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");
|
|
}
|
|
if (sock->ssl != NULL) {
|
|
int got;
|
|
got = SSL_read(sock->ssl, buffer, (int)args[1].as.i);
|
|
received = got > 0 ? (ssize_t)got : (got == 0 ? 0 : -1); // 0 = clean TLS shutdown -> EOF
|
|
} else {
|
|
received = recv(sock->fd, buffer, (size_t)args[1].as.i, 0);
|
|
}
|
|
if (received < 0) {
|
|
status = calogFail(result, calogErrArgE, calogSockErrStr());
|
|
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;
|
|
if (sock->ssl != NULL) {
|
|
int chunk;
|
|
int wrote;
|
|
chunk = (args[1].as.s.length - total) > INT32_MAX ? INT32_MAX : (int)(args[1].as.s.length - total);
|
|
wrote = SSL_write(sock->ssl, args[1].as.s.bytes + total, chunk);
|
|
sent = wrote > 0 ? (ssize_t)wrote : -1;
|
|
} else {
|
|
sent = send(sock->fd, args[1].as.s.bytes + total, (size_t)(args[1].as.s.length - total), CALOG_MSG_NOSIGNAL);
|
|
}
|
|
if (sent < 0) {
|
|
return calogFail(result, calogErrArgE, calogSockErrStr());
|
|
}
|
|
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;
|
|
CalogSocketT 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, calogSockErrStr());
|
|
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, calogSockErrStr());
|
|
}
|
|
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;
|
|
}
|