// 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 #include #include #include #include #include #include #include #include #include #include #ifdef CALOG_WITH_ENET #include #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