ymodem/
xmodem.rs

1use std::io::{Read, Write};
2pub use xymodem_util::*;
3
4// TODO: Send CAN byte after too many errors
5// TODO: Handle CAN bytes while sending
6// TODO: Implement Error for Error
7
8const SOH: u8 = 0x01;
9const STX: u8 = 0x02;
10const EOT: u8 = 0x04;
11const ACK: u8 = 0x06;
12const NAK: u8 = 0x15;
13const CAN: u8 = 0x18;
14const CRC: u8 = 0x43;
15
16pub type Result<T> = std::result::Result<T, Error>;
17
18#[derive(Copy, Clone, Debug)]
19pub enum Checksum {
20    Standard,
21    CRC16,
22}
23
24#[derive(Copy, Clone, Debug)]
25pub enum BlockLength {
26    Standard = 128,
27    OneK = 1024,
28}
29
30/// Configuration for the XMODEM transfer.
31#[derive(Copy, Clone, Debug)]
32pub struct Xmodem {
33    /// The number of errors that can occur before the communication is
34    /// considered a failure. Errors include unexpected bytes and timeouts waiting for bytes.
35    pub max_errors: u32,
36
37    /// The number of errors that can occur before the communication is
38    /// considered a failure. Errors include unexpected bytes and timeouts waiting for bytes.
39    ///
40    /// This only applies to the initial packet
41    pub max_initial_errors: u32,
42
43    /// The byte used to pad the last block. XMODEM can only send blocks of a certain size,
44    /// so if the message is not a multiple of that size the last block needs to be padded.
45    pub pad_byte: u8,
46
47    /// The length of each block. There are only two options: 128-byte blocks (standard
48    ///  XMODEM) or 1024-byte blocks (XMODEM-1k).
49    pub block_length: BlockLength,
50
51    /// The checksum mode used by XMODEM. This is determined by the receiver.
52    checksum_mode: Checksum,
53    errors: u32,
54    initial_errors: u32,
55}
56
57impl Xmodem {
58    /// Creates the XMODEM config with default parameters.
59    pub fn new() -> Self {
60        Xmodem {
61            max_errors: 16,
62            max_initial_errors: 16,
63            pad_byte: 0x1a,
64            block_length: BlockLength::Standard,
65            checksum_mode: Checksum::Standard,
66            errors: 0,
67            initial_errors: 0,
68        }
69    }
70
71    /// Starts the XMODEM transmission.
72    ///
73    /// `dev` should be the serial communication channel (e.g. the serial device).
74    /// `stream` should be the message to send (e.g. a file).
75    ///
76    /// # Timeouts
77    /// This method has no way of setting the timeout of `dev`, so it's up to the caller
78    /// to set the timeout of the device before calling this method. Timeouts on receiving
79    /// bytes will be counted against `max_errors`, but timeouts on transmitting bytes
80    /// will be considered a fatal error.
81    pub fn send<D: Read + Write, R: Read>(&mut self, dev: &mut D, stream: &mut R) -> Result<()> {
82        self.errors = 0;
83
84        dbg!("Starting XMODEM transfer");
85        (self.start_send(dev))?;
86        dbg!("First byte received. Sending stream.");
87        (self.send_stream(dev, stream))?;
88        dbg!("Sending EOT");
89        (self.finish_send(dev))?;
90
91        Ok(())
92    }
93
94    /// Receive an XMODEM transmission.
95    ///
96    /// `dev` should be the serial communication channel (e.g. the serial device).
97    /// The received data will be written to `outstream`.
98    /// `checksum` indicates which checksum mode should be used; Checksum::Standard is
99    /// a reasonable default.
100    ///
101    /// # Timeouts
102    /// This method has no way of setting the timeout of `dev`, so it's up to the caller
103    /// to set the timeout of the device before calling this method. Timeouts on receiving
104    /// bytes will be counted against `max_errors`, but timeouts on transmitting bytes
105    /// will be considered a fatal error.
106    pub fn recv<D: Read + Write, W: Write>(
107        &mut self,
108        dev: &mut D,
109        outstream: &mut W,
110        checksum: Checksum,
111    ) -> Result<()> {
112        self.errors = 0;
113        self.checksum_mode = checksum;
114        let mut handled_first_packet = false;
115        dbg!("Starting XMODEM receive");
116
117        let first_char;
118        loop {
119            (dev.write(&[match self.checksum_mode {
120                Checksum::Standard => NAK,
121                Checksum::CRC16 => CRC,
122            }])?);
123
124            match get_byte_timeout(dev)? {
125                bt @ Some(SOH) | bt @ Some(STX) => {
126                    // The first SOH or STX is used to initialize the transfer
127                    first_char = bt.unwrap();
128                    break;
129                }
130                _ => {
131                    self.initial_errors += 1;
132                    if self.initial_errors > self.max_initial_errors {
133                        eprint!(
134                            "Exhausted max retries ({}) while waiting for SOH or STX",
135                            self.max_initial_errors
136                        );
137                        return Err(Error::ExhaustedRetries);
138                    }
139                }
140            }
141        }
142        dbg!("NCG sent. Receiving stream.");
143        let mut packet_num: u8 = 1;
144        loop {
145            match if handled_first_packet {
146                get_byte_timeout(dev)?
147            } else {
148                Some(first_char)
149            } {
150                bt @ Some(SOH) | bt @ Some(STX) => {
151                    handled_first_packet = true;
152                    // Handle next packet
153                    let packet_size = match bt {
154                        Some(SOH) => 128,
155                        Some(STX) => 1024,
156                        _ => 0, // Why does the compiler need this?
157                    };
158                    let pnum = (get_byte(dev))?; // specified packet number
159                    let pnum_1c = (get_byte(dev))?; // same, 1's complemented
160                                                    // We'll respond with cancel later if the packet number is wrong
161                    let cancel_packet = packet_num != pnum || (255 - pnum) != pnum_1c;
162                    let mut data: Vec<u8> = Vec::new();
163                    data.resize(packet_size, 0);
164                    (dev.read_exact(&mut data))?;
165                    let success = match self.checksum_mode {
166                        Checksum::Standard => {
167                            let recv_checksum = (get_byte(dev))?;
168                            calc_checksum(&data) == recv_checksum
169                        }
170                        Checksum::CRC16 => {
171                            let recv_checksum =
172                                (((get_byte(dev))? as u16) << 8) + (get_byte(dev))? as u16;
173                            calc_crc(&data) == recv_checksum
174                        }
175                    };
176
177                    if cancel_packet {
178                        (dev.write(&[CAN]))?;
179                        (dev.write(&[CAN]))?;
180                        return Err(Error::Canceled);
181                    }
182                    if success {
183                        packet_num = packet_num.wrapping_add(1);
184                        (dev.write(&[ACK]))?;
185                        (outstream.write_all(&data))?;
186                    } else {
187                        (dev.write(&[NAK]))?;
188                        self.errors += 1;
189                    }
190                }
191                Some(EOT) => {
192                    // End of file
193                    (dev.write(&[ACK]))?;
194                    break;
195                }
196                Some(_) => {
197                    warn!("Unrecognized symbol!");
198                }
199                None => {
200                    if !handled_first_packet {
201                        self.errors = self.max_errors;
202                    } else {
203                        self.errors += 1;
204                    }
205                    warn!("Timeout!")
206                }
207            }
208            if self.errors >= self.max_errors {
209                eprint!(
210                    "Exhausted max retries ({}) while waiting for ACK for EOT",
211                    self.max_errors
212                );
213                return Err(Error::ExhaustedRetries);
214            }
215        }
216        Ok(())
217    }
218    fn start_send<D: Read + Write>(&mut self, dev: &mut D) -> Result<()> {
219        let mut cancels = 0u32;
220        loop {
221            match (get_byte_timeout(dev))? {
222                Some(c) => match c {
223                    NAK => {
224                        dbg!("Standard checksum requested");
225                        self.checksum_mode = Checksum::Standard;
226                        return Ok(());
227                    }
228                    CRC => {
229                        dbg!("16-bit CRC requested");
230                        self.checksum_mode = Checksum::CRC16;
231                        return Ok(());
232                    }
233                    CAN => {
234                        warn!("Cancel (CAN) byte received");
235                        cancels += 1;
236                    }
237                    c => warn!("Unknown byte received at start of XMODEM transfer: {}", c),
238                },
239                None => warn!("Timed out waiting for start of XMODEM transfer."),
240            }
241
242            self.errors += 1;
243
244            if cancels >= 2 {
245                eprint!(
246                    "Transmission canceled: received two cancel (CAN) bytes \
247                        at start of XMODEM transfer"
248                );
249                return Err(Error::Canceled);
250            }
251
252            if self.errors >= self.max_errors {
253                eprint!(
254                    "Exhausted max retries ({}) at start of XMODEM transfer.",
255                    self.max_errors
256                );
257                if let Err(err) = dev.write_all(&[CAN]) {
258                    warn!("Error sending CAN byte: {}", err);
259                }
260                return Err(Error::ExhaustedRetries);
261            }
262        }
263    }
264
265    fn send_stream<D: Read + Write, R: Read>(&mut self, dev: &mut D, stream: &mut R) -> Result<()> {
266        let mut block_num = 0u32;
267        loop {
268            let mut buff = vec![self.pad_byte; self.block_length as usize + 3];
269            let n = (stream.read(&mut buff[3..]))?;
270            if n == 0 {
271                dbg!("Reached EOF");
272                return Ok(());
273            }
274
275            block_num += 1;
276            buff[0] = match self.block_length {
277                BlockLength::Standard => SOH,
278                BlockLength::OneK => STX,
279            };
280            buff[1] = (block_num & 0xFF) as u8;
281            buff[2] = 0xFF - buff[1];
282
283            match self.checksum_mode {
284                Checksum::Standard => {
285                    let checksum = calc_checksum(&buff[3..]);
286                    buff.push(checksum);
287                }
288                Checksum::CRC16 => {
289                    let crc = calc_crc(&buff[3..]);
290                    buff.push(((crc >> 8) & 0xFF) as u8);
291                    buff.push((crc & 0xFF) as u8);
292                }
293            }
294
295            dbg!("Sending block {}", block_num);
296            (dev.write_all(&buff))?;
297
298            match (get_byte_timeout(dev))? {
299                Some(c) => {
300                    if c == ACK {
301                        dbg!("Received ACK for block {}", block_num);
302                        continue;
303                    } else {
304                        warn!("Expected ACK, got {}", c);
305                    }
306                    // TODO handle CAN bytes
307                }
308                None => warn!("Timeout waiting for ACK for block {}", block_num),
309            }
310
311            self.errors += 1;
312
313            if self.errors >= self.max_errors {
314                eprint!(
315                    "Exhausted max retries ({}) while sending block {} in XMODEM transfer",
316                    self.max_errors, block_num
317                );
318                return Err(Error::ExhaustedRetries);
319            }
320        }
321    }
322
323    fn finish_send<D: Read + Write>(&mut self, dev: &mut D) -> Result<()> {
324        loop {
325            (dev.write_all(&[EOT]))?;
326
327            match (get_byte_timeout(dev))? {
328                Some(c) => {
329                    if c == ACK {
330                        info!("XMODEM transmission successful");
331                        return Ok(());
332                    } else {
333                        warn!("Expected ACK, got {}", c);
334                    }
335                }
336                None => warn!("Timeout waiting for ACK for EOT"),
337            }
338
339            self.errors += 1;
340
341            if self.errors >= self.max_errors {
342                eprint!(
343                    "Exhausted max retries ({}) while waiting for ACK for EOT",
344                    self.max_errors
345                );
346                return Err(Error::ExhaustedRetries);
347            }
348        }
349    }
350}