// 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 #include #include #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(); }