Releases: rickybryce/ethernetgateway
Release list
ethernetgateway v0.8.0
Added
-
CP/M emulator — Z80 CPU + interactive console (in progress). A
new default-off config keycpm_emu_enabled(wired into the telnet, web, and
GUI config UIs) gates aK CP/M Systemmain-menu item. Selecting it boots a
real Z80 CPU (the BSD-licensediz80crate)
in a 64 KB machine driven by our own CP/M 2.2 BDOS, and drops into a Rust
CCP-liteA>prompt. The full console BDOS group (character/string output,
console input with echo, direct console I/O, read-console-buffer, console
status, version) is wired to the telnet/SSH session, so interactive Z80
programs can read and write the console; built-inHELLOandECHOdemos
exercise it. Works correctly on PETSCII (C64) terminals as well as ANSI/ASCII.
On launch it creates the drive foldersCPM/A..CPM/Hundertransfer_dir.
An interactive program can be aborted with a double-ESC, and a runaway is
bounded by an instruction budget. Completely separate from the Gateway Shell,
which emulates no CPU; the item is hidden and the key rejected while the
toggle is off.- Filesystem, part 1 — FCB + drives + sequential file I/O. The emulator
now has a directory-backed CP/M filesystem: each drive A:–H: is a folder in
theCPM/container, and the BDOS file calls for opening, creating,
closing, and sequentially reading/writing files (via the DMA buffer and a
parsed 36-byte FCB) are implemented and jailed undertransfer_dir. Drive
select / current-disk / set-DMA are wired, 8.3 filenames are enforced
(case-insensitive; host files that aren't valid 8.3 are invisible to CP/M),
and theA>prompt gainedA:..H:drive-change commands with a
drive-aware prompt. - Filesystem, part 2 — directory search + erase. The BDOS
search-first/search-next calls enumerate a drive's files (with?
wildcards, synthesizing CP/M directory entries per 16 KB extent) and the
delete call removes matching files. The CCP-lite gained the authentic
built-insDIR(list the current drive) andERA name(erase, with a
*.*confirmation), so files uploaded into aCPM/drive can be listed
and removed interactively. - Filesystem, part 3 — random-record I/O, file size, rename. The BDOS
random read/write calls seek to a record number (with the sequential
position kept in sync), compute-file-size reports a file's length in
records, and rename moves a file to a new 8.3 name (no clobber). This
completes the CP/M 2.2 file BDOS surface, so real utilities likePIP
andSTATbecome runnable once.COMloading lands. - Run a real
.COMfrom a drive. A command at theA>prompt that
isn't a built-in is now resolved as<verb>.COMon the drive (honoring a
B:drive prefix), loaded into the TPA, and executed — so actual CP/M
software (PIP, STAT, ASM, …) uploaded into aCPM/drive runs over
telnet/SSH. Page zero is set up exactly as the CCP does before launch: the
command tail is placed at 0x0080 and the first two arguments are parsed
into the default FCBs at 0x005C / 0x006C. The program image is loaded
jailed undertransfer_dir(canonical-prefix + symlink checks) and bounded
by the per-file size cap. - Full CP/M resident command set. The
A>prompt now implements all
six authentic CP/M 2.2 resident commands —DIR,ERA,REN,TYPE,
SAVE,USER(plus thed:drive change) — so no upload is needed for
everyday file work.RENrenames (no clobber);TYPEstreams a text file
and stops at the^Zend-of-file marker (binary files refused);USER
selects an area (only area 0 exists — one flat area per drive). To make
SAVEauthentic, the emulated machine now stays resident across commands:
the transient program area survives a warm boot back toA>(as on real
CP/M), soSAVE n filedumps the image a previous program (e.g.DDT)
left in memory. Low-memory vectors are reinstalled on each program load so
a program that trashes page zero can't corrupt the next one. - Virtual-modem UART port selection. A new config key
cpm_emu_uart
(wired into the telnet, web, and GUI config UIs, each showing a description
beside every choice) selects which machine/port address the emulated CP/M's
modem answers at —off(default), the RC2014/RomWBW Z80 SIO/2 channels
(rc2014_1a…rc2014_2b, 0x80–0x87), or the Altair 88‑2SIO / 88‑SIO
(altair_2sio1/altair_2sio2/altair_sio). Addresses and status-bit
conventions are taken from the RomWBW SIO driver and David Hansel's Altair
simulator. With a profile selected,CpmMachine's port I/O answers at those
addresses with a valid idle UART (transmit ready, nothing received) so
comms software can probe and initialise the port. - Virtual modem — outbound dialling. The CP/M modem now speaks Hayes
ATand can place calls:ATD A/ATD Bdial the gateway's own serial
Port A / Port B (via the existing peer-dial plumbing, like one machine
calling another), andATDT host:portopens a TCP connection. On answer it
reportsCONNECTand becomes a transparent data pipe;+++returns to
command mode andATHhangs up. A newauxprofile choice puts the modem
on the CP/M BDOSAUX:device (functions 3/4) — the hardware-independent
path for SC126/RomWBW software (a Z180 ASCI port profile can't work: the
Z80 core doesn't implement the Z180IN0/OUT0instructions the ASCI
uses). The modem is a self-contained async layer bridged to the guest's
synchronous UART/AUX byte rings at the CPU batch seam.
- Filesystem, part 1 — FCB + drives + sequential file I/O. The emulator
-
EGT80 — "Ethernet Gateway Terminal", a CP/M terminal of our own. Every
period CP/M terminal is built for one machine's serial port — commonly a
separate binary per port, or a build for one specific card, and some generic
builds carry no serial driver at all — and pairing one with the wrong port
produces silence rather than an error.EGT80.COM(newEGT80/directory,
Z80 assembly, CP/M 2.2 and CP/M 3) asks instead: a menu picks the port at run
time, from five families — Z80 SIO/2, 6850 ACIA, RomWBW HBIOS (RST 8),
Z180 ASCI and the CP/MAUX:device — with a free-form address entry for
boards at unusual ports. It carries a console layer chosen once at startup
(BIOS vectors on 2.2, BDOS 6 on 3), menus and help written for someone who
does not yet know which port their machine uses, terminal mode with an
escape-key menu, an ANSI/ASCII inbound filter (pass escape sequences through,
or strip them and the high bit for a printing terminal), settings saved into
its own.COM(a 128-byte-aligned patch area rewritten with a
random-record write, signature- and range-checked at startup and falling back
to defaults if damaged), and XMODEM transfer both ways — 128-byte blocks,
CRC-16 with the checksum fallback an older peer may insist on, one buffer
serving as both protocol block and CP/M record, abortable with a keypress, and
refusing to leave a partial file behind when a download fails.Shipped ready to use: the
.COMis compiled into the gateway binary and
placed on CP/M drive A: when the emulator first creates its drive folders, so
it is simply there. That copy is never overwritten afterwards, because the
settings live inside it; deleting it restores the shipped copy. Release
archives also carry the looseEGT80.COMfor sending to real hardware.The port screen names the port in force and offers
D— the default port
(the one the gateway also defaults to, so the pair works together again in one
keystroke), and the menu says(changed — press V to keep it for next time)
while a setting is live but not yet written to the file.^Cleaves the
wrong-port notice — which swallows everything else you type, so a half-typed
line can no longer run menu commands by accident — aborts a transfer in
progress, and cancels at the filename prompt.Built by running the real period assembler (SLR
Z80ASM) underzxcc, so
SLR80 compatibility is structural rather than hoped for, with M80+L80 and ZMAC
as portability gates. Wrong choices are diagnosed rather than left to hang: a
port that never accepts a byte is named and explained, HBIOS is refused on a
machine with noRST 8vector, and the Z180 ASCI family is refused on a
processor that is not a Z180 (MLTtells them apart). Two paths cannot be
exercised by any test here and are documented as reasoned-not-run: the Z180
ASCI driver (our Z80 core has noIN0/OUT0) and the CP/M 3 console path.- Virtual modem — RomWBW HBIOS access (
hbios_1/hbios_2). Some CP/M
comms software doesn't drive a UART at all: software built for RomWBW asks
the firmware to move the byte, issuing anRST 8with a function number in
Band a serial unit inC. On a port profile such a program hangs before
printing anything — its first call goes nowhere — which no port address can
fix; RomWBW-targeted builds of period comms software are exactly this
case. Two newcpm_emu_uartchoices answer that
API for one serial unit (the virtual modem), so those builds now run:AT→
OK,ATDT host:port→CONNECT, with data flowing both ways. TheRST 8
vector is installed only for these profiles, so every other setting keeps
the untouched page zero of a plain CP/M 2.2 machine; a call for a unit other
than the selected one is refused, so a mismatch fails the same recognisable
way a wrong port address does. Implemented: the serial (character device)
group — in, out, input/output status, initialise, query, describe — plus the
version and serial-unit-count calls. Refused with an error result: bank
switching / memory management, disk, RTC, video, sound, DSKY (RomWBW
hardware services with no counterpart here) — an honest failure at...
- Virtual modem — RomWBW HBIOS access (
ethernetgateway v0.7.0
Added
- Gateway Shell — a CP/M-inspired file manager over telnet/SSH. A new
S Gateway Shellitem on the File Transfer menu opens anA>command prompt
that presents the transfer directory as drive A: (flavor A: pure Rust, no
Z80/.COMemulation). Resident commandsDIR/LS,TYPE,DUMP,ERA
(DEL/RM),REN,COPY(PIP/CP),MOVE(MV),MKDIR(MD),
RMDIR(RD),CD,PWD,STAT,HELP(?), andEXITcover full file
management, including cross-directory copy/move via a/-separated path
syntax the base CP/M command set can't express, and*/?wildcards for
DIR/ERA/COPY.TYPE/DUMP/DIRpaginate with a--More--prompt, and
TYPErefuses binary files. Every operand is jailed to the transfer directory
(validated + canonicalized;../absolute/symlink escapes are refused); copy/
move honor the 8 MB transfer cap and theTYPE/DUMPviewers cap reads at
1 MiB. Works identically over telnet and SSH. Documented in user manual §8.10.
(Flavor B — a real Z80 CP/M 2.2 emulator — remains deferred.) - Third-party license notices and a license-policy gate.
THIRD-PARTY-NOTICES.md
(generated bycargo-aboutfrom
about.toml+about.hbs) reproduces every dependency's copyright notice and
license text. A new CIlicensesjob runs
cargo-denyagainst the
GPL-3-compatible allowlist indeny.tomland gates the build (no
continue-on-error), so a GPL-3-incompatible or unknown-licensed dependency
can't land silently. README documents the workflow.
Changed
- Added a full "as is" / no-warranty disclaimer at the top and bottom of
bothREADME.mdand the user manual (usermanual.html), including a note
that portions of the project were developed with the assistance of AI tools. README.mdcondensed to a quick-start + feature overview (~1815 → ~190
lines). The reference material that duplicated the user manual (the annotated
egateway.confdump, AT-command / S-register tables, telnet RFC compliance
matrix, release-verification and systemd walkthroughs, per-distro build
matrix) now lives only in the manual, with links; the repo-unique hardware
quick-start, security posture, and license are kept, and the screenshot is
surfaced near the top.- User manual: documented kercpm3's
Password:prompt onremote cd.
CP/M Kermit clients prompt for the optional Kermit CWD password (Protocol
Manual §6.7 second field) before sending theG Cpacket; the gateway's
Kermit server is unauthenticated by design and ignores that field, so the
directory change always succeeds — press Enter. Also noted the
recognised-but-unsupportedUSERcommand in the Gateway Shell (§8.10).
Fixed
- Gateway Shell:
DIR SUBnow lists the subdirectory's contents (like
DIR SUB/) instead of just showing the<DIR>entry — a wildcard-free
operand that names an existing directory is listed, matching the DOS/Unix
expectation.DIR name,DIR *.pat, andDIR fileare unchanged. - Gateway Shell: name resolution is now case-insensitive.
DIRshows names
uppercased, so a directory stored on disk asz80asmdisplayed asZ80ASM
andCD Z80ASMthen failed "No such directory." (only the lowercase form
worked) — and PETSCII terminals swap case on the wire, compounding it.
CD/TYPE/DUMP/ERA/STAT/REN/COPY/MOVEsource operands now match
an existing name case-insensitively (exact case wins, else the first
case-insensitive hit) and resolve to the real on-disk name; newly created
names keep the case as typed. Still fully jailed to the transfer directory. - Gateway Shell: the
A>prompt after HELP now appears on its own line. The
help pager's "Press any key" was dismissed with the cursor mid-line, so the
returning prompt was glued to it (Press any key.A>); the pager now advances
to a fresh line before returning (harmless for the menu callers, which
redraw). - PETSCII: declining color no longer drops a Commodore terminal to ASCII.
Color was tracked implicitly by the terminal type, so answering "N" to the
color prompt forcedTerminalType::Ascii— which also discarded PETSCII's
40-column, case-swapped, ANSI-stripped layout, leaving a C64 caller in an
80-column ASCII view. Color is now a separatecolor_enabledflag: declining
color keeps the detected terminal type (PETSCII stays PETSCII) and simply
emits plain text. Also makes the SSH/telnet gateway's PETSCII handling correct
for a no-color C64. Side effect for ANSI terminals: declining color now
means "no color in the gateway's own menus" rather than "downgrade to ASCII,"
so remote-host ANSI passed through the SSH/telnet gateway is no longer stripped
and the onward terminal is advertised asxterminstead ofdumb— the
correct behavior for a terminal that answered the ANSI-color prompt. - Serial: a serial-manager thread can no longer panic on a dropped runtime
across a config restart (round-7 review). The detached serial threads
block_onthe tokio runtime, but a SIGHUP restart dropped the runtime
without joining them; a thread stuck in the synchronousconnect_timeout
(an in-flight ATDT/peer dial to an unresponsive host, up to 60 s) would then
panic on its nextblock_on. The dial now connects asynchronously, raced
against the shutdown/restart flag (aborting within ~100 ms instead of being
blind for the whole carrier wait), andmainbounded-joins the serial
threads before dropping the runtime. Self-healing before; airtight now. - Web browser: a hostile page can no longer soft-DoS the render thread with
form-label lookups (round-6 F1). Each form field with anidbut no
placeholder/aria-label/title triggered a full recursive walk of the form
subtree looking for<label for="id">— O(fields × subtree), so a page of
tens of thousands of bare<input id=…>(under the 1 MB body cap) cost
quadratic CPU on a sharedspawn_blockingthread with no render time budget.
Labels are now collected in a single O(subtree) pass into an id→label map;
per-field lookup is O(1). - Weather: a malformed forecast timestamp no longer panics the fetch
(round-6). The MET.no forecast parse sliced the first 10 bytes of the
timefield guarded by byte length only; a timestamp with a multibyte char
in the first 10 bytes would panic on a mid-character boundary. It now uses a
char-boundary-safe accessor and skips a malformed entry. (Was contained to a
single weather fetch byspawn_blocking, never a process crash.) - Serial: dialing a console-mode port relayed to the master no longer wedges
the caller's serial thread (round-5 review). On a slave gateway with
allow_peer_dialon,ATD <ConsolePort>@<local-ip>reached a local console
bridge that nothing services (the console port runs the master-registration
loop, not the local bridge), so the caller's thread blocked forever on the
bridge oneshot — unrecoverable short of a full restart.connect_local_peer
now fails that dial fast with NO CARRIER (mirroring the Serial Gateway
picker's exclusion), and the console-bridge request is additionally raced
against a shutdown/restart poll so no path can pin the thread. - Relay: IPv6 onward-dial targets are handled correctly (F1). The onward-
dial path split/rebuilthost:portwith a barersplit_once(':'), leaving
brackets on an IPv6 host soconnectfailed. A sharedsplit_dial_host_port
now parses[2001:db8::1]:6400into a bare literal, the slave brackets IPv6
on the wire, and both halves agree (unbracketed IPv6 is rejected as
ambiguous). IPv4/hostname dialing is unchanged. - Telnet: a session-slot / broadcast-writer leak on a panicking session is
now prevented (F3). Slot release and writer de-registration ran only after
session.run()returned, so a future reachable panic would leak a
max_sessionsslot and grow the broadcast list unbounded. A RAII backstop
(SessionSlotGuard) now reclaims both on unwind; the normal path defuses it
after the graceful async cleanup. (No panic is reachable today — this is
defensive hardening consistent with the SSH/relay Drop guards.) - Kermit receive: windowed receiver now ACKs buffered out-of-order packets
(selective repeat, spec §5.5) (K1). Previously it buffered a correctly-
received future packet but only NAKed the missingexpected_seq— once per
future — never ACKing the good packet; the windowed sender counted each
duplicate NAK as a retry and re-sent packets it didn't need to. The receiver
now ACKs each future by its own seq and NAKs the gap once, matching the spec
and removing the redundant retransmissions (and the retry-budget pressure a
reordering link could put on a large window). Live C-Kermit 10.0 sliding-
window interop unchanged. - Punter receive: a premature-retransmit duplicate no longer corrupts the
file (P1/P2). On a slow/jittery link a data block whose first byte was
delayed past the byte-wait could trigger an earlyS/Bresend; the delayed
block and a re-sent copy would both arrive, and the receiver — which ignored
the block index for sequencing — appended the interior block twice,
returning a silently one-block-too-long file. The receiver now dedups on the
checksum-protectedNUMPOSblock index (dropping a block whose index did not
advance), bounded so a peer stuck re-sending one block still gives up. Verified
against the live CCGMS reference (both directions) — the dedup never triggers
on conforming traffic. - Punter: a mid-IAC-sequence timeout in a handshake window is now recoverable
(P3).accept_codetreats thetnioIAC-timeout (N4) as a soft "no code
this round" re-probe — matchingread_block— instead of aborting the whole
transfer; other read errors still abor...
ethernetgateway v0.6.4
Added
- Serial ports gain a third mode: Kermit Server. Alongside Modem
(AT Command) Mode and Telnet-Serial Mode, each serial port (A/B) can
now run as an always-on Kermit server: as soon as the port is enabled it
listens for Kermit packets directly on the wire — no AT commands, no
dialing, no menu. It is the same serverATDT KERMITreaches from the
modem emulator, but always on and with no AT layer; received files land
intransfer_dir, and it re-arms after every FINISH/BYE so the wire stays
a live server. The port reopens automatically if the device disappears
(matching modem mode). Selectable from the GUI Mode dropdown, the web
config's per-port "More…" popup, and the telnet per-port T toggle
(which now cycles Modem → Console → Kermit). Persists as
serial_a_mode/serial_b_mode = kermit. Auth and the telnet menu are
bypassed by design — enable only on trusted serial lines (same posture as
allow_atdt_kermit). - YMODEM receives multi-file batches (
sb file1 file2 …). The receiver
previously ran the end-of-batch handshake right after the first file's EOT,
so a batch sender lost every file after the first (and could hang waiting for
the receiver). It now reads the next block 0 at each EOT — a named block 0
starts the next file, the null block 0 ends the batch — and returns every
file. The first file keeps the user-entered name; files 2..N use the sender's
(sanitized) block-0 name, saved atomically like ZMODEM/Kermit batches. A
corrupt inter-file block 0 is NAK-retried (bounded), the batch is capped at
1000 files, and a non-UTF-8 file name is received under a generated name
rather than truncating the batch. - Weather works worldwide, not just US zip codes. The Weather menu now
accepts any city name or postal code (London,SW1A 1AA,Zürich,
62051), percent-encoding the query so spaces and non-ASCII are safe. A
City, CountryorCity, Regionqualifier disambiguates common names
(London, GBvsLondon, Ontario;Paris, FrancevsParis, Texas), and
the matched country is shown. A newweather_unitssetting —auto
(default: Fahrenheit/mph for the US, Celsius/km/h elsewhere),us, or
metric— controls display units; press U on the weather screen to cycle
them in place (no re-fetch). Wired into the telnet Other-Settings menu, the
web config page, and the GUI. The config keyweather_zipis renamed to
weather_location; an existingweather_zipvalue migrates automatically
on first load, and any saved location persists across sessions as before. - Configurable desktop GUI display scale (
gui_zoom). The console window
now honors agui_zoomsetting:auto(default) follows the monitor's own
scale factor as before, while a number (e.g.1.0,1.25,0.8) pins the
window's pixels-per-point absolutely so a display that reports an inflated
DPI no longer renders the GUI oversized. Selectable as "Display scale" from
the GUI's Server → More panel and the web config's Server → More page
(Auto / 75% / 100% / 125% / 150% / 200%), and clamped to 0.5–3.0. - Show the file being downloaded on the SELECT PROTOCOL screen. The
download protocol picker now displays the file name (truncated to the
terminal width) and byte size above the protocol list, so the user can
confirm the right file before choosing a protocol. - Make directories from the telnet file-transfer menu. A new M option
creates a subdirectory inside the current transfer working directory (the
name is validated like a filename — a single component, no..or/), then
asks whether to make it the working directory. - Weather falls back to MET Norway when Open-Meteo is unreachable. If the
Open-Meteo forecast host can't be reached, the Weather menu now automatically
retries the forecast against MET Norway (api.met.noLocationforecast 2.0 —
free, no API key, independent infrastructure), reusing the coordinates
already geocoded via Open-Meteo (worldwide coverage, so the fallback works for
any location). MET's data is kept in metric and converted to your chosen units
at display time, and its symbol codes mapped to descriptions; you only see an
error if both providers fail. - Wait for the receiver before starting a Kermit download
(kermit_wait_for_receiver, default on). A Kermit transfer is
receiver-driven at the start — the receiving side sends aNAKto solicit the
sender's Send-Init (Frank da Cruz, Kermit Protocol Manual §4). The gateway
now holds its Send-Init until that poke arrives and then sends exactly one, on
both interactive downloads and Kermit server GET responses. A client that
never pokes (e.g. C-Kermit) falls through a short bounded wait and gets an
unprompted Send-Init as before. Wired into the telnet Kermit-settings menu
(G), the web config page, and the GUI. - Verbose Kermit receive-path logging. With
verbose = true, the Kermit
upload/receive path now emits periodic per-packet progress plus a
per-file summary (bytes, packets, block-check type), matching the diagnostic
style of the XMODEM/YMODEM/ZMODEM paths. Off by default.
Changed
- Warning popups are now dark red (GUI and web). Security/confirmation
warnings previously looked identical to ordinary popups, so it wasn't obvious
the modal was blocking the next click and had to be acknowledged. The GUI's
four warning popups (ATDT-KERMIT, Kermit-server, disable-IP-safety,
master-needs-SSH) now use a dark-red panel + red border. On the web, the
nativeconfirm()/alert()warnings (disable web server, change web port,
master-needs-SSH) are replaced with matching dark-red modal dialogs whose
overlay blocks the form until the operator chooses Continue/Cancel. The web
also gains the enable-guard warnings it was missing versus the GUI —
Disable IP Safety, Kermit Server, and Allow ATDT KERMIT now raise
the same red confirmation before they take effect. - ZMODEM batch receive is capped at 1000 files (
MAX_BATCH_FILES, matching
YMODEM and Kermit), so a peer that streams endless files can't grow the
in-memory batch without bound; the receiver cancels and errors past the cap. - Config UI: tidier frames via "More" popups. The web config page now keeps
the Master/Slave relay settings under the Server frame's More… popup
(they were a separate card), matching the GUI and returning the page to six
frames. The AI Chat, Browser, and Weather frame (both web and GUI) is now
three rows — API key and homepage on the frame, with a More… button that
opens the weather location and units. - Weather fetch fails fast with a clearer message. The Open-Meteo request
now uses a 5 s connect timeout (was a single 15 s global) and retries once on
a transient transport failure, so an unreachable/blocked forecast host no
longer hangs the Weather menu for 15 s. Errors are distinguished:
"Not found - try 'City, Country'." (no geocoder match) vs "Weather service
unreachable. Try again later." (network/host down) vs "Weather service
returned bad data." (parse).
Fixed
- Serial Kermit Server Mode transfers at full speed. Kermit Server Mode was
far slower than the same server launched from the File Transfer menu. The
bridge that pumps bytes between the wire and the Kermit server
(run_console_bridge) drained its outbound queue only between wire reads,
and Kermit is stop-and-wait — so while the gateway composed each reply the
wire sat idle and the bridge blocked out the full wire-read timeout before
writing that reply, adding a fixed delay to every gateway-originated packet
(each ACK when receiving, each DATA when sending — hence the slowdown in both
directions). The menu/ATDT KERMITserver never had this because its pump
produces and flushes the reply in the same iteration that consumed the
request. Server Mode now uses that same inline pump (run_kermit_bridge_inline,
modeled on the modem online-mode path) instead of the decoupled two-thread
bridge, so a reply leaves the wire the moment it's produced. (The interactive
Serial Gateway console still usesrun_console_bridge, whose backpressure
design is intentional there.) - Kermit server uploads no longer drop a file on a name collision. When an
uploaded file's name already exists intransfer_dir, every Kermit-server
receive path (the telnet-menu server, the standalone TCP listener, and the
new serial Kermit Server Mode /ATDT KERMIT) now renames the incoming file
DOS/CP-M-Kermit style instead of skipping it — the base name is numbered
within 8 characters the way CP/M Kermit clients (e.g. kercpm3) do on a
download collision:abcdefgh.txt→abcdefg0.txt…abcdefg9.txt→
abcdef10.txt, and a shorter name such ashi.txt→hi0.txt. The original
file is never overwritten, and a verified resume still replaces its own
partial in place. (The pre-existing telnet-menu and TCP-listener paths
previously skipped such a collision with an "already exists" note.) ATDT KERMITuploads now actually save to disk. The serial Kermit-server
dial path passed a no-op file-commit hook to the server, but the Kermit
receiver only buffers uploaded files in memory and relies on that hook to
persist them — so a clientsend/putoverATDT KERMITcompleted on the
wire but left nothing intransfer_dir(downloads/get, which read from
disk, were unaffected). Both the always-on serial Kermit Server Mode (new,
above) and theATDT KERMITdial now commit each received file with the same
filename / subdir path-safety validation as the telnet and TCP-listener
Kermit server paths.- Kermit server GET is now case-insensitive. A client requesting a file in
a different case than it is stored on disk — CP/M clients such as kercpm3
uppercase filenames — no longer fails "File not found" and bu...
ethernet-gateway v0.6.3
Added
- The desktop GUI remembers its window position and size. The
configuration window now reopens where you last left it — its outer position
and inner size are saved (debounced) togui_window_geometryin
egateway.confand restored on the next launch. It is auto-managed: there is
no config-UI field for it, and an empty value means "use the default size and
let the window manager place it." Works on X11/Windows/macOS; Wayland
compositors don't expose a window's position, so it isn't remembered there. - Peer-dial: call another serial port directly. With the new
allow_peer_dialopt-in (default off; wired into telnet Configuration > M >
P, web, and GUI), a modem-mode port can dial another port by address —
ATD <Port>@<IP>(e.g.ATD B@192.168.1.50) — or select that port in the
Serial Gateway menu, and bridge straight through to the device on it (the
gateway equivalent of calling a friend's modem). A modem-mode target
rings and answers per its own AT rules (S0auto-answer / manualATA); a
console-mode target connects directly. The connection is a transparent
byte pipe, so a file-transfer protocol runs end to end between the two
devices. Result codes follow ATX (CONNECT/BUSY/NO ANSWER/NO CARRIER).
Works on the same gateway and, over the master/slave relay, from a slave
device to a port on its master (ATD <Port>@<master-ip>): the slave relays
the call and the master resolves the address to one of its own ports and
rings/connects it (gated by the master'smaster_accept_relays+
allow_peer_dial). Cross-gateway is symmetric: the master routes a peer
address to any port a slave has registered — a slave's console port
and its modem port (a slave modem port announces itself to the master and,
when dialed, rings the attached device) — so<Port>@<slave-ip>reaches a
slave's port from the master or, via the master as a crossbar, from another
slave (device ↔ slave-A ↔ master ↔ slave-B ↔ device). Addressing is by IP, so
gateways need distinct addresses (normal for separate machines). See README
"Peer-Dial" and user manual §9.2.3. - Live relay status in the telnet Master/Slave screen. A master now lists
the remote console ports slaves have registered (so you can see connected
slaves at a glance); a slave shows each console port's link state to its
master (down/connecting/registered/bridging) — relay connectivity is
now visible without reading the logs. - Relay channel handshake / protocol version. The master now writes a small
hello (EGRmagic + a protocol-version byte) as the first bytes on every
accepted master/slave relay or console-registration channel; the slave
validates it before using the channel. A master/slave version skew now fails
cleanly with an "upgrade the older gateway" message instead of desyncing, and
a slave pointed at a master that is declining relays (standalone,
master_accept_relays=false, or at capacity) now detects the refusal — the
absence of the hello — and backs off with a clear message, instead of
mistaking the refused-but-open channel for a live registration and idling. - Optional hardware carrier (DCD) signalling. New per-port opt-in
serial_a_drive_carrier/serial_b_drive_carrier(defaultfalse; also a
checkbox in the GUI/web config and the C key in the telnet per-port modem
menu). When enabled, the modem emulator drives DTR as a carrier proxy
(a PC/USB-serial adapter is a DTE and can't drive a DCD output, so you cross
DTR→DCD in a null-modem cable, as tcpser does), followingAT&C:&C0forces
it always asserted while the port is open,&C1(default) asserts on
CONNECTand drops onNO CARRIER/ATH/ hangup / relay-link-loss (so a
slave-attached machine sees loss-of-carrier in hardware too). When off, the
gateway makes zero modem-control-line calls, so ports without DCD wiring are
byte-for-byte unaffected. Modem mode only. - Master/Slave serial extender (optional). A gateway set to
gateway_role = slaveextends its serial ports to amastergateway over
the master's existing SSH port; the serial device reaches the master's menu,
file transfer, and dial-out as if attached to the master, and files always
land on the master. Defaultgateway_role = standaloneleaves the feature
entirely inert. Modem-mode ports relay on connect (the slave resolves its
local dial map; the master dials onward — "resolve local, dial central");
console-mode ports register with the master and appear in the master's Serial
Gateway picker (local ports + registered remote ports). New config keys
(telnet/web/GUI):gateway_role,master_accept_relays,slave_master_host,
slave_master_port,slave_master_username,slave_master_password,
relay_transport(onlysshimplemented). The slave authenticates with the
master's unified username/password and pins the master's SSH host key (TOFU,
ingateway_hosts); relay connections are gated bymaster_accept_relaysand
count against the session cap. The slave's main menu shows a SLAVE-mode notice
with the master address, and reconnects automatically if the link drops. - Serial sessions can now receive administrative broadcasts. A process-global
broadcast channel (serial::broadcast_to_serial) fans a message out to every
open serial port, delivered at the command prompt only — an in-call
(online) serial session, which may be carrying a binary file transfer, drains
its queued messages when it next returns to command mode (+++, hangup, or
call end) so a notice can never corrupt a transfer. This is the serial-side
counterpart to the telnet/SSH/relaybroadcast_to_sessionslist, completing
broadcast coverage across all connection types. The shutdown "Goodbye" keeps
its own reliable shutdown-flag write (which fires even mid-online) and is not
routed through this channel. Modem mode only. (Extension point: no production
broadcast is routed to it yet — the first admin-notice feature plugs in here.)
Fixed
- Serial
AT&Cnow updates the hardware carrier (DCD/DTR) line immediately.
Withserial_X_drive_carrierenabled, changingAT&Cat the command prompt
used to take effect only at the next connect/hangup; it now re-applies the
DCD line right away —&C0asserts DTR (carrier forced on regardless of call
state) and&C1restores follow-the-carrier — matching the documented
contract and the existingATZ/AT&Fbehavior. Found during on-hardware DCD
validation (DTR→DCD crossover). - GUI console started as a boot service now waits for the window manager.
When launched as a boot-time systemd service, the console window could come
up undecorated (no title bar / minimize / close) or with its title bar tucked
under the desktop panel, because it opened as soon as the X server accepted a
connection — before the window manager had taken over decoration and
placement. The display-wait now also waits (bounded, X11-only) for an EWMH
window manager (_NET_SUPPORTING_WM_CHECKon the root window) before opening
the window. Degrades safely: noxprop, a bare X server, or a non-EWMH WM
falls through after a short cap and opens anyway, and the server is never
delayed (only the window waits). Non-X11 targets (Windows, macOS, headless,
pure-Wayland) are unaffected — the wait returns immediately withoutDISPLAY. - Serial Gateway menu shows peer-dial addresses without spaces around
@.
Remote (slave) port entries are now displayed as<Port>@<ip>— exactly the
string you type to dial them (ATDT <Port>@<ip>). The previous spaced form
(<Port> @ <ip>) invited mistyped dial strings with embedded spaces. The
remote-bridge screen title and the master's registered-ports status list were
unspaced to match. - Master/Slave configuration now guides the operator by role. Across the
telnet menu, web, and desktop GUI, fields that don't apply to the selected
role are greyed out / disabled: accept relays is editable only for a
Master (and now defaults on when you switch to Master, since a master
with it off can't accept slaves), while the master host / port / user / pass
are editable only for a Slave. Switching to Master while the SSH server is
off now surfaces a warning (a popup in web/GUI, a dedicated screen in telnet)
explaining that slaves connect over SSH — it points you at the setting but
never toggles SSH for you. - Peer-dial now reminds you about local echo. A peer-dial connection is a
transparent link with no host echoing keystrokes back, so the Serial Gateway
picker shows a "enable local echo to see typing" tip, and the README /
user-manual peer-dial sections explain that each terminal needs local echo
(half-duplex) — and thatATEdoes not affect the online data path. - Shutdown "Goodbye" now reaches every session, not just when telnet is
enabled. The shutdown broadcast used to live inside the telnet accept loop,
so an SSH-only deployment (telnet_enabled = false) tore SSH and relay
sessions down with no notice. It is now a transport-neutral broadcast invoked
centrally at shutdown, so telnet, SSH, and master/slave relay sessions all
receive it for any combination of enabled servers (serial ports already emit
their own notice). The mechanism is reusable for future all-session messages. - File transfers over telnet no longer apply NVT CR-NUL stuffing, which
corrupted binary transfers through telnet↔serial bridges (e.g. tcpser) and
telnet-aware WiFi modems that don't symmetrically un-stuff. The shared
transfer I/O layer (tnio, used by XMODEM/YMODEM/ZMODEM/Kermit/Punter) now
escapes only IAC (0xFF→IAC IAC) and passes every other byte —
including CR (0x0D) — through literally, matching RFC 856 binary-transmission
semantics that 8-bit file transfer req...
ethernet-gateway v0.6.2
Added
- Session cap and idle timeout are now editable from the telnet Server
Configuration menu (theCandDkeys), matching the desktop GUI and the
web configuration page that already exposedmax_sessions/
idle_timeout_secs— completing three-UI parity for both settings. Idle
timeout accepts0to disable the idle disconnect. The screen's detected-IP
hint list is now capped so the new row keeps the PETSCII menu within its
22-row budget even on a multi-homed host (it previously overflowed at three or
more private addresses).
Security
- Fixed an SSRF-guard bypass for IPv6-literal URLs in the text-mode web browser.
guard_public_urlclassified IP literals withIpAddr::parse, buturl::Url::host_str()returns IPv6 literals bracketed (e.g.[::1]), which fails that parse and fell through to the resolver path — allowinghttp://[::1]/,http://[::ffff:127.0.0.1]/, and the like to reach loopback / link-local / internal IPv6 services (initial request and every redirect hop). The guard now strips the brackets before classifying, blocking the entire internal IPv6 space. Regression test added. IPv4 literals and DNS names were already handled correctly. - SSH: an unauthenticated connection no longer consumes a session slot.
new_clientincremented the session counter for every inbound TCP connection, before authentication, so a peer that opened many transport handshakes and stalled could exhaustmax_sessionsand lock out real users. The slot is now claimed only on a successful login (atomicfetch_add+ rollback, mirroring the telnet accept loop) and released only if it was claimed — and the cap is now exactlymax_sessions(was off-by-one,max_sessions + 1). - Web config:
POST /savenow enforces a same-origin check (CSRF defense-in-depth). A request whoseOrigin/Refererdoesn't match ourHostis rejected with 403, blocking a malicious page from riding the operator's cached Basic-auth credentials to rewrite config (including disabling auth). Requests with neither header (non-browser clients such ascurl, which can't be a CSRF vector) are still allowed; Basic auth continues to gate them. Lenient-on-absent by design for the trusted-LAN threat model. - Kermit server: defense-in-depth subdir re-validation on save. Both the in-session receiver and the standalone (auth-bypassing) Kermit listener now re-check
rx.subdirwithis_safe_relative_subdirbefore joining it to the transfer dir. No live traversal existed (subdir is only set after that same check inside the Kermit module), but re-validating at the save site closes the door on any future producer-side bypass — the same belt-and-suspenders rationale as the existing filename re-check.
Fixed
- Serial console bridge: a stalled telnet peer can no longer wedge server shutdown / port restart. The dedicated serial-reader thread used an unbounded
blocking_sendonto a bounded channel; when a bridged peer stopped reading and the channel filled, the thread parked past its shutdown/restart checks. It now polls withtry_send+ a short sleep, bailing on shutdown/restart or when the async pump drops its receiver. - Serial modem online mode (TCP): a remote host that stops reading no longer blocks shutdown.
online_mode_tcpset only a read timeout, so a full remote receive window parkedwrite_allindefinitely with the loop's shutdown/restart checks unreachable. A 5 s write timeout is now set (matching the duplex path); an expiry drops carrier (NO CARRIER). - XMODEM/YMODEM: YMODEM block 0 is now always validated as CRC-16. If block 0 took enough retries to cross the negotiation's CRC→checksum fallback point, the block-0 body (and then the data phase) could be misread as a 1-byte checksum, NAK-looping a CRC-only YMODEM sender to exhaustion. The block-0 read and the post-block-0 data phase are now pinned to CRC-16.
- Logging survives a poisoned lock.
loggernow recovers a poisoned mutex (into_inner) instead of silently dropping the line — matchingconfig.rs/gui.rs, and most valuable exactly when a thread has just panicked. - Kermit streaming: a sequence-aliased NAK now aborts cleanly instead of silently corrupting the file. In streaming mode the whole file sits in the sender's outstanding-packet set with wrapping (mod-64) sequence numbers, so a file larger than ~64 chunks aliases each seq across many packets. On a genuine mid-stream NAK/loss the sender matched the NAKed seq to the first (oldest) outstanding packet sharing it and retransmitted that stale packet; the receiver appends D-packets by sequence with no position field, so it landed the wrong data at the wrong offset. This was benign on lossless TCP/SSH (streaming's intended transport, where NAKs don't occur) and only reachable on an unreliable link such as a serial bridge. An unresolvable NAK now aborts with an actionable error ("disable
kermit_streamingfor this peer"); the timeout-driven retransmit path skips aliased seqs for the same reason. The reliable-transport happy path is unchanged. - ZMODEM:
ZFERR(0x0C) is now handled instead of ignored. A sender's file read/write-error frame aborts the receive cleanly with an informative error rather than falling through to the ignore arm and waiting out a frame timeout. Every Forsberg 1988 frame is now handled. - Text-mode web browser: fixed a remote-triggerable panic on Back. Returning to a previous page whose re-fetched content is shorter than the saved scroll position could index past the page and panic the session task. The scroll position is now clamped on restore and again defensively at render time.
Documentation
- Documented ZMODEM
ZCOMMAND(frame 0x12) as the one optional spec frame deliberately not implemented — it is recognized but always refused (non-zeroZCOMPL), since arbitrary/bin/sh -cexecution on a shared, long-lived host is an unacceptable default; use SSH for shell access. Noted in the user manual and the ZMODEM web reference. - Documented previously-undocumented config keys:
web_enabled,web_port,gateway_debug, andssh_gateway_authin the README config reference, andpunter_max_bad_rounds/punter_hangup_on_failurein the user manual. Added the now-handledZFERRframe to the ZMODEM web reference, and corrected the SSH reference'sauth_passwordlifecycle description to match the new claim-slot-on-successful-login behavior. - README config-reference completeness pass: the "All options"
egateway.confsample now listsdisable_ip_safetyand the per-portserial_a_petscii_translate/serial_b_petscii_translatekeys (all three are written by the config saver), the telnet Server-Configuration menu walkthrough documents the new session-cap / idle-timeout keys, and the Other Settings list now includes the gateway debug-trace toggle.
ethernet-gateway v0.6.1
Added
- Raspberry Pi 4+ (aarch64 Linux) build — releases now ship an
Ethernet_Gateway-aarch64.AppImagealongside the existing
x86_64 Linux / Windows / macOS artifacts, built on a native arm64
runner. Two ARM-only desktop-GUI fixes make it run on the Pi's
VideoCore/V3D GPU: the wgpu device now requests exactly the limits
the adapter advertises (so startup no longer aborts with
"Limit 'max_color_attachments' value 8 is better than allowed 4" or
the equivalent for other limits), and the GUI prefers the OpenGL ES
backend instead of the Pi's incomplete Vulkan driver (which panicked
with "Requested feature is not available on this device").
WGPU_BACKENDstill overrides. Other platforms are unaffected. - Punter (C1) file-transfer protocol — the protocol CCGMS /
Novaterm / StrikeTerm speak natively on Commodore BBSes, added
alongside XMODEM/YMODEM/ZMODEM/Kermit. Single-file C1 with the full
two-phase (file-type then data) handshake, both block checksums
(16-bit additive + cyclic), the "size of next block" framing, and
the three-S/Bend-off real C1 endpoints expect. Selectable in the
telnet upload/download protocol pickers; the outbound PRG/SEQ file
type is auto-detected from the filename. Newpunter_*tunables
(block size, timeouts, retries) are editable from the telnet File
Transfer settings menu, the web configuration page, and the desktop
GUI, and persist toegateway.conf. The send/receive entry points
take an open stream so a future Multi-Punter (MPP) batch wrapper can
layer on without touching the wire code. - Serial modem
AT+PETSCII=ncommand — toggles PETSCII⇄ASCII
translation on direct-TCP dials (AT+PETSCII=1on,AT+PETSCII=0
off) so a Commodore 64/PET dialingATDT host:portsees readable
text instead of raw ASCII. Set-only, in the ITU-T V.250+
extension namespace (&Pis the pulse-dial make/break ratio on real
Hayes modems, so it is intentionally left alone).AT+PETSCII=1
persists the setting immediately;AT&Vreports it as+PETSCII:n. - PETSCII translation is now editable from every configuration
surface — the per-port modem screen in the telnet/serial-console
menu, the web configuration page, and the desktop GUI — in addition
to the AT command. It is a per-serial-port setting saved to
egateway.conf. - Serial: inbound PETSCII punctuation normalizer, and the C64 PETSCII
DEL key (0x14, INST/DEL) is accepted as a command-line backspace
when PETSCII translation is active.+++escape sequences are
traced when the gateway debug trace is on. - Persisted
gateway_debugbyte-trace flag, toggleable from the
GUI/web General frame and the telnet Other Settings / Serial
Configuration menus. Read fresh per gateway session (no restart
needed);EGATEWAY_GATEWAY_DEBUGstill forces it on. The trace now
timestamps each input byte. - Web protocol reference pages served by the configuration web
server — per-protocol references (XMODEM, YMODEM, ZMODEM, Kermit, the
Hayes AT command set, and telnet), each documenting that protocol's
retry/recovery behavior, plus character-set and ANSI escape-sequence
references, reachable from a new References nav entry. - Kermit resume and locking-shift settings are now editable from
the telnet Kermit settings menu, the web configuration page, and the
desktop GUI (previouslyegateway.conf-only). punter_hangup_on_failure— optional drop-carrier-on-give-up for
Punter, editable from the telnet / web / GUI Punter settings. Because
C1 has no in-band abort, a give-up otherwise leaves the C64 hung;
enabling this drops carrier so it sees loss-of-carrier instead.
Fixed
- AI chat: a follow-up question that merely starts with a menu command
letter (e.g. "Quantum…") is no longer swallowed by the answer-screen
navigation. A lone command letter still navigates; any longer line
is sent to the model. - Transfer retry/recovery brought to strict spec. XMODEM/YMODEM now
NAK on a data-phase inter-block timeout (re-prompting the sender) and
cancel with CAN×3 on a non-duplicate block-sequence error instead of
NAK-looping; ZMODEM routes every data-phase error through one bounded
counter that re-sends ZRPOS and resets on progress (no infinite ZRPOS
loop on a permanently-corrupt stream); Kermit emits an Error packet
when it gives up so the peer is told rather than left waiting. - Punter no longer strands a peer on a failed transfer. A cancel /
restart from the C64 side is tolerated (longer pre-transfer input
drain), and corrupt-block recovery is bounded by its own larger round
cap rather than quitting early and leaving the peer hung.
Changed
- Removed the duplicate Port A/B status banner from the main
configuration menu — per-port mode is already shown under Serial
Configuration. - Punter bad-block cap decoupled —
punter_max_bad_rounds(default
30) bounds consecutive corrupt-block resend rounds separately from
punter_max_retries, since a real C64 peer never caps resends and a
low shared cap made the gateway give up first and strand it.
Security
- Updated
russh0.60.2 → 0.60.3 to clear two high-severity
(CVSS 7.5) allocation-DoS advisories in the SSH stack:
RUSTSEC-2026-0154 (russhunbounded 32-bit allocation) and
RUSTSEC-2026-0153 (russh-cryptovecuncheckedCryptoVec
allocation/growth). A malicious SSH client could otherwise drive
unbounded memory allocation on the SSH listener.
ethernet-gateway v0.6.0
Added
Configuration web server
- Optional HTTP listener that renders the same settings page the
desktop GUI does, in a browser. Off by default; toggle in the GUI
Server frame (new "Web Server" row between Telnet and Kermit) or
the telnetConfiguration > Server Configurationmenu's
W/Bkeys. Port defaults to 8080. - Hand-rolled HTTP/1.1 on tokio (no new dependencies) implementing
GET /(settings page),GET /logo.png(the same logo the GUI
uses),GET /logs(2-second polled log tail),GET /serial-ports
(live device enumeration for the dropdown refresh), and
POST /save(config persist + optional restart). - Per-frame Save buttons matching the GUI's three behaviors:
Server's Save and Restart (full server restart cycles through
main.rsexactly the way the GUI does), Serial's Save (just
reloads serial managers viaserial::restart_all_serial), and the
plain Save on every other frame (persist only). Unknown action
values fall back to plain Save so a hand-crafted POST with a typo
can't accidentally restart the server. - POST → 303 See Other → GET pattern: the save handler redirects
to/?notice=Configuration%20saved.so a browser reload after
submit doesn't resubmit the form. Client-side
history.replaceStatestrips the?notice=query right after
render so the banner appears once per save instead of persisting
across refreshes. - Serial-port dropdown + refresh button populated server-side
fromserialport::available_ports()(the same source the GUI
ComboBox uses); a small ↻ button next to each port re-scans via
GET /serial-portsand rewrites both selects' options in-place
without a full page reload. Operator's selection is preserved
across refreshes, and a saved port that isn't currently detected
stays visible with a(saved)suffix. - CSS Grid Server-frame layout so the two
Port:colons in each
column line up across rows; per-port inputs sized to 6 chars (any
valid TCP port fits) so the More button fits on row 1 alongside
Telnet + Web Server. - JS modal popups for the More views, plus inline confirmation
dialogs that warn before disabling the web server or changing the
web port — both actions break the operator's current connection. - Connection-breaking notice included in the post-save banner
when the operator's just-confirmed change will sever the browser
session (e.g. "Web server port changed to 9090. Reconnect at the
new port.").
Web auth and lockout
- HTTP Basic Auth gated on the same
security_enabledflag that
guards telnet. Uses the project's existing length-leak-resistant
constant_time_eqfromtelnet.rs. - Shared brute-force lockout map with telnet and SSH. Three
failures across any of the three protocols trip a 5-minute IP ban
(the sameLockoutMapthe telnet listener uses); failed web
attempts respond with429 Too Many Requests+Retry-After: 300
once the threshold is crossed. The 429 fires before the auth
check on every subsequent request, so a banned IP can't keep us
busy parsing malformed POSTs either. - Same IP-safety allowlist as telnet: when login is not required
anddisable_ip_safetyis off, only private / loopback /
link-local source IPs are accepted (and*.*.*.1gateway
addresses are rejected).
Web defense-in-depth
- 30-second read timeout on
read_requestto stop slow-loris clients
from parking a tokio task indefinitely. MAX_INFLIGHT = 16concurrent connections with aDrop-guarded
slot release; excess connections get a503 Service Unavailable+
Retry-After: 5rather than being parked behind the read timeout.- 16 KB cap on request headers, 64 KB cap on POST body — bounded so
a hostile peer can't drive the per-connection buffer to OOM. - UTF-8 round-trip safe:
url_decodeaccumulates percent-decoded
bytes into aVec<u8>then runsfrom_utf8_lossy, so values like
weather_zip = 日本語survive the form → config-file → form
cycle without corruption.
Changed
Unified telnet / SSH / web credentials
- One username / password pair now covers the telnet menu, the
SSH server, and the web configuration UI. The old per-protocol
ssh_username/ssh_passwordconfig keys are gone. Defaults
unchanged atadmin/changeme. - One-time migration: if the operator's
egateway.confstill has
non-defaultssh_username/ssh_passwordvalues and the
unifiedusername/passwordare still at the factory defaults,
the legacy SSH values are adopted into the unified pair on load
(with aNote: migrating legacy ssh_username=…log line). Once
the next save runs, the legacy keys disappear from the written
file. If both pairs were already customized, the unified pair
wins (the legacy SSH values are silently dropped). - GUI Security frame collapses from two rows (separate Telnet /
SSH credential rows) to oneLogin User / Passrow + a spacer
that keeps the frame the same height as the adjacent Server frame. - Telnet Security menu drops the
S(Set SSH username) /
W(Set SSH password) items; the remainingU/Pitems now
readSet username/Set password(no more "telnet"
qualifier). Status shows a singleUsername:/Password:
pair instead of two. - Help screens under
Configuration > Securityand
Configuration > Server Configurationupdated: the security
help notes "One username/password covers telnet, SSH, and the
web UI" and the server help describes the newW(Toggle Web) /
B(Set Web port) keys.
GUI Server frame
- Fixed-width listener column slots so the two
Port:colons line
up between rows — the same colon-alignment the web frame gets
from CSS Grid. The earlier hand-tunedadd_space(16.0)left the
colons at different X positions because "Telnet" / "SSH" and
"Web Server" / "Kermit Server" have different intrinsic widths. - More button moved up to row 1 (with Telnet + Web Server),
mirroring the web layout.
GUI Serial Ports frame (web-side parity adjustments)
- Web Serial frame's header now carries both ports' Enabled
checkboxes alongside per-port titles ("Serial Port A" / "Serial
Port B"), matching the GUI's layout exactly. Per-port rows are
nowPort X: [select ▼] [↻] Baud: [...] [More...]with the More
button kept on the same line via a no-wrap row class.
Logger
- Added a parallel non-draining
snapshot(max)API alongside the
existingdrain(). The GUI keeps usingdrain()for its
per-frame console accumulator; the web/logsendpoint polls
snapshot()so the two views don't compete for log lines.
ethernet-gateway v0.5.5
Added
Dual serial-port support
- Two physically independent serial ports —
Port AandPort B—
each with its own enabled flag, mode (modem emulator or telnet-serial
console), device path, baud, AT/S-register state, and stored
phone-number slots. The two ports run in separate manager threads,
persist AT&W state separately, and host independent console-bridge
slots, so the operator can run a Hayes modem on one wire and a
telnet-serial bridge on the other (or any other mix) without
cross-talk. - A/B picker submenus — the
Configuration > Mentry is now
Serial Configuration and opens a picker listing both ports' status;
selecting a port drops into that port's settings. The main-menu
Serial Gateway (G) likewise opens an A/B picker before bridging,
showing both ports' status (ineligible ports are dimmed) so the user
can see why a port isn't available. - Per-port mode toggle moved from the Configuration menu to the
per-port settings menu (T item). Hidden from sessions that arrived
over a serial port itself, since flipping that port to console mode
would tear down the caller's own connection before they could
acknowledge. - GUI Serial Port frame redesigned: header row carries both ports'
Enabled checkboxes and a shared Save button; one row per port
beneath with a device-path dropdown, baud field, and per-port
More… button into an advanced popup (mode, framing, flow, full
Hayes AT state). Both popups are independent so settings can be
compared side-by-side.
Changed
- Config schema split into per-port keys: every former
serial_*
key is nowserial_a_*orserial_b_*. Legacy single-port configs
auto-migrate into Port A on first read; the next save rewrites the
file in dual-port form. Existing single-port deployments upgrade
transparently with Port B disabled by default. - Serial Gateway main-menu visibility — now requires at least one
port to be in console mode (so the menu can't dead-end at an empty
picker). - Dialup mapping stays a single shared
dialup.confconsulted by
both ports' modems — phone-number lookups are global, not per-port. - Documentation refreshed end-to-end (
README.md,
usermanual.html,index.html) for the dual-port architecture,
including config-key tables, GUI screenshots/descriptions, and the
Console Mode walkthrough. ATI0/ATI3identification strings now advertise the modem as
Hayes-compatible, matching the behavior callers (BBS dialers, vintage
terminal software) expect from a Hayes ID query.
Fixed
- PETSCII width compliance in the new pickers and per-port menu
titles: replaced em-dashes with ASCII hyphens and switched the
picker layout to two lines per port (role label + device/baud) so
worst-case lines fit the 40-col PETSCII budget. - Stale help text in
console_show_helpthat told users to
"Press T at the Configuration menu" — T moved into the per-port
settings menu.
Security
- AI-chat output sanitization — replies from the Groq API are now
normalized (\r\n/\r→\n) and passed through a
sanitize_for_terminalfilter before display, stripping ANSI escape
sequences, control bytes, lone CRs, and telnet IAC so a prompt-injected
reply can't smuggle terminal-control payloads through the chat surface. - Auth-lockout map bounded —
record_auth_failurenow sweeps entries
past the lockout window on every call, so a long-running public-facing
instance can no longer accumulate one entry per distinct attacker IP
indefinitely.
ethernet-gateway v0.5.4
Added
Serial Console Mode
- Telnet-serial bridge as a second role for the serial port,
alongside the existing Hayes AT modem emulator. Selectable via the
newserial_modeconfig key (modem/console). The existing
G Serial Gatewaymain-menu item now bridges the telnet/SSH session
straight to the wire so an operator can drive a microcontroller,
RS-232 device, or other serial console remotely. - Hot mode switch — flipping
serial_mode(from the GUI dropdown,
the newT Toggle Modem/Console modeentry on the Configuration
menu, oregateway.confdirectly) reconfigures the running serial
thread within one manager-poll interval. No restart required.
Changed
- Configuration menu reorganized to surface the new mode toggle and
to relabelM Modem Emulator↔M Serial Consolebased on
currentserial_mode. The new menu walkthrough is documented in
user-manual §5.6. - Documentation pass: §3.2 of the user manual gained 22 previously
undocumented config keys (the fullkermit_*family,
ssh_gateway_auth,disable_ip_safety,allow_atdt_kermit,
kermit_server_enabled/_port);index.htmlgrew a Kermit
subsection in the file-transfer config tables and added cross-links
tokermit.htmlfrom each protocol-prompt step; the chapter-8 intro
now correctly describes five protocols (the old "three related
protocols" framing predated the ZMODEM and Kermit chapters).
Fixed
Console bridge hardening
run_console_bridgecould wedge indefinitely when the telnet
peer's TCP write buffer was full: the spawned async task's
duplex_write.write_all().awaitwould park with no wake-up source,
stranding the manager thread until process restart. Bounded with a
200 ms timeout thenabort().- Orphaned bridge requests on serial-mode flip: a request that
arrived in the slot just beforeSERIAL_RESTARTfired could be
silently abandoned becauseconsole_manager_tickreturned without
polling the slot, leaving the requester'srx.awaitblocked forever.
Slot is now drained withErr("Serial mode changed")on every exit
path. - TOCTOU between request-eligibility check and slot insert:
request_console_bridgenow re-checks
check_console_bridge_eligibleunder the slot lock so an operator
flippingserial_mode(or disabling serial, or clearing the port
path) and callingrestart_serial()in the narrow window between
the fast-path check and the slot insert can no longer leave a
request stuck until shutdown. - Unbounded
session_to_portchannel replaced with a bounded
tokio::sync::mpsc::channel(64); a flow-controlled wire (CTS-low,
slow peer) plus a fast typist or paste can no longer balloon
in-memory queue depth. The async-side.send().awaitnow
backpressuresduplex_read, which backpressures the telnet peer. - Slot-cleanup duplication removed from the
Err(_)arm of
rx.await; letConsoleSlotGuard's drop own slot teardown.
Serial mode switch responsiveness
- Modem online loops (
online_mode_tcp,online_mode_duplex) now
honorSERIAL_RESTARTon every iteration; previously a mode flip
could lag by one block-read interval before the loop noticed.
Menu UX & doc-vs-code drift
G Serial GatewayandT Toggle Modem/Console modeare
now hidden from sessions that arrived over the serial port itself.
The handler-side rejections remain as defense in depth (a serial-side
caller can still type the letter blind), but the menu no longer
advertises items that always error.- Manual cross-references to "chapter 9.10" corrected to "9.13"
(Console Mode lives at 9.13; 9.10 is Chained Command Lines). AT&K1redescribed as Auto-detect (stored, no wire effect)
instead of "Reserved"; the parser atsrc/serial.rs:1140accepts
&K1and emitsFlowSet(1). Missing&K1row added to Appendix
B.4.AT&Fentry now notes that it drops the active connection,
matching theAtResult::Resetreturn.- Bare
kermitalias forATDT KERMITdocumented alongside the
existingkermit-server/kermit serveraliases.