txmodems/variants/api/
xmodem.rs

1use alloc::{boxed::Box, vec, vec::Vec};
2use core::convert::From;
3
4use crate::common::{
5    calc_checksum, calc_crc, get_byte, get_byte_timeout, ModemError,
6    ModemResult, ModemTrait, XModemTrait,
7};
8use core2::io::{Read, Write};
9
10use crate::variants::xmodem::{
11    common::{BlockLengthKind, ChecksumKind},
12    Consts,
13};
14
15// TODO: Send CAN byte after too many errors
16// TODO: Handle CAN bytes while sending
17// TODO: Implement Error for Error
18
19/// `Xmodem` acts as state for XMODEM transfers
20#[derive(Default, Debug, Copy, Clone)]
21pub struct XModem {
22    /// The number of errors that can occur before the communication is
23    /// considered a failure. Errors include unexpected bytes and timeouts waiting for bytes.
24    pub max_errors: u32,
25
26    /// The byte used to pad the last block. XMODEM can only send blocks of a certain size,
27    /// so if the message is not a multiple of that size the last block needs to be padded.
28    pub pad_byte: u8,
29
30    /// The length of each block. There are only two options: 128-byte blocks (standard
31    ///  XMODEM) or 1024-byte blocks (XMODEM-1k).
32    pub block_length: BlockLengthKind,
33
34    /// The checksum mode used by XMODEM. This is determined by the receiver.
35    checksum_mode: ChecksumKind,
36    errors: u32,
37}
38
39impl ModemTrait for XModem {
40    fn new() -> Self
41    where
42        Self: Sized,
43    {
44        Self {
45            max_errors: 16,
46            pad_byte: 0x1a,
47            block_length: BlockLengthKind::Standard,
48            checksum_mode: ChecksumKind::Standard,
49            errors: 0,
50        }
51    }
52}
53
54impl XModemTrait for XModem {
55    fn send<D, R>(&mut self, dev: &mut D, inp: &mut R) -> ModemResult<()>
56    where
57        D: Read + Write,
58        R: Read,
59    {
60        self.errors = 0;
61
62        self.init_send(dev)?;
63
64        self.send_stream(dev, inp)?;
65
66        self.finish_send(dev)?;
67
68        Ok(())
69    }
70
71    fn receive<D, W>(
72        &mut self,
73        dev: &mut D,
74        out: &mut W,
75        checksum: ChecksumKind,
76    ) -> ModemResult<()>
77    where
78        D: Read + Write,
79        W: Write,
80    {
81        self.errors = 0;
82        self.checksum_mode = checksum;
83
84        dev.write_all(&[match self.checksum_mode {
85            ChecksumKind::Standard => Consts::NAK.into(),
86            ChecksumKind::Crc16 => Consts::CRC.into(),
87        }])?;
88
89        let mut packet_num: u8 = 1;
90        loop {
91            match get_byte_timeout(dev)?.map(Consts::from) {
92                bt @ Some(Consts::SOH | Consts::STX) => {
93                    // Handle next packet
94                    let packet_size = match bt {
95                        Some(Consts::SOH) => 128,
96                        Some(Consts::STX) => 1024,
97                        _ => 0, // Why does the compiler need this?
98                    };
99                    let pnum = get_byte(dev)?; // specified packet number
100                    let pnum_1c = get_byte(dev)?; // same, 1's complemented
101                                                  // We'll respond with cancel later if the packet number is wrong
102                    let cancel_packet =
103                        packet_num != pnum || (255 - pnum) != pnum_1c;
104                    let mut data: Vec<u8> = Vec::new();
105                    data.resize(packet_size, 0);
106                    dev.read_exact(&mut data)?;
107                    let success = match self.checksum_mode {
108                        ChecksumKind::Standard => {
109                            let recv_checksum = get_byte(dev)?;
110                            calc_checksum(&data) == recv_checksum
111                        }
112                        ChecksumKind::Crc16 => {
113                            let recv_checksum = (u16::from(get_byte(dev)?)
114                                << 8)
115                                + u16::from(get_byte(dev)?);
116                            calc_crc(&data) == recv_checksum
117                        }
118                    };
119
120                    if cancel_packet {
121                        dev.write_all(&[Consts::CAN.into()])?;
122                        dev.write_all(&[Consts::CAN.into()])?;
123                        return Err(ModemError::Canceled);
124                    }
125                    if success {
126                        packet_num = packet_num.wrapping_add(1);
127                        dev.write_all(&[Consts::ACK.into()])?;
128                        out.write_all(&data)?;
129                    } else {
130                        dev.write_all(&[Consts::NAK.into()])?;
131                        self.errors += 1;
132                    }
133                }
134                #[allow(non_snake_case)]
135                Some(_EOT) => {
136                    // End of file
137                    dev.write_all(&[Consts::ACK.into()])?;
138                    break;
139                }
140                None => {
141                    self.errors += 1;
142                }
143            }
144            if self.errors >= self.max_errors {
145                dev.write_all(&[Consts::CAN.into()])?;
146                return Err(ModemError::ExhaustedRetries {
147                    errors: Box::from(self.errors),
148                });
149            }
150        }
151        Ok(())
152    }
153
154    fn init_send<D>(&mut self, dev: &mut D) -> ModemResult<()>
155    where
156        D: Read + Write,
157    {
158        let mut cancels = 0u32;
159        loop {
160            if let Some(c) = get_byte_timeout(dev)?.map(Consts::from) {
161                match c {
162                    Consts::NAK => {
163                        self.checksum_mode = ChecksumKind::Standard;
164                        return Ok(());
165                    }
166                    Consts::CRC => {
167                        self.checksum_mode = ChecksumKind::Crc16;
168                        return Ok(());
169                    }
170                    Consts::CAN => {
171                        cancels += 1;
172                    }
173                    _c => (),
174                }
175            }
176
177            self.errors += 1;
178
179            if cancels >= 2 {
180                return Err(ModemError::Canceled);
181            }
182
183            if self.errors >= self.max_errors {
184                // FIXME: Removed a unused 'if let' here. To be re-added?
185                return Err(ModemError::ExhaustedRetries {
186                    errors: Box::from(self.errors),
187                });
188            }
189        }
190    }
191
192    fn finish_send<D>(&mut self, dev: &mut D) -> ModemResult<()>
193    where
194        D: Read + Write,
195    {
196        loop {
197            dev.write_all(&[Consts::EOT.into()])?;
198
199            if let Some(c) = get_byte_timeout(dev)? {
200                // Appease Clippy with this conditional black.
201                #[allow(clippy::redundant_else)]
202                if c == Consts::ACK.into() {
203                    return Ok(());
204                }
205            };
206
207            self.errors += 1;
208
209            if self.errors >= self.max_errors {
210                return Err(ModemError::ExhaustedRetries {
211                    errors: Box::from(self.errors),
212                });
213            }
214        }
215    }
216
217    fn send_stream<D, R>(&mut self, dev: &mut D, inp: &mut R) -> ModemResult<()>
218    where
219        D: Read + Write,
220        R: Read,
221    {
222        let mut block_num = 0u32;
223        loop {
224            let mut buff = vec![self.pad_byte; self.block_length as usize + 3];
225            let n = inp.read(&mut buff[3..])?;
226            if n == 0 {
227                return Ok(());
228            }
229
230            block_num += 1;
231            buff[0] = match self.block_length {
232                BlockLengthKind::Standard => Consts::SOH.into(),
233                BlockLengthKind::OneK => Consts::STX.into(),
234            };
235            buff[1] = (&block_num & 0xFF) as u8;
236            buff[2] = 0xFF - &buff[1];
237
238            match self.checksum_mode {
239                ChecksumKind::Standard => {
240                    let checksum = calc_checksum(&buff[3..]);
241                    buff.push(checksum);
242                }
243                ChecksumKind::Crc16 => {
244                    let crc = calc_crc(&buff[3..]);
245                    buff.push(((crc >> 8) & 0xFF) as u8);
246                    buff.push((&crc & 0xFF) as u8);
247                }
248            }
249
250            dev.write_all(&buff)?;
251
252            if let Some(c) = get_byte_timeout(dev)? {
253                if c == Consts::ACK.into() {
254                    continue;
255                }
256                // TODO handle CAN bytes
257            }
258
259            self.errors += 1;
260
261            if self.errors >= self.max_errors {
262                return Err(ModemError::ExhaustedRetries {
263                    errors: Box::from(self.errors),
264                });
265            }
266        }
267    }
268}