joeylib2/tests/gate/common/gateNet.c

189 lines
4.8 KiB
C

// 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 <arpa/inet.h>
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <poll.h>
#include <signal.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#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;
}