joeylib2/tests/gate/serialLoopPeer.c

735 lines
26 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// serialLoopPeer.c - the host end of the serial loopback gates.
//
// serialLoopPeer tcp-server PORT WALL [BANNER] listen; the emulator connects
// serialLoopPeer tcp-client PORT WALL [BANNER] connect to the emulator
// serialLoopPeer pty PATHFILE WALL [BANNER] make a raw pty pair, write the
// guest end's path to PATHFILE
//
// The guest runs SERTEST (examples/sertest): it sends a banner, then echoes
// every byte it receives. This peer waits for the banner (the guest's
// transmit works), then sends a probe line and expects it back (its receive
// works too). WALL is the wall-clock budget in seconds. Prints one LOOP
// PASS / LOOP FAIL line; exit 0 on pass, 1 on fail, 2 on bad arguments.
//
// --close-hold FILE (pty) then has SERTEST close the port: its DTR check,
// run for the closes alone (a pty has no modem lines), every marker line
// acknowledged, after which the port stays closed. Prints one CLOSE PASS /
// CLOSE FAIL line more, and keeps the pty pair open until FILE exists or
// WALL runs out, so the gate can read the guest end's settings after the
// close (verify-sdl3-serial.sh).
//
// --stream N --rate RATE (TCP, nullmodem transparent:1) then streams N
// bytes of printable ASCII through SERTEST's echo, at most STREAM_WINDOW
// bytes ahead of what has come back, and checks every byte returns once, in
// order. Prints one STREAM PASS / STREAM FAIL line more, with the round
// trip's bytes a second; PASS needs that to be at least STREAM_MIN_PERCENT
// of what the line rate RATE (bits a second) carries.
//
// --dtr-check (TCP, with DOSBox's nullmodem in transparent:0 mode, which
// puts the guest's modem lines in the byte stream as they change: 0xFF 0xFF
// is a data 0xFF, 0xFF <b> the lines, bit 0 RTS, bit 1 DTR) then runs
// SERTEST's DTR check: it sends DTR_CHECK, acknowledges each of SERTEST's
// marker lines (so SERTEST changes a line only once the marker before has
// gone out whole), and reads the line states against the markers.
// jlSerialSetDtr(false) and (true) must reach the line, closing and opening
// the port again must keep DTR up, and after a hang-up (DTR dropped, then
// the port closed) DTR must stay down for HANGUP_WATCH_SECONDS. Prints one
// DTR PASS / DTR FAIL line more.
//
// The options may come anywhere among the arguments. The verdict lines
// begin as shown above (the gates read them); what the guest sent is shown
// in double quotes, escaped where it is not printable.
#define _DEFAULT_SOURCE
#define _XOPEN_SOURCE 600
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <termios.h>
#include <unistd.h>
#include "gateArg.h"
#include "gateNet.h"
#include "gateText.h"
#include "toolBuf.h"
#include "toolFile.h"
#define TOOL_NAME "serialLoopPeer"
// The arguments that are not options: mode, target, wall, and a banner.
#define POS_MODE 0
#define POS_TARGET 1
#define POS_WALL 2
#define POS_BANNER 3
#define POS_MIN 3
#define POS_MAX 4
#define DEFAULT_BANNER "JLSERTEST-READY"
#define PROBE "PING12345"
#define PROBE_LINE PROBE "\r\n"
#define RESEND_SECONDS 3.0
#define POLL_SECONDS 0.5
#define CONNECT_SECONDS 2.0
#define CONNECT_RETRY_SECONDS 0.5
#define READ_SIZE 256u
// How much of the stream a line shows (LOOP FAIL a little more).
#define SHOWN_BYTES 80u
#define FAIL_EXTRA_BYTES 40u
#define FAIL_SHOWN_BYTES (SHOWN_BYTES + FAIL_EXTRA_BYTES)
#define TMP_SUFFIX ".tmp"
#define OPTION_DASHES "--"
// --stream: the window, the chunks, the pass mark, and the bytes it sends
// (printable ASCII, so never SERTEST's DTR_CHECK).
#define STREAM_WINDOW 1024u
#define STREAM_CHUNK 64u
#define STREAM_POLL_SECONDS 0.01
#define STREAM_SETTLE_SECONDS 1.0
#define STREAM_MIN_PERCENT 75
#define STREAM_FIRST_BYTE 0x20u
#define STREAM_BYTE_VALUES 95u
#define STREAM_SHOWN_BYTES 16u
#define PERCENT 100.0
// 8N1: a start bit, 8 data bits, a stop bit.
#define LINE_BITS_PER_BYTE 10
// SERTEST's DTR check (examples/sertest/sertest.c).
#define DTR_CHECK "\x04"
#define DTR_ACK "\x06"
#define HANGUP_WATCH_SECONDS 3.0
#define FAILS_MAX 5u
// DOSBox nullmodem transparent:0 framing.
#define LINE_ESCAPE 0xFFu
#define LINE_DTR 0x02u
// SERTEST's DTR check markers, in the order it sends them.
typedef enum MarkE {
MARK_OFF = 0,
MARK_ON,
MARK_REOPENED,
MARK_HANGUP,
MARK_COUNT
} MarkE;
// DTR as it was at some point: never reported, down, or up.
typedef enum DtrStateE {
DTR_UNKNOWN = 0,
DTR_DOWN,
DTR_UP
} DtrStateE;
// One DTR state the guest reported, with the count of data bytes before it.
typedef struct DtrT {
size_t at;
bool up;
} DtrT;
// A transparent:0 stream split into data and the guest's DTR states (DtrT
// records, in the order they came).
typedef struct LinesT {
bool escaped;
size_t dataCount;
ToolBufT states;
} LinesT;
// The link to the guest: a pty's host end or a TCP connection, how long a
// read waits, and whether the stream is transparent:0 framed (--dtr-check).
typedef struct LinkT {
int fd;
bool isPty;
bool framed;
double pollSeconds;
LinesT lines;
} LinkT;
// Where in the stream each of SERTEST's markers was, the first count of
// them found.
typedef struct MarksT {
size_t at[MARK_COUNT];
uint32_t count;
} MarksT;
static void ackMarks(LinkT *link, const ToolBufT *got, size_t base, MarksT *marks);
static int closeHold(LinkT *link, double wall, double start, ToolBufT *got, const char *holdFile);
static int connectOne(uint16_t port, double wall, double start);
static int connectWithin(uint16_t port, double seconds);
static DtrStateE dtrBefore(const LinesT *lines, size_t position);
static bool dtrChanges(const LinesT *lines, size_t first, size_t last, bool up);
static int dtrCheck(LinkT *link, double wall, double start, ToolBufT *got);
static const DtrT *dtrStates(const LinesT *lines, size_t *count);
static size_t linesFeed(LinesT *lines, uint8_t *data, size_t len);
static void linkSend(LinkT *link, const void *data, size_t len);
static int loop(LinkT *link, double wall, double start, const char *banner, ToolBufT *got);
static void markFail(const char *label, const MarksT *marks, const ToolBufT *got, size_t base);
static int openPty(const char *pathFile);
static void rawMode(int fd);
static bool receive(LinkT *link, ToolBufT *into);
static int stream(LinkT *link, double wall, double start, size_t total, long rate);
static uint8_t streamByte(size_t index);
static int usage(void);
static const char *const kMarks[MARK_COUNT] = {
"JLDTR-OFF\r\n",
"JLDTR-ON\r\n",
"JLDTR-REOPENED\r\n",
"JLDTR-HANGUP\r\n"
};
// Acknowledge each of SERTEST's marker lines (after got[base]) as it
// completes, noting where it is.
static void ackMarks(LinkT *link, const ToolBufT *got, size_t base, MarksT *marks) {
const char *mark;
size_t at;
while (marks->count < MARK_COUNT) {
mark = kMarks[marks->count];
at = gateTextFind(got->data, got->len, base, mark, strlen(mark));
if (at == GATE_TEXT_NOT_FOUND) {
return;
}
marks->at[marks->count] = at;
marks->count++;
linkSend(link, DTR_ACK, strlen(DTR_ACK));
}
}
// After the loopback: SERTEST's DTR check for its closes alone, then the
// pty held open until holdFile exists.
static int closeHold(LinkT *link, double wall, double start, ToolBufT *got, const char *holdFile) {
MarksT marks;
size_t base;
int status;
base = got->len;
marks.count = 0u;
linkSend(link, DTR_CHECK, strlen(DTR_CHECK));
while (marks.count < MARK_COUNT && gateNetNow() - start < wall) {
if (!receive(link, got)) {
break;
}
ackMarks(link, got, base, &marks);
}
if (marks.count == MARK_COUNT) {
printf("CLOSE PASS: every marker acknowledged; SERTEST has closed the port\n");
status = 0;
} else {
markFail("CLOSE", &marks, got, base);
status = 1;
}
while (access(holdFile, F_OK) != 0 && gateNetNow() - start < wall) {
receive(link, NULL);
}
return status;
}
// A connection to the emulator, tried every CONNECT_RETRY_SECONDS until
// wall seconds from start have gone; -1 when none was made.
static int connectOne(uint16_t port, double wall, double start) {
int fd;
while (gateNetNow() - start < wall) {
fd = connectWithin(port, CONNECT_SECONDS);
if (fd >= 0) {
return fd;
}
gateNetSleep(CONNECT_RETRY_SECONDS);
}
return -1;
}
// One connection attempt, given seconds to complete; -1 when it fails.
static int connectWithin(uint16_t port, double seconds) {
struct sockaddr_in addr;
socklen_t errLen;
int fd;
int flags;
int err;
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);
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
return -1;
}
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
err = 0;
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
err = errno;
errLen = sizeof(err);
if (err == EINPROGRESS && gateNetWait(fd, POLLOUT, seconds)) {
getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errLen);
}
}
if (err != 0) {
close(fd);
return -1;
}
fcntl(fd, F_SETFL, flags);
return fd;
}
// DTR as it was when the data byte at position arrived.
static DtrStateE dtrBefore(const LinesT *lines, size_t position) {
const DtrT *states;
size_t count;
size_t i;
DtrStateE state;
states = dtrStates(lines, &count);
state = DTR_UNKNOWN;
for (i = 0; i < count && states[i].at <= position; i++) {
state = states[i].up ? DTR_UP : DTR_DOWN;
}
return state;
}
// Whether DTR was reported as up (or down) after data byte first, up to
// last.
static bool dtrChanges(const LinesT *lines, size_t first, size_t last, bool up) {
const DtrT *states;
size_t count;
size_t i;
states = dtrStates(lines, &count);
for (i = 0; i < count; i++) {
if (first < states[i].at && states[i].at <= last && states[i].up == up) {
return true;
}
}
return false;
}
// After the loopback (the stream so far is got, its line states decoded):
// SERTEST's DTR check, then the watch after its hang-up.
static int dtrCheck(LinkT *link, double wall, double start, ToolBufT *got) {
const LinesT *lines;
const DtrT *states;
const char *fails[FAILS_MAX];
MarksT marks;
size_t base;
size_t count;
size_t i;
uint32_t failCount;
uint32_t f;
double hangupAt;
bool watching;
bool shown;
lines = &link->lines;
base = got->len;
marks.count = 0u;
hangupAt = 0.0;
watching = false;
linkSend(link, DTR_CHECK, strlen(DTR_CHECK));
while (gateNetNow() - start < wall) {
if (watching && gateNetNow() - hangupAt >= HANGUP_WATCH_SECONDS) {
break;
}
if (!receive(link, got)) {
break;
}
// The last marker starts the watch.
ackMarks(link, got, base, &marks);
if (!watching && marks.count == MARK_COUNT) {
watching = true;
hangupAt = gateNetNow();
}
}
if (marks.count < MARK_COUNT) {
markFail("DTR", &marks, got, base);
return 1;
}
if (gateNetNow() - hangupAt < HANGUP_WATCH_SECONDS) {
printf("DTR FAIL: out of time before the watch after the hang-up ended\n");
return 1;
}
failCount = 0u;
if (dtrBefore(lines, marks.at[MARK_OFF]) != DTR_DOWN) {
fails[failCount++] = "jlSerialSetDtr(false) did not drop DTR";
}
if (dtrBefore(lines, marks.at[MARK_ON]) != DTR_UP) {
fails[failCount++] = "jlSerialSetDtr(true) did not raise DTR";
}
if (dtrChanges(lines, marks.at[MARK_ON], marks.at[MARK_REOPENED], false)) {
fails[failCount++] = "closing and opening the port dropped DTR";
}
if (dtrBefore(lines, marks.at[MARK_HANGUP]) != DTR_DOWN) {
fails[failCount++] = "jlSerialSetDtr(false) after the reopen did not drop DTR";
}
if (dtrChanges(lines, marks.at[MARK_HANGUP], lines->dataCount, true)) {
fails[failCount++] = "closing the port after the hang-up raised DTR";
}
if (failCount == 0u) {
printf("DTR PASS: SetDtr(false/true) reached the line, close+open kept DTR up, a hang-up stayed down %.0f s after the close\n", HANGUP_WATCH_SECONDS);
return 0;
}
printf("DTR FAIL: ");
for (f = 0; f < failCount; f++) {
printf("%s%s", f == 0u ? "" : "; ", fails[f]);
}
// The states reported since the check, each after its count of data
// bytes.
printf("; DTR since the check (data byte, state):");
states = dtrStates(lines, &count);
shown = false;
for (i = 0; i < count; i++) {
if (states[i].at >= base) {
printf("%s%zu %s", shown ? ", " : " ", states[i].at, states[i].up ? "up" : "down");
shown = true;
}
}
printf("%s\n", shown ? "" : " none");
return 1;
}
// The DTR states the guest has reported, and (in count) how many.
static const DtrT *dtrStates(const LinesT *lines, size_t *count) {
*count = lines->states.len / sizeof(DtrT);
return (const DtrT *)lines->states.data;
}
// The data bytes of len raw ones (in place, at the start of data), the DTR
// states among them recorded with the count of data bytes before each. An
// escape at the end waits for the byte after it.
static size_t linesFeed(LinesT *lines, uint8_t *data, size_t len) {
DtrT state;
size_t in;
size_t out;
out = 0u;
for (in = 0; in < len; in++) {
if (!lines->escaped && data[in] == LINE_ESCAPE) {
lines->escaped = true;
} else if (lines->escaped && data[in] != LINE_ESCAPE) {
lines->escaped = false;
state.at = lines->dataCount;
state.up = (data[in] & LINE_DTR) != 0u;
toolBufAdd(&lines->states, &state, sizeof(state));
} else {
lines->escaped = false;
data[out++] = data[in];
lines->dataCount++;
}
}
return out;
}
// Every byte to the guest; a link that fails ends the peer (exit 1).
static void linkSend(LinkT *link, const void *data, size_t len) {
if (!gateNetSendAll(link->fd, data, len)) {
toolFail("send: %s", strerror(errno));
}
}
// Wait for the banner, then send the probe (again every RESEND_SECONDS)
// until it comes back. got collects the stream.
static int loop(LinkT *link, double wall, double start, const char *banner, ToolBufT *got) {
size_t sentAt;
double lastSend;
sentAt = GATE_TEXT_NOT_FOUND;
lastSend = 0.0;
while (gateNetNow() - start < wall) {
if (gateTextFind(got->data, got->len, 0u, banner, strlen(banner)) != GATE_TEXT_NOT_FOUND && (sentAt == GATE_TEXT_NOT_FOUND || gateNetNow() - lastSend > RESEND_SECONDS)) {
linkSend(link, PROBE_LINE, strlen(PROBE_LINE));
lastSend = gateNetNow();
if (sentAt == GATE_TEXT_NOT_FOUND) {
sentAt = got->len;
}
}
if (!receive(link, got)) {
break;
}
if (sentAt != GATE_TEXT_NOT_FOUND && gateTextFind(got->data, got->len, sentAt, PROBE, strlen(PROBE)) != GATE_TEXT_NOT_FOUND) {
printf("LOOP PASS: banner ");
gateNetShowBytes(stdout, banner, strlen(banner));
printf(" seen, probe echoed; stream=");
gateNetShowBytes(stdout, got->data, got->len < SHOWN_BYTES ? got->len : SHOWN_BYTES);
printf("\n");
return 0;
}
}
printf("LOOP FAIL: stream=");
gateNetShowBytes(stdout, got->data, got->len < FAIL_SHOWN_BYTES ? got->len : FAIL_SHOWN_BYTES);
printf("\n");
return 1;
}
// "<label> FAIL" for the first marker SERTEST did not send, with the stream
// since the check began.
static void markFail(const char *label, const MarksT *marks, const ToolBufT *got, size_t base) {
const char *mark;
size_t shown;
mark = kMarks[marks->count];
shown = got->len - base < SHOWN_BYTES ? got->len - base : SHOWN_BYTES;
printf("%s FAIL: no ", label);
gateNetShowBytes(stdout, mark, strlen(mark));
printf(" from SERTEST; stream=");
gateNetShowBytes(stdout, got->data + base, shown);
printf("\n");
}
// A raw pty pair: the host end returned, the guest end's path published in
// pathFile (whole, by a rename: the gate polls for the file). The guest end
// stays open here for the peer's life, so the guest's closes never hang the
// host end up.
static int openPty(const char *pathFile) {
const char *guestPath;
char *tmpPath;
size_t tmpLen;
int host;
int guest;
host = posix_openpt(O_RDWR | O_NOCTTY);
if (host < 0 || grantpt(host) != 0 || unlockpt(host) != 0 || (guestPath = ptsname(host)) == NULL) {
toolFail("cannot make a pty: %s", strerror(errno));
}
guest = open(guestPath, O_RDWR | O_NOCTTY);
if (guest < 0) {
toolFail("cannot open %s: %s", guestPath, strerror(errno));
}
rawMode(guest);
rawMode(host);
guestPath = ttyname(guest);
tmpLen = strlen(pathFile) + sizeof(TMP_SUFFIX);
tmpPath = (char *)malloc(tmpLen);
if (guestPath == NULL || tmpPath == NULL) {
toolFail("cannot name the pty");
}
snprintf(tmpPath, tmpLen, "%s%s", pathFile, TMP_SUFFIX);
if (!toolWriteFile(tmpPath, (const uint8_t *)guestPath, strlen(guestPath)) || rename(tmpPath, pathFile) != 0) {
toolFail("cannot write %s", pathFile);
}
free(tmpPath);
return host;
}
// Raw mode: every POSIX input and local mode off, no output processing, 8
// bits without parity, reads returning each byte.
static void rawMode(int fd) {
struct termios mode;
if (tcgetattr(fd, &mode) != 0) {
toolFail("tcgetattr: %s", strerror(errno));
}
mode.c_iflag &= ~(tcflag_t)(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
mode.c_oflag &= ~(tcflag_t)OPOST;
mode.c_cflag &= ~(tcflag_t)(PARENB | CSIZE);
mode.c_cflag |= CS8;
mode.c_lflag &= ~(tcflag_t)(ECHO | ECHOE | ECHOK | ECHONL | ICANON | IEXTEN | ISIG | NOFLSH | TOSTOP);
mode.c_cc[VMIN] = 1;
mode.c_cc[VTIME] = 0;
if (tcsetattr(fd, TCSAFLUSH, &mode) != 0) {
toolFail("tcsetattr: %s", strerror(errno));
}
}
// What the guest sent within the link's poll time, added to into (unless
// NULL): false at the end of a TCP stream. A pty's host end fails its reads
// while no guest has the other end open (SERTEST closes and reopens the
// port), so on a pty a failed read is only "nothing yet".
static bool receive(LinkT *link, ToolBufT *into) {
uint8_t raw[READ_SIZE];
int32_t got;
size_t len;
got = gateNetRecv(link->fd, raw, sizeof(raw), link->pollSeconds);
if (got <= 0) {
return got == GATE_NET_IDLE || link->isPty;
}
len = (size_t)got;
if (link->framed) {
len = linesFeed(&link->lines, raw, len);
}
if (into != NULL) {
toolBufAdd(into, raw, len);
}
return true;
}
// After the loopback: total bytes through SERTEST's echo, timed. The
// probe's last echoes go first.
static int stream(LinkT *link, double wall, double start, size_t total, long rate) {
ToolBufT got;
uint8_t chunk[STREAM_CHUNK];
size_t sent;
size_t len;
size_t at;
size_t i;
double settle;
double began;
double elapsed;
double perSecond;
double passMark;
settle = gateNetNow() + STREAM_SETTLE_SECONDS;
while (gateNetNow() < settle) {
if (!receive(link, NULL)) {
printf("STREAM FAIL: the connection closed before the stream\n");
return 1;
}
}
memset(&got, 0, sizeof(got));
sent = 0u;
began = gateNetNow();
while (got.len < total && gateNetNow() - start < wall) {
if (sent < total && (sent < got.len || sent - got.len < STREAM_WINDOW)) {
len = total - sent < STREAM_CHUNK ? total - sent : STREAM_CHUNK;
for (i = 0; i < len; i++) {
chunk[i] = streamByte(sent + i);
}
linkSend(link, chunk, len);
sent += len;
}
if (!receive(link, &got)) {
break;
}
}
elapsed = gateNetNow() - began;
perSecond = elapsed > 0.0 ? (double)got.len / elapsed : 0.0;
passMark = (double)rate / LINE_BITS_PER_BYTE * STREAM_MIN_PERCENT / PERCENT;
len = got.len < total ? got.len : total;
for (at = 0; at < len && got.data[at] == streamByte(at); at++) {
// Up to the first wrong byte.
}
if (got.len != total || at < total) {
printf("STREAM FAIL: %zu of %zu bytes back, the first wrong at %zu; ", got.len, total, at);
gateNetShowBytes(stdout, got.data + at, got.len - at < STREAM_SHOWN_BYTES ? got.len - at : STREAM_SHOWN_BYTES);
printf("\n");
return 1;
}
if (perSecond < passMark) {
printf("STREAM FAIL: %zu bytes back in order, %.0f a second, under %.0f (%d%% of %ld baud)\n", total, perSecond, passMark, STREAM_MIN_PERCENT, rate);
return 1;
}
printf("STREAM PASS: %zu bytes back in order, none lost, %.0f a second (%ld baud carries %ld)\n", total, perSecond, rate, rate / LINE_BITS_PER_BYTE);
return 0;
}
static uint8_t streamByte(size_t index) {
return (uint8_t)(STREAM_FIRST_BYTE + index % STREAM_BYTE_VALUES);
}
static int usage(void) {
fprintf(stderr, "usage: %s tcp-server PORT WALL [BANNER] listen; the emulator connects\n %s tcp-client PORT WALL [BANNER] connect to the emulator\n %s pty PATHFILE WALL [BANNER] make a raw pty pair, write the\n guest end's path to PATHFILE\noptions: --close-hold FILE (pty), --stream N --rate RATE (TCP), --dtr-check (TCP)\n", TOOL_NAME, TOOL_NAME, TOOL_NAME);
return GATE_USAGE_EXIT;
}
int main(int argc, char **argv) {
LinkT link;
ToolBufT got;
const char *pos[POS_MAX];
const char *holdFile;
const char *banner;
long streamBytes;
long rate;
uint16_t port;
double wall;
double start;
bool withDtrCheck;
bool isPty;
bool isServer;
int posCount;
int i;
int status;
gateNetStart(TOOL_NAME);
holdFile = NULL;
streamBytes = 0;
rate = 0;
withDtrCheck = false;
posCount = 0;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--dtr-check") == 0) {
withDtrCheck = true;
} else if (strcmp(argv[i], "--stream") == 0 && i + 1 < argc) {
streamBytes = gateArgLong("stream length", argv[++i]);
} else if (strcmp(argv[i], "--rate") == 0 && i + 1 < argc) {
rate = gateArgLong("rate", argv[++i]);
} else if (strcmp(argv[i], "--close-hold") == 0 && i + 1 < argc) {
holdFile = argv[++i];
} else if (strncmp(argv[i], OPTION_DASHES, strlen(OPTION_DASHES)) == 0 || posCount == POS_MAX) {
return usage();
} else {
pos[posCount++] = argv[i];
}
}
if (posCount < POS_MIN) {
return usage();
}
// --dtr-check and --stream are TCP's, --close-hold the pty's, and a
// stream needs its line rate.
isPty = strcmp(pos[POS_MODE], "pty") == 0;
isServer = strcmp(pos[POS_MODE], "tcp-server") == 0;
if ((!isPty && !isServer && strcmp(pos[POS_MODE], "tcp-client") != 0) || (withDtrCheck && isPty) || (holdFile != NULL && !isPty) || streamBytes < 0 || (streamBytes > 0 && (isPty || withDtrCheck || rate <= 0)) || (rate != 0 && streamBytes == 0)) {
return usage();
}
port = isPty ? 0u : gateArgPort(pos[POS_TARGET]);
wall = gateArgDouble("wall", pos[POS_WALL]);
banner = posCount > POS_BANNER ? pos[POS_BANNER] : DEFAULT_BANNER;
start = gateNetNow();
memset(&link, 0, sizeof(link));
memset(&got, 0, sizeof(got));
link.isPty = isPty;
link.framed = withDtrCheck;
link.pollSeconds = POLL_SECONDS;
if (isPty) {
link.fd = openPty(pos[POS_TARGET]);
status = loop(&link, wall, start, banner, &got);
if (status != 0 || holdFile == NULL) {
return status;
}
return closeHold(&link, wall, start, &got, holdFile);
}
link.fd = isServer ? gateNetAcceptOne(port, wall) : connectOne(port, wall, start);
if (link.fd < 0) {
printf("LOOP FAIL: no connection on port %u\n", (unsigned)port);
return 1;
}
status = loop(&link, wall, start, banner, &got);
if (status != 0) {
return status;
}
if (streamBytes > 0) {
link.pollSeconds = STREAM_POLL_SECONDS;
return stream(&link, wall, start, (size_t)streamBytes, rate);
}
if (!withDtrCheck) {
return status;
}
return dtrCheck(&link, wall, start, &got);
}