Overview

ZMODEM (Chuck Forsberg, 1988) is the modern, streaming, batch-capable evolution of the XMODEM family — and the one most terminal programs start automatically.

Where XMODEM and YMODEM send one fixed block and wait for an acknowledgement, ZMODEM is built around frames and subpackets with position-based recovery: data is identified by its byte offset in the file, so the receiver recovers from an error by telling the sender where to resume rather than which block to resend. It adds, over YMODEM:

  • CRC-32 (as well as CRC-16) for far stronger error detection.
  • ZDLE escaping so the stream survives links that eat control bytes or XON/XOFF flow control.
  • Autostart — the sender can kick off a transfer from any menu; the gateway detects it and switches to receive.
  • Crash recovery / resume via ZRPOS, and per-file skip via ZSKIP in a batch.

The gateway implements the full Forsberg 1988 spec in src/zmodem.rs: ZDLE escaping, hex / binary16 / binary32 headers, CRC-16 and CRC-32, and batch send and receive. Its ZRINIT advertises CANFDX | CANOVIO | CANFC32 (full-duplex, overlapped I/O, CRC-32 capable), but the data path runs stop-and-wait — every non-final data subpacket is ZCRCQ-terminated and ACK-gated (the final one closes the frame with ZCRCE). That is slightly slower than true streaming in theory, but rock-solid over a telnet or SSH hop where buffering and flow control are unpredictable.

Coming from XMODEM/YMODEM? ZMODEM shares none of their block framing — it is a different wire protocol. The XMODEM and YMODEM pages cover the simpler block-and-wait scheme; ZMODEM is the right choice on clean modern links and is what Qodem, SyncTERM, ZOC, and ExtraPuTTY use by default.

Frames, Headers & Subpackets

Everything on the wire is either a header or a data subpacket.

Header encodings

A header carries a frame type plus a 4-byte argument (a file position, a capability mask, a length). It comes in three encodings, all introduced by the ZPAD ZDLE sync (* * 0x18 …), which is also what makes autostart possible:

EncodingIntroCheckUsed for
Hex** ZDLE BCRC-16Handshake frames — human-readable, robust.
Binary16* ZDLE ACRC-16Data frames with CRC-16.
Binary32* ZDLE CCRC-32Data frames with CRC-32 (when CANFC32 is negotiated).

The A / B / C suffix is exactly what the autostart prefix **\x18[ABC] encodes.

Frame types
FrameHexSent byMeaning
ZRQINIT0x00Sender“Request init” — opens a transfer (and triggers autostart).
ZRINIT0x01Receiver“Ready” — carries the capability flags.
ZSINIT0x02SenderOptional sender init flags / attention string.
ZACK0x03EitherAcknowledge (e.g. a flow-control checkpoint).
ZFILE0x04SenderFile header — followed by a name/size subpacket.
ZSKIP0x05Receiver“Skip this file” — decline one file in a batch.
ZNAK0x06EitherLast header was garbled — resend it.
ZABORT0x07EitherAbort the whole session.
ZFIN0x08EitherFinish — tear the session down (both sides exchange it).
ZRPOS0x09Receiver“Resume at this byte offset” — accept, recover, or resume.
ZDATA0x0ASenderData frame at a byte offset; subpackets follow.
ZEOF0x0BSenderEnd of file — argument is the final length.
ZFERR0x0CSenderSender hit a file read/write error and can't continue — the gateway (as receiver) aborts the transfer cleanly rather than waiting out a timeout.
ZCRC0x0DEitherVerified-resume CRC. A receiver holding a partial file asks the sender to prove the first N bytes match (N=0 means whole file); the gateway (as sender) answers with the CRC-32 of those bytes so the peer can resume safely instead of restarting.
ZCHALLENGE0x0EReceiverOptional liveness check — the gateway (as sender) echoes the value back in a ZACK.
ZFREECNT0x11SenderOptional free-space query — the gateway (as receiver) answers with a ZACK.
ZCOMMAND0x12SenderOptional spec feature, not implemented — “run this command.” The gateway never executes it; it refuses every request with a non-zero ZCOMPL. Use SSH for shell access. See the note below.
ZSTDERR0x13SenderInformational text for the receiver's stderr (progress notes, “skipped because…”). Purely cosmetic — the gateway (as receiver) drains and logs it without changing the transfer outcome.
ZCOMMAND (0x12) is recognized but never executed. ZCOMMAND is an optional part of the ZMODEM spec — it is not required for a compliant implementation, and most transfers never use it. It lets a connected sender push a shell command for the receiver to run, returning the command's exit status in a ZCOMPL header. On Chuck Forsberg's original Unix rzsz reference code the command string is handed straight to /bin/sh -c; its classic use was a sender pushing sz somefile to auto-start a download.

