Subj : docs/v322_new.md src/sbbs3/zmodem.c To : Git commit to main/sbbs/master From : Rob Swindell (on Debian Linux) Date : Tue Aug 25 2026 01:12:30 https://gitlab.synchro.net/main/sbbs/-/commit/fd038d9dd3cba68b4d95d026 Modified Files: docs/v322_new.md src/sbbs3/zmodem.c Log Message: zmodem: escape CR unconditionally when ESCCTL is negotiated A sender with ESCCTL negotiated escaped every control character except carriage return, which it routed to a conditional rule -- escape only when the previous byte sent was '@' -- and otherwise put on the wire raw. A receiver that requested ESCCTL discards unescaped control characters by definition, so it discarded those CRs, every subpacket carrying one failed its CRC, and the transfer never advanced: the sender ZRPOSed to zero until it spent its error budget and gave up. With random data that is any subpacket at all. A download to any receiver requesting ESCCTL was therefore impossible, not merely slow. lrzsz settles what the rule should be. In zsendline_init() (zm.c) the table entry for 015/0215 is: if (Zctlesc) tab = 1; /* always escape */ else if (!turbo_escape) tab = 2; /* the conditional '@' rule */ else tab = 0; So the conditional rule is the NON-ESCCTL case, and ESCCTL escapes CR like any other control character. This engine had the two swapped: the conditional rule was reachable only with ESCCTL on, where it is wrong, and never reachable with ESCCTL off, where it would be right. Since it could not fire correctly in either state, drop it -- fold CR into the control class and let the escape mask decide, which is exactly lrzsz's behaviour for both states. The '@' rule can be added properly later if wanted; the last_sent field it read is left in place, being public. The unescaped path is provably untouched: without ESCCTL neither the old CR class nor the new one is in the active mask, so CR is a normal byte either way. Confirmed byte-for-byte -- a 4 MB send still puts 4,311,816 bytes on the wire, 256 MB still receives at 484.99 MB/s and sends at 206.24 MB/s, and the 3e-6 corruption gate still passes 5 of 5 receiving and 3 of 3 sending. With the fix, ESCCTL interoperates in every direction: sexyz to lrz, lrz to sexyz, and sexyz to itself all verify byte-identical. The strongest evidence it is now correct rather than merely accepted is the wire size -- sexyz puts 5,245,034 bytes on the wire where lsz puts 5,245,030 for the same file, so the two escape the same byte set. Random data exercises every one of the 256 values, so nothing is escaped by luck. Under injected errors the escaped path is a little less robust than the plain one: 4 of 5 at 3e-6 and 5 of 5 at 1e-6, against 5 of 5 for the unescaped path at 3e-6. It carries 25 % more bytes and so takes 25 % more corruption at a given per-byte rate. No lrzsz baseline exists for that comparison -- lsz -e to lrz -e does not survive the harness's error injection at all, dying inside 0.04 s on every attempt. --- SBBSecho 3.37-Linux * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705) .