• src/sbbs3/sexyz.c zmodem.c zmodem.h

    From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Mon Aug 24 22:46:27 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/d16135c1046c973abab5afad
    Modified Files:
    src/sbbs3/sexyz.c zmodem.c zmodem.h
    Log Message:
    zmodem: rebuild the plain-byte table when a receiver sets EscapeCtrlChars

    rx_plain_tab[] caches escape_ctrl_chars, and the commit that derived the receive fast path from it rebuilt the table in only two places:
    zmodem_init(), and zmodem_parse_zrinit(). A receiver never reaches the
    second one -- only a sender parses a received ZRINIT -- so on the receive
    side the table was whatever zmodem_init() built, with the flag still at
    its FALSE default.

    sexyz then assigns zm.escape_ctrl_chars directly from sexyz.ini's EscapeCtrlChars (sexyz.c), after zmodem_init() and with nothing to
    rebuild the table. With EscapeCtrlChars=true a receive therefore ran
    with a table that called control characters plain, and both new paths
    took them at face value: zmodem_rx() returned them verbatim and
    recv_span() copied them into the subpacket, where the old code dropped
    them as "unescaped ctrl char". That is the whole point of ESCCTL, and
    the config exists for exactly the links that need it.

    Rebuild in zmodem_send_zrinit(). A receive session always passes
    through it, the consumer's value is necessarily final there -- the
    ZRINIT built in that function is what announces ESCCTL to the peer --
    and it costs a 256-iteration loop once per session. Reproduced and
    verified with a harness that replays sexyz's init order: before, an escape_ctrl_chars set after zmodem_init() left rx_plain_tab[0x01]=1;
    after, send_zrinit() brings it to 0.

    Since escape_ctrl_chars is a public struct field any consumer may
    assign, say so where it is declared and list the three rebuild points in
    the table function's comment.

    Also stop bypassing recv_byte()'s DebugRx trace. The span path skips recv_byte() entirely, so with DebugRx=true the per-byte RX log lost
    every byte a span carried -- around 97 % of the stream on random data,
    leaving a trace of nothing but escapes. Disable the span path when
    debug_rx is set, the way it already is for telnet mode, on the same
    grounds: both are cases where recv_byte() has to see every byte. No
    cost when the flag is off.

    No throughput change: 484.4 MB/s against 484.2 before, receiver CPU
    0.55 s. Clean receives verify byte-identical 3 of 3 and the 3e-6
    corruption gate passes 5 of 5, sender gate 3 of 3.

    Found by reviewing the series after the sexyz 3.5 release, at Rob
    Swindell's request.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net