That is arbitrary remote code execution on the host, which is an unacceptable default for a long-lived, multi-session server: unlike Forsberg's throwaway single-session rz, one ZMODEM session here shares the process with every other connected user. The gateway therefore handles the frame defensively rather than ignoring it — it recognizes ZCOMMAND, drains the command subpacket so the wire stays in sync, replies a non-zero ZCOMPL (refused), and ends the session. Every request is rejected and there is no configuration switch to enable it. For command access, use the authenticated, encrypted SSH interface instead. Every other Forsberg 1988 frame is fully implemented.
Subpacket terminators

Data rides in subpackets; the byte that ends each one tells the receiver what to do next. The gateway sends data using ZCRCQ (ACK-gated) between subpackets and ZCRCE on the last, which is what gives it its safe stop-and-wait cadence.

TerminatorByteMeaning
ZCRCEhEnd of frame, no more data this frame, no ACK. (The gateway's final data subpacket.)
ZCRCGiMore data follows, streaming — no ACK.
ZCRCQjMore data follows, ACK required (mid-frame flow control). (Each non-final data subpacket the gateway sends.)
ZCRCWkEnd of frame, more data next frame, ACK required. (The gateway's ZFILE name/size subpacket.)

ZDLE Escaping

How ZMODEM keeps control bytes from being eaten by the link.

ZDLE (ZMODEM Data Link Escape, 0x18 — the same byte as CAN) is the escape marker. Any byte in the data or CRC that could be intercepted by the transport — ZDLE itself, DLE (0x10), the XON/XOFF flow-control pair (0x11 / 0x13) and their high-bit twins (0x91 / 0x93), and CR (0x0D, and its high-bit twin 0x8D) — is rewritten on the wire as ZDLE followed by the byte with bit 6 flipped (byte XOR 0x40). The receiver reverses the transform.

This is why ZMODEM survives XON/XOFF-flow-controlled serial links and telnet hops that would choke on a raw 0x11: those bytes never appear literally in the escaped stream. (Over telnet the gateway additionally handles the 0xFF IAC transform; ZDLE and IAC escaping are independent layers.)

A receiver can ask the sender to escape more than that default set by setting ESCCTL (escape every control character) or ESC8 (escape every 8th-bit byte) in its ZRINIT capability flags — the gateway honors both when it is the sender. On decode it also accepts the legacy ZRUB0 / ZRUB1 rubout escapes (a sender's alternate encoding of 0x7F / 0xFF). And because ZDLE is the same byte as CAN, a run of them is how a peer signals abort: the gateway treats five or more consecutive CANs (or a ZDLE CAN pair) as an immediate cancel rather than waiting out a timeout.


Download — Gateway as Sender

You pull a file from the gateway. The gateway sends; your terminal's receiver drives the conversation with ZRINIT / ZRPOS.

The single-file handshake (the ZFILE → ZEOF cycle repeats per file in a batch):

gateway  -> ZRQINIT                       open the session
term     <- ZRINIT (CANFDX|CANOVIO|CANFC32)  receiver ready + caps
gateway  -> ZFILE + subpacket("name\0size\0", ZCRCW)
term     <- ZRPOS(0)                     accept; start at byte 0
gateway  -> ZDATA(0) + 1024-byte subpackets (ZCRCQ, ACK-gated; final ZCRCE) ... until EOF
gateway  -> ZEOF(length)                 final file size
term     <- ZRINIT                       ready for next file
gateway  -> ZFIN
term     <- ZFIN
gateway  -> "OO"                          over and out

Step by step

  1. From the File Transfer menu press D, pick the file, and choose Z. The gateway opens with ZRQINIT (most terminals also auto-receive the moment they see it).
  2. The terminal answers ZRINIT, advertising its capabilities; the gateway notes whether CRC-32 is available.
  3. The gateway sends a ZFILE header followed by a subpacket carrying the filename and exact size (and mtime / mode when known), terminated with ZCRCW.
  4. The receiver replies ZRPOS(0) to accept and start at byte 0 — or ZSKIP to decline this file, or a non-zero ZRPOS to resume a partial file.
  5. The gateway sends a ZDATA header at that offset, then streams 1024-byte data subpackets, each non-final one ZCRCQ-terminated and waiting for the receiver's ZACK before the next (the last closes the frame with ZCRCE) — the stop-and-wait cadence.
  6. If the receiver detects an error it sends ZRPOS(offset); the gateway seeks back to that byte offset and resumes from there.
  7. At end of file the gateway sends ZEOF(length). The receiver answers ZRINIT (ready for the next file).
  8. With no more files the gateway sends ZFIN; the receiver returns ZFIN; the gateway sends the "OO" over-and-out trailer and the session ends.

Upload — Gateway as Receiver

You send a file to the gateway. The gateway receives, so it drives with ZRINIT / ZRPOS / ZSKIP.

gateway  -> ZRINIT (CANFDX|CANOVIO|CANFC32)  ready + caps
term     <- ZFILE + subpacket("name\0size\0")
gateway  -> ZRPOS(0)   accept   (or ZSKIP to decline a colliding name)
term     <- ZDATA + data subpackets ... 
gateway  -> ZRPOS(offset)                 only on a CRC error: rewind
term     <- ZEOF(length)
gateway  -> ZRINIT                        ready for next file
term     <- ZFIN
gateway  -> ZFIN
term     <- "OO"

Step by step

  1. Start a receive one of two ways: press U, enter a name, and choose Z — or simply start a ZMODEM send in your terminal from any menu and let autostart catch it (see below).
  2. The gateway sends ZRINIT advertising CANFDX | CANOVIO | CANFC32.
  3. The sender transmits a ZFILE header and a subpacket with the filename, size, and optional mtime / mode. The gateway parses it and validates the path (no traversal).
  4. The gateway replies ZRPOS(0) to accept. In a batch, a colliding filename is declined with ZSKIP so the sender moves on.
  5. The sender streams ZDATA subpackets. The gateway checks each subpacket's CRC (16 or 32); on a bad one — or a late/garbled header, or a stall — it re-sends ZRPOS with the last good byte offset and the sender resumes there. A good subpacket clears the error count; zmodem_max_retries consecutive errors without progress ends the transfer.
  6. The sender sends ZEOF(length); the gateway answers ZRINIT for the next file. mtime and mode from the ZFILE subpacket are applied to the saved file when present.
  7. The sender sends ZFIN; the gateway returns ZFIN; the sender sends "OO" and the session closes.

Batch & Autostart
Batch transfers

A batch is the ZFILE → ZDATA → ZEOF cycle repeated once per file, with the receiver answering ZRINIT after each ZEOF to signal “ready for the next.” The single ZFIN / ZFIN / "OO" exchange closes the whole session. Either side can decline an individual file with ZSKIP without aborting the batch.

Autostart

Most terminals (Qodem, ZOC, SyncTERM, ExtraPuTTY) begin a ZMODEM send by emitting the autostart sequence — **\x18B, **\x18A, or **\x18C (a hex / binary16 / binary32 ZRQINIT) — directly into whatever menu the user is sitting at, expecting the far end to “just receive.” The gateway's idle input loop watches for that ZPAD ZPAD ZDLE prefix and, on a match:

  1. logs “ZMODEM autostart detected; switching to receive,”
  2. drains any residual ZRQINIT bytes (the sender re-sends once we ZRINIT),
  3. hands off to the normal receive flow — using the sender's filename after path validation, applying mtime / mode if present,
  4. and returns to whichever menu the user was on when the transfer fired.
No autostart prefix? Some MS-DOS-era and scripted sz senders don't emit it. Drive the transfer manually instead: File Transfer → U / DZ.

Error Recovery, Resume & Skip
SituationWhat happens
Any data-phase error (bad subpacket CRC 16/32, a late or garbled header, a position mismatch)The receiver re-sends ZRPOS with the last good byte offset and the sender resumes there — recovery is by position, not block number. All of these share one counter that resets on each good subpacket and bounds consecutive errors at zmodem_max_retries (default 10) before the receiver cancels.
Garbled headerAnswered with ZNAK (or a repeated request); the frame is re-sent.
Resume a partial fileOn ZFILE the receiver may answer a non-zero ZRPOS; the gateway-as-sender honors it and starts mid-file.
Verified resume (CRC)A receiver that wants to prove its partial copy matches before appending sends a ZCRC request (byte count N, or 0 for the whole file); the gateway-as-sender answers with the CRC-32 of those bytes. On a match the peer resumes; on a mismatch it restarts from zero — either way no corruption. (lrzsz rz --crc-check.)
Decline one fileZSKIP — the gateway sends it to refuse a colliding name on receive, and honors it on send by advancing to the next file.
AbortZABORT, or the CAN (0x18) run a terminal sends on Ctrl-X; the session tears down cleanly.
Stall (no data within zmodem_frame_timeout)The receiver re-prompts with ZRPOS rather than abandoning the transfer on the first quiet moment, counting toward the same consecutive-error bound; the sender likewise retransmits stalled frames up to zmodem_max_retries.
File exceeds 8 MBThe transfer is aborted — the same cap as the other protocols.

Tunables

ZMODEM has its own independent timeout set (separate from the XMODEM family). 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.

KeyDefaultMeaning
zmodem_negotiation_timeout45 sTime to complete the ZRQINIT / ZRINIT handshake.
zmodem_negotiation_retry_interval5 sGap between ZRINIT / ZRQINIT re-sends during the handshake.
zmodem_frame_timeout30 sPer-frame read timeout once the transfer is live (headers and subpackets).
zmodem_max_retries10Retry cap for ZRQINIT / ZRPOS / ZDATA frames before a ZABORT.
See also: the XMODEM and YMODEM references for the block-and-wait protocols, the File Transfer section of the User Manual for the menu walkthrough, and the Kermit Reference for the 7-bit-clean alternative. ZMODEM is the fastest choice on clean modern links.

References

Deep-dive reference pages for every protocol and interface, plus the character-set tables and ANSI escape-sequence reference.

XMODEM

128-byte blocks; CRC-16 / checksum negotiation.

YMODEM

Block-0 metadata, batch, exact size truncation.

ZMODEM (this page)

Streaming; ZDLE, CRC-32, autostart, resume.

Kermit

Send-Init negotiation; F / A / D / Z / B transfer.

Punter

C1 dual checksum, two-phase, GOO/BAD/ACK.

AT Commands

Hayes command set, S-registers, +++ escape.

Telnet

IAC negotiation, every option, the NVT data phase.

SSH

Server, gateway, host keys, TOFU, auth modes.

Character Code Tables

Hex tables for every encoding: ASCII, ANSI, PETSCII, ATASCII, Baudot/ITA2, ZX Spectrum, TRS-80.

ANSI Escape Sequences

Cursor, colour/SGR, erase, and screen-mode escape codes, with the raw hex bytes.