189 lines
4.8 KiB
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;
|
|
}
|