Add public read-only TBPSession.HasPendingTx returning True iff
the session has outbound work pending right now (bytes already
in the send buffer, queued messages awaiting flatten, or an
active TX file with more data). Lets driver loops choose
whether Transport.WaitReady should also wait for socket
WRITE-readiness in addition to read.
Recommended pattern:
while Session.NextStep do
Transport.WaitReady(True, Session.HasPendingTx, 50);
Matches Argus's CanSend socket-buffer pacing model
(p_binkp.pas:1129-1131). Previous fpc-binkp drivers
unintentionally followed binkd's poll-with-timeout model and
paid a 50 ms idle wait per frame.
Live measurement on a 10 MB BinkP outbound (Comet 1.2.2 daemon
driving fpc-binkp against a Comet 1.2.2 BinkP-fallback
responder) before/after wiring the new flag:
bbsnode2 (FreeBSD/LAN): 134 s -> 2.7 s (~50x)
sea-vps11 (Linux/WAN): 134 s -> 1.7 s (~80x)
Wire protocol unchanged. Consumers pinned to 0.3.0 can
upgrade in place; HasPendingTx is purely additive and callers
that don't use it keep the old behaviour.
BP_MIN_COMPATIBLE_VERSION stays at 0.2.0.
fpc-binkp
A Free Pascal library for the BinkP/1.1 mailer protocol (FTS-1026, FTS-1027) with support for the widely-deployed vendor extensions (NR, ND, MBT, PLZ, CRYPT, DES-CBC, ED25519).
Sibling library to fpc-msgbase (message
storage), fpc-ftn-transport (PKT,
ArcMail, queues), and comet (the mailer daemon
that consumes all three). Same vendoring model: each library
is its own repo with its own tests; consumers cp / rsync
units from units/ into their build; bug fix → commit here →
consumers pull at their own cadence → rebuild → deploy.
Scope
| Concern | Owned by |
|---|---|
| BinkP/1.1 wire protocol (frames, commands, state machine) | fpc-binkp |
| CRAM-MD5, CRYPT, DES-CBC, ED25519 auth | fpc-binkp |
| PLZ (zlib per-frame) compression | fpc-binkp |
| NR (resume), ND (non-destructive), MBT (multi-batch) | fpc-binkp |
| PKT / ArcMail / BSO queue | fpc-ftn-transport |
| Message storage (JAM, Squish, ...) | fpc-msgbase |
| Session dispatch, config, UI, logging backends | consumer |
| Dialup / EMSI / modem framing | out of scope — separate library |
The library handles the wire. Consumers own everything above and below: the socket, the filesystem, the logging sink, the decision of which file to send next, what "secure" means for an inbound directory.
Status
Current: 0.1.0 — initial release. Core library compiles on all five targets (Linux, FreeBSD, Windows, OS/2, DOS go32v2). Reference TCP transport is UNIX-only; DOS has a dedicated Watt-32 transport; Windows / OS/2 consumers plug in their own IBPTransport.
Seven test programs green including a full two-session round-trip self-test. End-to-end smoke-tested on localhost (outbound ↔ inbound, 10 KB binary transferred with identical SHA-256, full CRAM-MD5 + CRYPT + MBT).
Naming
Unit prefix: bp.<category>.<name>.pas — bp = "binkp".
All units use {$mode objfpc}{$H+}{$modeswitch advancedrecords}.
Architecture
Step-based engine (modeled on Argus, not binkd):
Session := TBPSession.Create(bpsOutbound, Transport, Provider, Events);
Session.Config.LocalAddress := FTNAddr('1:218/720');
Session.Config.Advertise := [bpOptNR, bpOptND, bpOptPLZ, bpOptMBT];
Session.Config.OnPostAuth := @MyPostAuthHook;
while Session.NextStep do
begin
// embedder is in control between every tick.
// can cancel, inspect state, swap provider, etc.
end;
writeln('Result: ', Session.Result.Description);
Session.Free;
The embedder drives the loop. Each call to NextStep does a
bounded unit of I/O + state-machine work and returns. This
gives:
- Testability. Replace
Transportwith aTMemoryStream- backed mock; feed in canned bytes; assert on emitted frames. No sockets required. - Cancellation. Stop calling
NextStep; free the session. - Multiplexing. An event loop can drive N sessions on a
single thread if the
Transportimplementation supports non-blocking I/O. - Observability. Inspect
Session.Phase,Session.RemoteInfobetween ticks. Hooks fire at every policy decision.
See docs/architecture.md for the full
design, docs/api.md for the consumer-facing
API reference, and docs/spec.md for the
FTSC / FSP spec cheat sheet and wire-format notes.
Building
Once units land:
./build.sh # all targets
./build.sh x86_64-linux # just one
./run_tests.sh # run test suite
Supported targets (matches fpc-ftn-transport):
x86_64-linux, i386-go32v2, i386-os2, i386-win32,
i386-linux, i386-freebsd.
License
GPL-2.0 — same as comet.
Layout
fpc-binkp/
├── build.sh — multi-target build driver
├── CHANGELOG.md — release notes, tagged vX.Y.Z
├── README.md — this file
├── run_tests.sh — test runner
├── fpc.cfg — FPC search paths
├── docs/
│ ├── architecture.md — engine design, hook taxonomy
│ ├── api.md — public API reference
│ └── spec.md — FTS-1026/1027 + FSP cheat sheet
├── examples/
│ ├── example_outbound.pas — embed as originator
│ ├── example_inbound.pas — embed as answerer
│ ├── example_mock.pas — replay a captured session
│ └── example_multiplex.pas — N sessions one thread
├── src/
│ ├── bp.version.pas — BP_VERSION constant
│ ├── bp.types.pas — enums, records, forward decls
│ ├── bp.frame.pas — TBPFrame, read/write helpers
│ ├── bp.opt.pas — OPT keyword parser / builder
│ ├── bp.cram.pas — CRAM-MD5 (HMAC-MD5 RFC 2104)
│ ├── bp.crypt.pas — FSP-1037 password-derived cipher
│ ├── bp.des.pas — Argus DES-CBC stream
│ ├── bp.plz.pas — zlib per-frame compression
│ ├── bp.transport.pas — IBPTransport interface
│ ├── bp.provider.pas — IBPFileProvider interface
│ ├── bp.events.pas — IBPEventSink, event records
│ ├── bp.config.pas — TBPSessionConfig record
│ └── bp.session.pas — TBPSession engine (NextStep)
├── tests/
│ ├── test_frame.pas
│ ├── test_cram.pas
│ ├── test_crypt.pas
│ ├── test_des.pas
│ ├── test_plz.pas
│ ├── test_opt.pas
│ ├── test_session_outbound.pas
│ ├── test_session_inbound.pas
│ ├── vectors/ — captured traffic, one file per case
│ └── testutil.pas — test harness helpers
└── units/ — compiled output (.ppu/.o), per target