XMODEM Protocol Reference
A protocol-level walkthrough of how the gateway negotiates and moves data with the XMODEM family — the same code path serves XMODEM, XMODEM-1K, and YMODEM.
XMODEM (Ward Christensen, 1977) is the simplest of the file-transfer protocols the gateway speaks: a stop-and-wait, block-at-a-time scheme where the sender transmits one fixed-size block, waits for the receiver to acknowledge it, and only then sends the next. Every retro terminal program ever written understands it, which is why it is the lowest common denominator for moving files to and from a Commodore 64, a CP/M box, or an AltairDuino.
The gateway implements three members of the family on a single shared
send/receive engine (src/xmodem.rs):
| Variant | Block | Header byte | Adds |
|---|---|---|---|
| XMODEM | 128 bytes | SOH (0x01) | The 1977 original. |
| XMODEM-1K | 1024 bytes | STX (0x02) | 8× bigger blocks → less per-block overhead. |
| YMODEM | 1024 bytes | STX + a block 0 | A SOH “block 0” carrying filename, size, and mtime. |
Two things are negotiated at the start of every transfer and are worth keeping straight: the error-check method (CRC-16 or the older 8-bit checksum) and, on receive, the block size (decided per block from the header byte). Both are covered in detail below.
The handful of bytes that drive the handshake.
| Byte | Hex | Name | Meaning |
|---|---|---|---|
SOH | 0x01 | Start of Header | A 128-byte block follows. |
STX | 0x02 | Start of Text | A 1024-byte block follows. |
EOT | 0x04 | End of Transmission | No more data — the file is complete. |
ACK | 0x06 | Acknowledge | Block received intact; send the next one. |
NAK | 0x15 | Negative Acknowledge | Bad block (or “start in checksum mode”); resend. |
C | 0x43 | (ASCII ‘C’) | Receiver request: “start, and use CRC-16.” |
CAN | 0x18 | Cancel | Two in a row aborts the transfer. |
SUB | 0x1A | Substitute | Padding for the final short block. |
The original. Each block is announced with SOH and carries
exactly 128 bytes of payload. Small blocks mean a corrupted block is
cheap to resend, which is why it survives on noisy links, but the
per-block ACK round-trip caps throughput.
Identical framing, but each block is announced with STX and
carries 1024 bytes — eight times the payload for the same header and
one ACK round-trip, so it is markedly faster on a clean link. The two
sizes can be mixed within one transfer: the receiver
branches on the header byte (SOH vs STX) for
every block, so a sender may stream 1K blocks and drop to a 128-byte
block for a short final chunk.
XMODEM-1K plus metadata. Before the data, the sender transmits a
block 0 — an SOH block whose block
number is 0x00 (complement 0xFF) — whose
128-byte payload holds the filename, the exact file size, and optionally
the modification time, all NUL-separated. Because the receiver learns the
real size up front, it can truncate the file to the byte rather than
guessing where the padding starts — which is the only way to receive
a file that legitimately ends in 0x1A bytes (many
binaries do) without corruption.
Two ways to detect a corrupted block — negotiated by the receiver's very first byte.
| Checksum (1977) | CRC-16 (later) | |
|---|---|---|
| Trailer size | 1 byte | 2 bytes |
| Computation | Sum of the data bytes, mod 256 | CRC-16/CCITT, polynomial 0x1021 |
| Requested by | Receiver sends NAK (0x15) | Receiver sends C (0x43) |
| Catches | Most single-byte errors | Far more multi-byte / burst errors |
The choice is made entirely by which byte the receiver sends to
start the transfer. A C says “I speak CRC-16,
please use it”; a NAK says “fall back to the
plain checksum.” CRC is strongly preferred — it catches error
patterns a simple sum misses — so the gateway always asks
for CRC first and only drops to checksum for genuinely ancient senders
that never implemented it.
C request and just start sending checksum blocks anyway. On
the first block the gateway validates the trailer in the mode it
asked for and, if that fails, re-checks it under the other method. If the
alternate matches, it silently locks the whole session to that method
(pushing back the one stray trailer byte). So a CRC request that meets a
checksum sender still completes cleanly.
You send a file from your terminal to the gateway. The gateway is the receiver, so it drives the handshake.
From the File Transfer menu press U, type a filename, and choose X on the upload-protocol screen. The single X entry accepts plain XMODEM, XMODEM-1K, and YMODEM — the gateway auto-detects which one your terminal actually uses.
C (0x43) to request CRC-16, and repeats it on an interval (xmodem_negotiation_retry_interval, default 7 s) so a sender that starts late still hears it.xmodem_negotiation_timeout, default 45 s) elapses, the transfer proceeds in CRC mode.NAK (0x15) for the remainder — the checksum request — rescuing pre-CRC clients.STX (0x02) → this is a 1024-byte block (XMODEM-1K).SOH (0x01) with block number 0x00 and complement 0xFF → this is a YMODEM block 0. The gateway parses the filename + size, validates its CRC, sends ACK, then sends a second C to start the data phase.SOH (0x01) with any other block number → an ordinary 128-byte data block.ACK (0x06) and the sender advances to the next block.NAK (0x15) and the sender resends the same block, up to xmodem_max_retries (default 10) consecutive errors before it cancels.ACK'd again without being stored — this recovers from a lost ACK without duplicating data.CAN (0x18) bytes — the spec-mandated response.xmodem_block_timeout, default 20 s), the gateway NAKs to re-prompt the sender — which retransmits the block it's still awaiting an ACK for — bounded by the same retry count rather than abandoning the transfer on the first quiet moment.EOT (0x04); the gateway answers with a final ACK.C and consumes the end-of-batch “null block 0” (an all-zero filename) that signals “no more files,” and ACKs it.SUB (0x1A) padding is stripped.You pull a file from the gateway to your terminal. Here the gateway is the sender, so it waits for your terminal to drive the handshake.
Press D, pick the file from the numbered list, then choose the protocol. On download you select the variant explicitly, because the gateway must commit to a block size before it starts sending.
xmodem_negotiation_timeout, default 45 s) for your terminal to send its opening byte.C (0x43) → the gateway sends every trailer as a 2-byte CRC-16.NAK (0x15) → the gateway sends every trailer as a 1-byte checksum.SOH block, number 0x00, complement 0xFF, carrying the filename and exact byte size.ACK, then a C, which together start the data phase. Plain XMODEM and XMODEM-1K skip this step entirely.255 → 0 → 1… on long files. Each is: header, block number, its complement, the data, then the trailer in the negotiated method.SOH block.STX blocks; the gateway may drop to a 128-byte SOH block for a short final chunk rather than padding a whole 1K block.SUB (0x1A) when the file doesn't end on a block boundary.ACK. A NAK (or a read timeout, xmodem_block_timeout, default 20 s) makes it resend the same block, up to xmodem_max_retries (default 10) times.EOT (0x04).NAK the first EOT; the gateway resends it and completes once it sees the ACK.Exactly what goes on the wire for one block.
| Field | Bytes | Notes |
|---|---|---|
| Header | 1 | SOH (128-byte block) or STX (1024-byte block) |
| Block number | 1 | 1, 2, 3 … wrapping at 256. (Block 0 = YMODEM header.) |
| Complement | 1 | One's-complement of the block number (255 - n) — a cheap header sanity check. |
| Data | 128 or 1024 | The payload, padded with SUB (0x1A) on the final short block. |
| Trailer | 2 (CRC) or 1 (checksum) | CRC-16/CCITT (poly 0x1021), or the 8-bit sum, per negotiation. |
So a single CRC-mode block looks like this on the wire:
128-byte CRC block (133 bytes total):
01 3C C3 [ ...128 data bytes... ] 9A 7F
^ ^ ^ ^^^^^^
SOH #60 ~#60 CRC-16
128-byte checksum block (132 bytes total):
01 3C C3 [ ...128 data bytes... ] 8E
^ ^ ^ ^^
SOH #60 ~#60 sum
1024-byte CRC block (1029 bytes total):
02 05 FA [ ...1024 data bytes... ] CRC CRC
^ ^ ^
STX #5 ~#5
| Situation | What happens |
|---|---|
| Bad CRC / checksum or bad complement | Receiver sends NAK; sender resends the same block (bounded by xmodem_max_retries consecutive errors). |
Lost ACK (duplicate block arrives) | The duplicate is ACK'd again but not stored, keeping the streams in sync. |
| Valid block, wrong (non-duplicate) number | An unrecoverable loss of sync — the receiver cancels with three CAN bytes. |
| No block within the block timeout | The receiver NAKs to re-prompt the sender (which retransmits), bounded by xmodem_max_retries; the sender likewise resends on its own timeout. |
| Too many retries | The transfer is cancelled with three CAN (0x18) bytes. |
| Deliberate abort | Two consecutive CAN bytes from either side abort the session (a lone CAN is treated as line noise). |
| File exceeds 8 MB | The receiver aborts with three CAN bytes. |
| Telnet binary data | Over telnet, 0xFF (IAC) and bare CR are escaped per RFC 854 when IAC mode is on — toggle it with I on the File Transfer menu. SSH and serial links skip this. |
One shared set of keys covers XMODEM, XMODEM-1K, and YMODEM (they share the code path). Editable from the telnet Configuration → File Transfer menu, the GUI / web File Transfer → More… popup, or egateway.conf — changes apply on the next transfer, no restart.
| Key | Default | Meaning |
|---|---|---|
xmodem_negotiation_timeout | 45 s | How long to wait for the far end to start. |
xmodem_negotiation_retry_interval | 7 s | Gap between C / NAK pokes during the handshake. |
xmodem_block_timeout | 20 s | Per-block read deadline once the transfer is live. |
xmodem_max_retries | 10 | Resends of one block before giving up. |
Deep-dive reference pages for every protocol and interface, plus the character-set tables and ANSI escape-sequence reference.
128-byte blocks; CRC-16 / checksum negotiation.
Block-0 metadata, batch, exact size truncation.
Streaming; ZDLE, CRC-32, autostart, resume.
Send-Init negotiation; F / A / D / Z / B transfer.
C1 dual checksum, two-phase, GOO/BAD/ACK.
Hayes command set, S-registers, +++ escape.
IAC negotiation, every option, the NVT data phase.
Server, gateway, host keys, TOFU, auth modes.
Hex tables for every encoding: ASCII, ANSI, PETSCII, ATASCII, Baudot/ITA2, ZX Spectrum, TRS-80.
Cursor, colour/SGR, erase, and screen-mode escape codes, with the raw hex bytes.