Ken Johnson a085a8b47a v0.3.0: TFXFdTransport + partial-send fix + Janus RPOS handler parity
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.
2026-04-27 11:57:18 -07:00
2026-04-21 16:05:14 -07:00

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.transportIFXTransport interface (non-blocking byte stream). Shape matches fpc-binkp's IBPTransport with an added Flush for the modem-era byte-framing engines. A consumer can back both libraries onto one socket implementation via a thin adapter.
  • fx.providerIFXFileProvider interface (pluggable file I/O). Mirrors fpc-binkp's IBPFileProvider ownership contract: NextOutbound / AcknowledgeSent / AcknowledgeFailed for TX; OpenForReceive / FinalizeReceive / CleanupReceive for 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) for fx.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 3050 % 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/writers
  • fpc-ftn-transport — PKT / BSO / bundles / TIC
  • fpc-binkp — BinkP mailer
  • fpc-comet — Comet native mailer
  • fpc-filexferthis library — pre-BinkP transfer engines
  • fpc-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.

Description
Free Pascal file-transfer protocols: XModem/YModem, ZModem, SEAlink, Hydra, Janus, Nova. Transport-abstracted via IFXTransport.
Readme 1.9 MiB
Languages
Pascal 98.5%
Shell 1.5%