Skip to main content

zmodem2/
header.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2017-2020 Alexey Arbuzov
3// Copyright (c) 2023-2025 Jarkko Sakkinen
4
5//! ZMODEM protocol header, encoding, and frame definitions.
6
7use crate::buffer::Buffer;
8use crate::crc::{crc16_xmodem, crc32_iso_hdlc};
9use crate::error::Error;
10use crate::io::{Read, Write};
11use crate::zdle;
12use crate::{XON, ZDLE, ZPAD};
13use bitflags::bitflags;
14
15/// Buffer size with enough capacity for an escaped header
16const HEADER_SIZE: usize = 32;
17/// The size of the header payload (frame type + flags).
18const HEADER_PAYLOAD_SIZE: usize = 5;
19
20pub(crate) const ZACK_HEADER: Header = Header::new(Encoding::ZHEX, Frame::ZACK, &[0; 4]);
21pub(crate) const ZDATA_HEADER: Header = Header::new(Encoding::ZBIN32, Frame::ZDATA, &[0; 4]);
22pub(crate) const ZEOF_HEADER: Header = Header::new(Encoding::ZBIN32, Frame::ZEOF, &[0; 4]);
23pub(crate) const ZFIN_HEADER: Header = Header::new(Encoding::ZHEX, Frame::ZFIN, &[0; 4]);
24pub(crate) const ZNAK_HEADER: Header = Header::new(Encoding::ZHEX, Frame::ZNAK, &[0; 4]);
25pub(crate) const ZRPOS_HEADER: Header = Header::new(Encoding::ZHEX, Frame::ZRPOS, &[0; 4]);
26pub(crate) const ZRQINIT_HEADER: Header = Header::new(Encoding::ZHEX, Frame::ZRQINIT, &[0; 4]);
27
28/// Data structure for holding a ZMODEM protocol header, which begins a frame,
29/// and is followed optionally by a variable number of subpackets.
30#[repr(C)]
31#[derive(PartialEq)]
32pub struct Header {
33    encoding: Encoding,
34    frame: Frame,
35    flags: [u8; 4],
36}
37
38impl Header {
39    /// Creates a new instance
40    #[must_use]
41    pub const fn new(encoding: Encoding, frame: Frame, flags: &[u8; 4]) -> Self {
42        Self {
43            encoding,
44            frame,
45            flags: *flags,
46        }
47    }
48
49    /// Returns `Encoding` of the frame
50    #[must_use]
51    pub const fn encoding(&self) -> Encoding {
52        self.encoding
53    }
54
55    /// Returns `Frame`, containing the frame type
56    #[must_use]
57    pub const fn frame(&self) -> Frame {
58        self.frame
59    }
60
61    /// Returns count for the frame types using this field
62    #[must_use]
63    pub const fn count(&self) -> u32 {
64        u32::from_le_bytes(self.flags)
65    }
66
67    /// Encodes and writes the header to the serial port
68    ///
69    /// # Errors
70    ///
71    /// * [`Read`](crate::Error::Read) when the read I/O fails with the serial port
72    /// * [`Write`](crate::Error::Write) when the write I/O fails with the serial port
73    /// * [`Data`](crate::Error::Data) when corrupted data has been detected
74    pub fn write<P>(&self, port: &mut P) -> Result<Option<()>, Error>
75    where
76        P: Write + ?Sized,
77    {
78        if write_header_start(port, self.encoding)?.is_none() {
79            return Ok(None);
80        }
81
82        let mut out: Buffer<HEADER_SIZE> = Buffer::new();
83        out.push(self.frame as u8).map_err(|_| Error::OutOfMemory)?;
84        out.extend_from_slice(&self.flags)
85            .map_err(|_| Error::OutOfMemory)?;
86
87        let mut crc = [0u8; 4];
88        let crc_len = make_crc(&out, &mut crc, self.encoding);
89        out.extend_from_slice(&crc[..crc_len])
90            .map_err(|_| Error::OutOfMemory)?;
91
92        if self.encoding == Encoding::ZHEX {
93            let mut hex_buf = [0u8; HEADER_SIZE];
94            let len = out.len() * 2;
95            let hex = &mut hex_buf.get_mut(..len).ok_or(Error::UnexpectedEof)?;
96            hex::encode_to_slice(&out, hex).map_err(|_| Error::OutOfMemory)?;
97            if write_slice_escaped(port, hex)?.is_none() {
98                return Ok(None);
99            }
100
101            if write_header_end_hex(port, self.frame)?.is_none() {
102                return Ok(None);
103            }
104        } else if write_slice_escaped(port, &out)?.is_none() {
105            return Ok(None);
106        }
107
108        Ok(Some(()))
109    }
110
111    /// Reads and decodes a header from the serial port, and returns a new
112    /// instance
113    ///
114    /// # Errors
115    ///
116    /// * [`Read`](crate::Error::Read) when the read I/O fails with the serial port
117    /// * [`Write`](crate::Error::Write) when the write I/O fails with the serial port
118    /// * [`Data`](crate::Error::Data) when corrupted data has been detected
119    pub fn read<P>(port: &mut P) -> Result<Option<Header>, Error>
120    where
121        P: Read + ?Sized,
122    {
123        let Some(encoding_byte) = port.read_byte()? else {
124            return Ok(None);
125        };
126        let encoding = Encoding::try_from(encoding_byte)?;
127
128        let mut out_hex: Buffer<HEADER_SIZE> = Buffer::new();
129        for _ in 0..Header::read_size(encoding) {
130            let Some(byte) = read_byte_unescaped(port)? else {
131                return Ok(None);
132            };
133            out_hex.push(byte).map_err(|_| Error::OutOfMemory)?;
134        }
135
136        let mut out: Buffer<HEADER_SIZE> = Buffer::new();
137        if encoding == Encoding::ZHEX {
138            let mut out_bytes = [0u8; HEADER_SIZE / 2];
139            let out_len = out_hex.len() / 2;
140            hex::decode_to_slice(&out_hex, &mut out_bytes[..out_len])
141                .map_err(|_| Error::MalformedHeader)?;
142            out.extend_from_slice(&out_bytes[..out_len])
143                .map_err(|_| Error::OutOfMemory)?;
144        } else {
145            out.extend_from_slice(&out_hex)
146                .map_err(|_| Error::OutOfMemory)?;
147        }
148        check_crc(&out[..5], &out[5..], encoding)?;
149        let frame = Frame::try_from(out[0])?;
150        let mut header = Header::new(encoding, frame, &[0; 4]);
151        header.flags.copy_from_slice(&out[1..=4]);
152        Ok(Some(header))
153    }
154
155    /// Returns a new instance with the flags substitude with a count
156    /// for the frame types using this field.
157    #[must_use]
158    pub const fn with_count(&self, count: u32) -> Self {
159        Header::new(self.encoding, self.frame, &count.to_le_bytes())
160    }
161
162    /// Returns the serialized size of the header payload (payload + CRC)
163    const fn read_size(encoding: Encoding) -> usize {
164        match encoding {
165            Encoding::ZBIN => HEADER_PAYLOAD_SIZE + 2,
166            Encoding::ZBIN32 => HEADER_PAYLOAD_SIZE + 4,
167            Encoding::ZHEX => (HEADER_PAYLOAD_SIZE + 2) * 2,
168        }
169    }
170}
171
172/// The ZMODEM protocol frame encoding
173#[repr(u8)]
174#[allow(clippy::upper_case_acronyms)]
175#[derive(Clone, Copy, Debug, PartialEq)]
176pub enum Encoding {
177    ZBIN = 0x41,
178    ZHEX = 0x42,
179    ZBIN32 = 0x43,
180}
181
182impl TryFrom<u8> for Encoding {
183    type Error = Error;
184
185    fn try_from(value: u8) -> Result<Self, Self::Error> {
186        match value {
187            0x41 => Ok(Encoding::ZBIN),
188            0x42 => Ok(Encoding::ZHEX),
189            0x43 => Ok(Encoding::ZBIN32),
190            _ => Err(Error::MalformedEncoding(value)),
191        }
192    }
193}
194
195#[repr(u8)]
196#[allow(clippy::upper_case_acronyms)]
197#[derive(Clone, Copy, Debug, PartialEq)]
198/// Frame types
199pub enum Frame {
200    /// Request receive init
201    ZRQINIT = 0,
202    /// Receiver capabilities and packet size
203    ZRINIT = 1,
204    /// Send init sequence (optional)
205    ZSINIT = 2,
206    /// ACK to above
207    ZACK = 3,
208    /// File name from sender
209    ZFILE = 4,
210    /// To sender: skip this file
211    ZSKIP = 5,
212    /// Last packet was garbled
213    ZNAK = 6,
214    /// Abort batch transfers
215    ZABORT = 7,
216    /// Finish session
217    ZFIN = 8,
218    /// Resume data trans at this position
219    ZRPOS = 9,
220    /// Data packet(s) follow
221    ZDATA = 10,
222    /// End of file
223    ZEOF = 11,
224    /// Fatal Read or Write error Detected
225    ZFERR = 12,
226    /// Request for file CRC and response
227    ZCRC = 13,
228    /// Receiver's Challenge
229    ZCHALLENGE = 14,
230    /// Request is complete
231    ZCOMPL = 15,
232    /// Other end canned session with CAN*5
233    ZCAN = 16,
234    /// Request for free bytes on filesystem
235    ZFREECNT = 17,
236    /// Command from sending program
237    ZCOMMAND = 18,
238    /// Output to standard error, data follows
239    ZSTDERR = 19,
240}
241
242impl TryFrom<u8> for Frame {
243    type Error = Error;
244
245    fn try_from(value: u8) -> Result<Self, Self::Error> {
246        match value {
247            0 => Ok(Frame::ZRQINIT),
248            1 => Ok(Frame::ZRINIT),
249            2 => Ok(Frame::ZSINIT),
250            3 => Ok(Frame::ZACK),
251            4 => Ok(Frame::ZFILE),
252            5 => Ok(Frame::ZSKIP),
253            6 => Ok(Frame::ZNAK),
254            7 => Ok(Frame::ZABORT),
255            8 => Ok(Frame::ZFIN),
256            9 => Ok(Frame::ZRPOS),
257            10 => Ok(Frame::ZDATA),
258            11 => Ok(Frame::ZEOF),
259            12 => Ok(Frame::ZFERR),
260            13 => Ok(Frame::ZCRC),
261            14 => Ok(Frame::ZCHALLENGE),
262            15 => Ok(Frame::ZCOMPL),
263            16 => Ok(Frame::ZCAN),
264            17 => Ok(Frame::ZFREECNT),
265            18 => Ok(Frame::ZCOMMAND),
266            19 => Ok(Frame::ZSTDERR),
267            _ => Err(Error::MalformedFrame(value)),
268        }
269    }
270}
271
272bitflags! {
273    /// `ZRINIT` flags
274    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275    pub struct Zrinit: u8 {
276        /// Can send and receive in full-duplex
277        const CANFDX = 0x01;
278        /// Can receive data in parallel with disk I/O
279        const CANOVIO = 0x02;
280        /// Can send a break signal
281        const CANBRK = 0x04;
282        /// Can decrypt
283        const CANCRY = 0x08;
284        /// Can uncompress
285        const CANLZW = 0x10;
286        /// Can use 32-bit frame check
287        const CANFC32 = 0x20;
288        /// Expects control character to be escaped
289        const ESCCTL = 0x40;
290        /// Expects 8th bit to be escaped
291        const ESC8 = 0x80;
292    }
293}
294
295fn write_header_start<P>(port: &mut P, encoding: Encoding) -> Result<Option<()>, Error>
296where
297    P: Write + ?Sized,
298{
299    if port.write_byte(ZPAD)?.is_none() {
300        return Ok(None);
301    }
302    if encoding == Encoding::ZHEX && port.write_byte(ZPAD)?.is_none() {
303        return Ok(None);
304    }
305    if port.write_byte(ZDLE)?.is_none() {
306        return Ok(None);
307    }
308    port.write_byte(encoding as u8)
309}
310
311fn write_header_end_hex<P>(port: &mut P, frame: Frame) -> Result<Option<()>, Error>
312where
313    P: Write + ?Sized,
314{
315    if port.write_byte(b'\r')?.is_none() {
316        return Ok(None);
317    }
318    if port.write_byte(b'\n')?.is_none() {
319        return Ok(None);
320    }
321    if frame != Frame::ZACK && frame != Frame::ZFIN && port.write_byte(XON)?.is_none() {
322        return Ok(None);
323    }
324    Ok(Some(()))
325}
326
327fn make_crc(data: &[u8], out: &mut [u8], encoding: Encoding) -> usize {
328    if encoding == Encoding::ZBIN32 {
329        let crc = crc32_iso_hdlc(data).to_le_bytes();
330        out[..4].copy_from_slice(&crc[..4]);
331        4
332    } else {
333        let crc = crc16_xmodem(data).to_be_bytes();
334        out[..2].copy_from_slice(&crc[..2]);
335        2
336    }
337}
338
339fn check_crc(data: &[u8], crc: &[u8], encoding: Encoding) -> Result<(), Error> {
340    let mut crc2 = [0u8; 4];
341    let crc2_len = make_crc(data, &mut crc2, encoding);
342    if *crc == crc2[..crc2_len] {
343        Ok(())
344    } else if encoding == Encoding::ZBIN32 {
345        Err(Error::UnexpectedCrc32)
346    } else {
347        Err(Error::UnexpectedCrc16)
348    }
349}
350
351pub(crate) fn write_slice_escaped<P>(port: &mut P, buf: &[u8]) -> Result<Option<()>, Error>
352where
353    P: Write + ?Sized,
354{
355    for value in buf {
356        if write_byte_escaped(port, *value)?.is_none() {
357            return Ok(None);
358        }
359    }
360
361    Ok(Some(()))
362}
363
364pub(crate) fn write_byte_escaped<P>(port: &mut P, value: u8) -> Result<Option<()>, Error>
365where
366    P: Write + ?Sized,
367{
368    let escaped = zdle::ZDLE_TABLE[value as usize];
369    if escaped != value && port.write_byte(ZDLE)?.is_none() {
370        return Ok(None);
371    }
372    port.write_byte(escaped)
373}
374
375pub(crate) fn read_byte_unescaped<P>(port: &mut P) -> Result<Option<u8>, Error>
376where
377    P: Read + ?Sized,
378{
379    let Some(b) = port.read_byte()? else {
380        return Ok(None);
381    };
382    Ok(Some(if b == ZDLE {
383        let Some(b) = port.read_byte()? else {
384            return Ok(None);
385        };
386        zdle::UNZDLE_TABLE[b as usize]
387    } else {
388        b
389    }))
390}