calog/libs/calogNet.c

855 lines
30 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 <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>
#ifdef CALOG_WITH_ENET
#include <enet/enet.h>
#endif
// 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;
static pthread_mutex_t gNetLibMutex = PTHREAD_MUTEX_INITIALIZER;
static NetLibT *gNetLib = NULL;
#ifdef CALOG_WITH_ENET
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);
#endif
static void netCloser(uint32_t type, void *resource);
static int32_t netMapSetInt(CalogAggT *map, const char *key, int64_t value);
static int32_t netMapSetStr(CalogAggT *map, const char *key, const char *bytes, int64_t length);
static int32_t netOpenBound(uint16_t port, int socktype, bool doListen, CalogValueT *result, int *fdOut);
static int netResolve(const char *host, uint16_t port, int socktype, bool passive, struct addrinfo **out);
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);
int32_t calogNetRegister(CalogT *calog) {
pthread_mutex_lock(&gNetLibMutex);
if (gNetLib == NULL) {
NetLibT *lib;
lib = (NetLibT *)calloc(1, sizeof(*lib));
if (lib == NULL) {
pthread_mutex_unlock(&gNetLibMutex);
return calogErrOomE;
}
lib->handles = calogHandleTableCreate();
if (lib->handles == NULL) {
free(lib);
pthread_mutex_unlock(&gNetLibMutex);
return calogErrOomE;
}
#ifdef CALOG_WITH_ENET
if (enet_initialize() != 0) {
calogHandleTableDestroy(lib->handles, NULL);
free(lib);
pthread_mutex_unlock(&gNetLibMutex);
return calogErrOomE;
}
#endif
gNetLib = lib;
}
gNetLib->refCount++;
pthread_mutex_unlock(&gNetLibMutex);
calogRegisterInline(calog, "tcpConnect", tcpConnect, gNetLib);
calogRegisterInline(calog, "tcpListen", tcpListen, gNetLib);
calogRegisterInline(calog, "tcpAccept", tcpAccept, gNetLib);
calogRegisterInline(calog, "tcpSend", tcpSend, gNetLib);
calogRegisterInline(calog, "tcpRecv", tcpRecv, gNetLib);
calogRegisterInline(calog, "tcpClose", tcpClose, gNetLib);
calogRegisterInline(calog, "udpOpen", udpOpen, gNetLib);
calogRegisterInline(calog, "udpSendTo", udpSendTo, gNetLib);
calogRegisterInline(calog, "udpRecvFrom", udpRecvFrom, gNetLib);
calogRegisterInline(calog, "udpClose", udpClose, gNetLib);
#ifdef CALOG_WITH_ENET
calogRegisterInline(calog, "enetHost", enetHost, gNetLib);
calogRegisterInline(calog, "enetConnect", enetConnect, gNetLib);
calogRegisterInline(calog, "enetService", enetService, gNetLib);
calogRegisterInline(calog, "enetSend", enetSend, gNetLib);
calogRegisterInline(calog, "enetDisconnect", enetDisconnect, gNetLib);
calogRegisterInline(calog, "enetClose", enetClose, gNetLib);
#endif
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);
#ifdef CALOG_WITH_ENET
enet_deinitialize();
#endif
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: {
NetSocketT *sock;
sock = (NetSocketT *)resource;
close(sock->fd);
free(sock);
break;
}
#ifdef CALOG_WITH_ENET
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;
#endif
default:
break;
}
}
// Set map[key] = integer. On failure the (freshly built) key is released; the scalar value
// needs none.
static int32_t netMapSetInt(CalogAggT *map, const char *key, int64_t value) {
CalogValueT keyValue;
CalogValueT intValue;
int32_t status;
status = calogValueString(&keyValue, key, (int64_t)strlen(key));
if (status != calogOkE) {
return status;
}
calogValueInt(&intValue, value);
status = calogAggSet(map, &keyValue, &intValue);
if (status != calogOkE) {
calogValueFree(&keyValue);
}
return status;
}
// Set map[key] = binary-safe string. On failure any built values are released.
static int32_t netMapSetStr(CalogAggT *map, const char *key, const char *bytes, int64_t length) {
CalogValueT keyValue;
CalogValueT stringValue;
int32_t status;
status = calogValueString(&keyValue, key, (int64_t)strlen(key));
if (status != calogOkE) {
return status;
}
status = calogValueString(&stringValue, bytes, length);
if (status != calogOkE) {
calogValueFree(&keyValue);
return status;
}
status = calogAggSet(map, &keyValue, &stringValue);
if (status != calogOkE) {
calogValueFree(&keyValue);
calogValueFree(&stringValue);
}
return status;
}
// 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;
}
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);
}
// 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;
NetSocketT *sock;
lib = (NetLibT *)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogIntE) {
return calogFail(result, calogErrArgE, "tcpClose expects (handle)");
}
sock = (NetSocketT *)calogHandleRemove(lib->handles, args[0].as.i, NET_TYPE_TCP);
if (sock == NULL) {
sock = (NetSocketT *)calogHandleRemove(lib->handles, args[0].as.i, NET_TYPE_TCP_LISTEN);
}
if (sock == NULL) {
return calogFail(result, calogErrArgE, "tcpClose: invalid handle");
}
close(sock->fd);
free(sock);
return calogOkE;
}
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 (args[1].as.i < 0 || args[1].as.i > NET_PORT_MAX) {
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 (args[0].as.i < 0 || args[0].as.i > NET_PORT_MAX) {
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;
NetSocketT *sock;
lib = (NetLibT *)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogIntE) {
return calogFail(result, calogErrArgE, "udpClose expects (handle)");
}
sock = (NetSocketT *)calogHandleRemove(lib->handles, args[0].as.i, NET_TYPE_UDP);
if (sock == NULL) {
return calogFail(result, calogErrArgE, "udpClose: invalid handle");
}
close(sock->fd);
free(sock);
return calogOkE;
}
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 (args[0].as.i < 0 || args[0].as.i > NET_PORT_MAX) {
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 = netMapSetStr(map, "data", buffer, (int64_t)received);
free(buffer);
if (status == calogOkE) {
status = netMapSetStr(map, "host", hostBuffer, (int64_t)strlen(hostBuffer));
}
if (status == calogOkE) {
status = netMapSetInt(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 (args[2].as.i < 0 || args[2].as.i > NET_PORT_MAX) {
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;
}
#ifdef CALOG_WITH_ENET
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 (args[2].as.i < 0 || args[2].as.i > NET_PORT_MAX) {
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 (args[0].as.i < 0 || args[0].as.i > NET_PORT_MAX) {
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 > 255) {
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)");
}
if (args[1].as.i < 0) {
return calogFail(result, calogErrArgE, "enetService: timeout must be non-negative");
}
host = (ENetHost *)calogHandleGet(lib->handles, args[0].as.i, NET_TYPE_ENET_HOST);
if (host == NULL) {
return calogFail(result, calogErrArgE, "enetService: invalid host handle");
}
serviced = enet_host_service(host, &event, (enet_uint32)args[1].as.i);
if (serviced < 0) {
return calogFail(result, calogErrArgE, "enetService: service failed");
}
status = calogAggCreate(&map, calogMapE);
if (status != calogOkE) {
return calogFail(result, status, "enetService: out of memory");
}
if (serviced == 0 || event.type == ENET_EVENT_TYPE_NONE) {
status = netMapSetStr(map, "type", "none", 4);
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 = netMapSetStr(map, "type", "connect", 7);
break;
case ENET_EVENT_TYPE_RECEIVE:
status = netMapSetStr(map, "type", "receive", 7);
if (status == calogOkE) {
status = netMapSetInt(map, "channel", (int64_t)event.channelID);
}
if (status == calogOkE) {
status = netMapSetStr(map, "data", (const char *)event.packet->data, (int64_t)event.packet->dataLength);
}
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
status = netMapSetStr(map, "type", "disconnect", 10);
// 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:
status = netMapSetStr(map, "type", "none", 4);
break;
}
if (status == calogOkE) {
status = netMapSetInt(map, "peer", peerHandle);
}
if (status != calogOkE) {
calogAggFree(map);
return calogFail(result, status, "enetService: failed to build the event");
}
calogValueAgg(result, map);
return calogOkE;
}
#endif