// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // gateNet.c - sockets, waits, and the clock shared by the gates' serial // and network peers, and received bytes shown printably (gateNet.h). #define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include #include #include #include "gateNet.h" #include "toolFile.h" #define MS_PER_SECOND 1000.0 #define NS_PER_SECOND 1000000000.0 #define WAIT_FOREVER (-1) // Bytes from SHOW_PRINTABLE up to (not including) SHOW_DELETE show as // themselves. #define SHOW_PRINTABLE 0x20u #define SHOW_DELETE 0x7Fu int gateNetAccept(int listener, double seconds, struct sockaddr_in *from) { struct sockaddr_in peer; socklen_t peerLen; int conn; if (!gateNetWait(listener, POLLIN, seconds)) { return -1; } peerLen = sizeof(peer); conn = accept(listener, (struct sockaddr *)&peer, &peerLen); if (conn < 0) { toolFail("accept: %s", strerror(errno)); } if (from != NULL) { *from = peer; } return conn; } int gateNetAcceptOne(uint16_t port, double seconds) { int listener; int conn; listener = gateNetListen(port, GATE_NET_ONE_PEER); conn = gateNetAccept(listener, seconds, NULL); close(listener); return conn; } int gateNetListen(uint16_t port, int backlog) { struct sockaddr_in addr; int fd; int on; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr(GATE_NET_HOST); on = 1; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0 || setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0 || bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0 || listen(fd, backlog) != 0) { toolFail("cannot listen on %s:%u: %s", GATE_NET_HOST, (unsigned)port, strerror(errno)); } return fd; } double gateNetNow(void) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return (double)now.tv_sec + (double)now.tv_nsec / NS_PER_SECOND; } int32_t gateNetRecv(int fd, uint8_t *buf, size_t cap, double seconds) { ssize_t got; if (!gateNetWait(fd, POLLIN, seconds)) { return GATE_NET_IDLE; } got = read(fd, buf, cap); if (got == 0) { errno = 0; } return got > 0 ? (int32_t)got : GATE_NET_CLOSED; } bool gateNetSendAll(int fd, const void *data, size_t len) { const uint8_t *at; ssize_t put; at = (const uint8_t *)data; while (len > 0u) { put = write(fd, at, len); if (put < 0 && errno == EINTR) { continue; } if (put <= 0) { return false; } at += put; len -= (size_t)put; } return true; } void gateNetShowBytes(FILE *out, const void *data, size_t len) { const uint8_t *bytes; size_t i; uint8_t c; bytes = (const uint8_t *)data; fputc('"', out); for (i = 0; i < len; i++) { c = bytes[i]; if (c == '"' || c == '\\') { fprintf(out, "\\%c", c); } else if (c == '\n') { fputs("\\n", out); } else if (c == '\r') { fputs("\\r", out); } else if (c == '\t') { fputs("\\t", out); } else if (c < SHOW_PRINTABLE || c >= SHOW_DELETE) { fprintf(out, "\\x%02x", c); } else { fputc(c, out); } } fputc('"', out); } void gateNetSleep(double seconds) { struct timespec left; if (seconds <= 0.0) { return; } left.tv_sec = (time_t)seconds; left.tv_nsec = (long)((seconds - (double)left.tv_sec) * NS_PER_SECOND); while (nanosleep(&left, &left) != 0 && errno == EINTR) { // A signal cut the sleep short: sleep what is left of it. } } void gateNetStart(const char *toolName) { toolFileName(toolName); setvbuf(stdout, NULL, _IOLBF, 0); signal(SIGPIPE, SIG_IGN); } bool gateNetWait(int fd, short events, double seconds) { struct pollfd watch; double ms; int timeoutMs; int ready; // Whole milliseconds, rounded up: never a shorter wait than asked. ms = ceil(seconds * MS_PER_SECOND); timeoutMs = ms >= (double)INT_MAX ? WAIT_FOREVER : (ms > 0.0 ? (int)ms : 0); watch.fd = fd; watch.events = events; do { watch.revents = 0; ready = poll(&watch, 1, timeoutMs); } while (ready < 0 && errno == EINTR); // An error is "ready": the read or write that follows reports it. return ready != 0; }