Two real interop bug fixes plus a transport-layer refactor that adds
serial port / pty / pipe support without changing the protocol engines.
Surfaced live against Xenia (TCP loopback, 10 MB bidir tests) and
verified against a USR Courier V.34 vmodem on /dev/vserial3.
Fixes
-----
* Transport partial-send dropped tail bytes. TFXTcpTransport.Send
was a thin fpSend wrapper that returned the raw call result; on
a non-blocking socket fpSend can return less than Len when the
kernel send buffer is partially full. All nine protocol layers
(Janus / Hydra / Nova / ZModem / SEAlink / EMSI / WaZOO /
FTS-1 / XModem) call Send once per logical write and assume
blocking-write semantics. Tail bytes were silently dropped,
leaving truncated packets on the wire and triggering CRC/RPOS
storms at the peer. TFXFdTransport.Send now loops internally
on partial writes, mirroring btxe's SENDCHARS (asyn_lnx.c:278).
* Janus RPOS handler diverged from btxe in three places (janus.c:
649-697 RPOSPKT). Side-by-side review against the reference:
- dedup gate covered only block-size reset, not seek + retry
clear -> duplicate RPOS re-seeked and dropped XmitRetry
- missing LastTx := TxPos (btxe: lasttx = txpos = unpack(rxbuf))
- block-size reduction hard-coded to 64 (btxe: txblklen >>= 2;
if < 64 then 64 -- gradual back-off)
- missing CLEAR_OUTBOUND() equivalent. Cannot revoke TCP
in-flight bytes but can clear the in-process FOutBuf so we
don't ADD more bytes that would arrive as stale-position
blocks after the resync.
Combined with the transport partial-send fix, Janus 10 MB
Fimail<->Xenia bidir goes from "6 RPOS retries in 175 s" to
"zero retries, 167 s".
Added
-----
* TFXFdTransport (new unit fx.transport.fd) -- single fd-based
IFXTransport implementation handling TCP, serial, pty, pipe
uniformly through POSIX read/write/select. Three constructors:
CreateClient(host, port) -- outbound TCP
CreateFromFd(fd, peer, IsSocket) -- wrap any open fd
CreateSerial(device, baud, peer) -- termios raw 8N1 + RTS/CTS
Verified against /dev/vserial3 running USR Courier V.34
emulation: full Hayes AT command set round-trips correctly.
* DCD / DTR modem control on serial fds. IsConnected polls
TIOCMGET & TIOCM_CAR (mirrors btxe com_online, asyn_lnx.c:235)
-- read-only, never mutates FClosed, so DCD bouncing up at
ATA/CONNECT works. Close + ShutdownWrite clear TIOCM_DTR
before closing the fd; modems disconnect when DTR drops, the
byte-for-byte equivalent of +++ATH.
Changed
-------
* fx.transport.tcp is now a 13-line type-alias shim: TFXTcpTransport
= TFXFdTransport. Existing imports keep working without edits;
new code should import fx.transport.fd directly.
fpc-filexfer
Free Pascal library of FidoNet-era file-transfer engines. Sibling
to fpc-binkp in the fpc-* library ecosystem: fpc-binkp handles
the BinkP mailer protocol; fpc-filexfer handles everything that
came before.
Currently at 0.2.0 (April 2026).
What's in it
| Unit | Protocol | Spec |
|---|---|---|
fx.xmodem |
XModem / XModem-CRC / XModem-1K / YModem / YModem-G | Christensen/Forsberg |
fx.sealink |
SEAlink sliding-window | FSC-0019 |
fx.zmodem |
ZModem / ZedZap / DirectZap | FTS-0018 / FTS-0020 |
fx.hydra |
Hydra bidirectional | FTS-0057 |
fx.janus |
Janus bidirectional | BinkleyTerm XE tradition |
fx.nova |
Nova bidirectional with SHA-256 | Xenia Mailer (Ken Johnson) |
Framework:
fx.transport—IFXTransportinterface (non-blocking byte stream). Shape matchesfpc-binkp'sIBPTransportwith an addedFlushfor the modem-era byte-framing engines. A consumer can back both libraries onto one socket implementation via a thin adapter.fx.provider—IFXFileProviderinterface (pluggable file I/O). Mirrorsfpc-binkp'sIBPFileProviderownership contract:NextOutbound/AcknowledgeSent/AcknowledgeFailedfor TX;OpenForReceive/FinalizeReceive/CleanupReceivefor RX.fx.provider.fs— reference local-filesystem provider.fx.transport.tcp— reference UNIX TCP transport (non-UNIX consumers plug their own Watt-32 / WinSock / OS2 equivalent).fx.events— progress + session-end callback signatures.fx.crc— CRC-16 CCITT, CRC-16 SEAlink, CRC-16 reflected (Hydra), CRC-32 (ZModem-32 / Hydra / Nova).fx.sha— SHA-256 for Nova's content-addressed transfer verification.
TLogProc from fpc-log is used throughout; there is no per-library
log shape.
Using it
Protocol engines are session classes; each has a Run, SendBatch, or
ReceiveBatch entry point that takes a transport and a provider:
uses fx.types, fx.transport.tcp, fx.provider.fs, fx.sealink;
var
Transport: IFXTransport;
Provider: IFXFileProvider;
Session: TFXSEAlinkSession;
Result: TFXResult;
begin
Transport := TFXTcpTransport.CreateClient('peer.example', 24554);
Provider := TFXFsProvider.Create('/inbound', '/tmp/fx');
Session := TFXSEAlinkSession.Create;
try
Result := Session.Run(Transport, Provider, @MyLogProc,
{IsOriginator=}True);
if not Result.Success then
Writeln('transfer failed: ', Result.ErrorMessage);
finally
Session.Free;
end;
end.
See fx.zmodem.pas / fx.hydra.pas / fx.janus.pas / fx.nova.pas
for the equivalent entry points on the other engines.
Building
./build.sh # builds all supported targets
./build.sh x86_64-linux # single target
Supported targets: x86_64-linux, x86_64-freebsd, x86_64-win64,
i386-linux, i386-freebsd, i386-go32v2, i386-os2, i386-win32
(8 total). The reference TCP transport (fx.transport.tcp) is
UNIX-only; other targets get the library minus that unit.
Dependencies:
- FPC 3.2.2+
fpc-log>= 0.1.0 (pulled via sibling directory../fpc-log)paszlib(FPC RTL) forfx.nova's per-block ZLIB compression- Nothing else. No external crypto library — SHA-256 is in-tree.
Testing
The in-tree tests/ harness ships a smoke test (test_crc) run
via ./run_tests.sh. Cross-library parity against the Fimail
sources is verified externally; downstream consumers should
pin by tag and run their own integration harness.
The 0.2.0 release was a full re-extraction of all six protocol
engines from the pre-extraction Fimail sources after the v0.1.x
mechanical carve-out was discovered to have silently dropped
30–50 % of protocol code per file. See CHANGELOG.md for the
per-protocol breakdown and source-of-truth references.
Ecosystem
fpc-filexfer is part of a family of Free Pascal libraries for FidoNet-related work:
fpc-log— log interface (TLogProc)fpc-msgbase— message-base readers/writersfpc-ftn-transport— PKT / BSO / bundles / TICfpc-binkp— BinkP mailerfpc-comet— Comet native mailerfpc-filexfer— this library — pre-BinkP transfer enginesfpc-emsi— EMSI / WaZOO / FTS-0001 session handshakes
fpc-emsi sits above fpc-filexfer: once its dispatcher has
identified which transfer engine the peer wants, the caller hands
a matching fx.* session off to run the transfer.
Licence
Translations of protocol code (sealink, zmodem, hydra, janus) are line-for-line ports of public-domain or GPL implementations (Xenia Mailer, BinkleyTerm XE). Nova is original. See each unit's header for attribution.
This library is Free Pascal code. Use it.