joeylib2/tests/gate/iigsSerialPeer.c

154 lines
4.9 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// iigsSerialPeer.c - the host end of the Apple IIgs serial gates. MAME's
// bitbanger socket connects out, so this listens on 127.0.0.1:PORT.
//
// iigsSerialPeer capture PORT FILE
// iigsSerialPeer loss PORT TOTAL LOG
//
// capture (verify-iigs-serial.sh): every byte the IIgs sends, added to FILE
// as it arrives (the gate kills this peer once MAME is done, and reads
// FILE). Waits CAPTURE_ACCEPT_SECONDS for MAME (FILE left empty when it
// never comes), then collects for CAPTURE_SECONDS or until MAME goes away.
// Exit 0.
//
// loss (verify-iigs-serial-loss.sh): once the probe has sent READY, TOTAL
// bytes (byte k = k & 0xFF) in one go; then drains whatever the probe
// sends until MAME goes away. LOG gets a line when the bytes went and one
// with the start of what came back. Exit 0; 1 when MAME never connects
// (within LOSS_ACCEPT_SECONDS).
//
// Bad arguments exit 2.
#define _DEFAULT_SOURCE
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "gateArg.h"
#include "gateNet.h"
#include "gateText.h"
#include "toolBuf.h"
#include "toolFile.h"
#define TOOL_NAME "iigsSerialPeer"
#define ARG_MODE 1
#define ARG_PORT 2
#define ARG_FILE 3
#define ARG_TOTAL 3
#define ARG_LOG 4
#define CAPTURE_ARGS 4
#define LOSS_ARGS 5
#define READ_SECONDS 1.0
#define READ_SIZE 256u
#define CAPTURE_ACCEPT_SECONDS 300.0
#define CAPTURE_SECONDS 280.0
#define LOSS_ACCEPT_SECONDS 600.0
#define READY "READY"
#define BYTE_MASK 0xFFu
#define SHOWN_BYTES 64u
static int capture(uint16_t port, const char *path);
static int loss(uint16_t port, long total, const char *logPath);
static int usage(void);
// Every byte MAME's connection carries, kept in path.
static int capture(uint16_t port, const char *path) {
FILE *out;
uint8_t chunk[READ_SIZE];
double deadline;
int32_t got;
int conn;
conn = gateNetAcceptOne(port, CAPTURE_ACCEPT_SECONDS);
out = fopen(path, "wb");
if (out == NULL) {
toolFail("cannot write %s: %s", path, strerror(errno));
}
deadline = gateNetNow() + CAPTURE_SECONDS;
while (conn >= 0 && gateNetNow() < deadline) {
got = gateNetRecv(conn, chunk, sizeof(chunk), READ_SECONDS);
if (got == GATE_NET_CLOSED) {
break;
}
if (got > 0 && (fwrite(chunk, 1u, (size_t)got, out) != (size_t)got || fflush(out) != 0)) {
toolFail("cannot write %s: %s", path, strerror(errno));
}
}
fclose(out);
return 0;
}
// On READY, the stream; then drain until MAME closes the link.
static int loss(uint16_t port, long total, const char *logPath) {
ToolBufT seen;
ToolBufT pattern;
FILE *log;
uint8_t chunk[READ_SIZE];
bool sent;
int32_t got;
long k;
int conn;
log = fopen(logPath, "w");
if (log == NULL) {
toolFail("cannot write %s: %s", logPath, strerror(errno));
}
conn = gateNetAcceptOne(port, LOSS_ACCEPT_SECONDS);
if (conn < 0) {
toolFail("MAME never connected to port %u", (unsigned)port);
}
memset(&seen, 0, sizeof(seen));
memset(&pattern, 0, sizeof(pattern));
sent = false;
for (;;) {
got = gateNetRecv(conn, chunk, sizeof(chunk), READ_SECONDS);
if (got == GATE_NET_CLOSED) {
break;
}
if (got == GATE_NET_IDLE) {
continue;
}
toolBufAdd(&seen, chunk, (size_t)got);
if (!sent && gateTextFind(seen.data, seen.len, 0u, READY, strlen(READY)) != GATE_TEXT_NOT_FOUND) {
for (k = 0; k < total; k++) {
toolBufByte(&pattern, (uint32_t)k & BYTE_MASK);
}
if (!gateNetSendAll(conn, pattern.data, pattern.len)) {
toolFail("send: %s", strerror(errno));
}
sent = true;
fprintf(log, "peer: READY seen, sent %ld bytes\n", total);
fflush(log);
}
}
fprintf(log, "peer: received ");
gateNetShowBytes(log, seen.data, seen.len < SHOWN_BYTES ? seen.len : SHOWN_BYTES);
fprintf(log, "\n");
fclose(log);
return 0;
}
static int usage(void) {
fprintf(stderr, "usage: %s capture PORT FILE\n %s loss PORT TOTAL LOG\n", TOOL_NAME, TOOL_NAME);
return GATE_USAGE_EXIT;
}
int main(int argc, char **argv) {
gateNetStart(TOOL_NAME);
if (argc == CAPTURE_ARGS && strcmp(argv[ARG_MODE], "capture") == 0) {
return capture(gateArgPort(argv[ARG_PORT]), argv[ARG_FILE]);
}
if (argc == LOSS_ARGS && strcmp(argv[ARG_MODE], "loss") == 0) {
return loss(gateArgPort(argv[ARG_PORT]), gateArgLong("total", argv[ARG_TOTAL]), argv[ARG_LOG]);
}
return usage();
}