1use crate::io::uart::Uart;
6use anyhow::Result;
7use std::io::{Read, Write};
8use thiserror::Error;
9
10#[derive(Debug, Error)]
11pub enum XmodemError {
12 #[error("Cancelled")]
13 Cancelled,
14 #[error("Exhausted retries: {0}")]
15 ExhaustedRetries(usize),
16 #[error("Unsupported mode: {0}")]
17 UnsupportedMode(String),
18}
19
20#[derive(Debug, Clone, Copy)]
21#[repr(usize)]
22pub enum XmodemBlock {
23 Block128 = 128,
24 Block1k = 1024,
25}
26
27#[derive(Debug)]
28pub struct Xmodem {
29 pub max_errors: usize,
30 pub pad_byte: u8,
31 pub block_len: XmodemBlock,
32}
33
34impl Default for Xmodem {
35 fn default() -> Self {
36 Self::new()
37 }
38}
39
40impl Xmodem {
41 const POLYNOMIAL: u16 = 0x1021;
42 const CRC: u8 = 0x43;
43 const SOH: u8 = 0x01;
44 const STX: u8 = 0x02;
45 const EOF: u8 = 0x04;
46 const ACK: u8 = 0x06;
47 const NAK: u8 = 0x15;
48 const CAN: u8 = 0x18;
49
50 pub fn new() -> Self {
51 Xmodem {
52 max_errors: 16,
53 pad_byte: 0xff,
54 block_len: XmodemBlock::Block1k,
55 }
56 }
57
58 fn crc16(buf: &[u8]) -> u16 {
59 let mut crc = 0u16;
60 for byte in buf {
61 crc ^= (*byte as u16) << 8;
62 for _bit in 0..8 {
63 let msb = crc & 0x8000 != 0;
64 crc <<= 1;
65 if msb {
66 crc ^= Self::POLYNOMIAL;
67 }
68 }
69 }
70 crc
71 }
72
73 pub fn send(&self, uart: &dyn Uart, data: impl Read) -> Result<()> {
74 self.send_start(uart)?;
75 self.send_data(uart, data)?;
76 self.send_finish(uart)?;
77 Ok(())
78 }
79
80 fn send_start(&self, uart: &dyn Uart) -> Result<()> {
81 let mut ch = 0u8;
82 let mut cancels = 0usize;
83 loop {
85 uart.read(std::slice::from_mut(&mut ch))?;
86 match ch {
87 Self::CRC => {
88 return Ok(());
89 }
90 Self::NAK => {
91 return Err(XmodemError::UnsupportedMode("standard checksums".into()).into());
92 }
93 Self::CAN => {
94 cancels += 1;
95 if cancels >= 2 {
96 return Err(XmodemError::Cancelled.into());
97 }
98 }
99 _ => {
100 let p = ch as char;
101 log::info!(
102 "Unknown byte received while waiting for XMODEM start: {p:?} ({ch:#x?})"
103 );
104 }
105 }
106 }
107 }
108
109 fn send_data(&self, uart: &dyn Uart, mut data: impl Read) -> Result<()> {
110 let mut block = 0usize;
111 let mut errors = 0usize;
112 loop {
113 block += 1;
114 let mut buf = vec![self.pad_byte; self.block_len as usize + 3];
115 let n = data.read(&mut buf[3..])?;
116 if n == 0 {
117 break;
118 }
119
120 buf[0] = match self.block_len {
121 XmodemBlock::Block128 => Self::SOH,
122 XmodemBlock::Block1k => Self::STX,
123 };
124 buf[1] = block as u8;
125 buf[2] = 255 - buf[1];
126 let crc = Self::crc16(&buf[3..]);
127 buf.push((crc >> 8) as u8);
128 buf.push((crc & 0xFF) as u8);
129 log::info!("Sending block {block}");
130
131 let mut cancels = 0usize;
132 loop {
133 uart.write(&buf)?;
134 let mut ch = 0u8;
135 uart.read(std::slice::from_mut(&mut ch))?;
136 match ch {
137 Self::ACK => break,
138 Self::NAK => {
139 log::info!("XMODEM send got NAK. Retrying.");
140 errors += 1;
141 }
142 Self::CAN => {
143 cancels += 1;
144 if cancels >= 2 {
145 return Err(XmodemError::Cancelled.into());
146 }
147 }
148 _ => {
149 log::info!("Expected ACK. Got {ch:#x}.");
150 errors += 1;
151 }
152 }
153 if errors >= self.max_errors {
154 return Err(XmodemError::ExhaustedRetries(errors).into());
155 }
156 }
157 }
158 Ok(())
159 }
160
161 fn send_finish(&self, uart: &dyn Uart) -> Result<()> {
162 uart.write(&[Self::EOF])?;
163 let mut ch = 0u8;
164 uart.read(std::slice::from_mut(&mut ch))?;
165 if ch != Self::ACK {
166 log::info!("Expected ACK. Got {ch:#x}.");
167 }
168 Ok(())
169 }
170
171 pub fn receive(&self, uart: &dyn Uart, data: &mut impl Write) -> Result<()> {
172 uart.write(&[Self::CRC])?;
174
175 let mut block = 1u8;
176 let mut errors = 0usize;
177 loop {
178 let mut byte = 0u8;
180 uart.read(std::slice::from_mut(&mut byte))?;
181 let block_len = match byte {
182 Self::SOH => 128,
183 Self::STX => 1024,
184 Self::EOF => {
185 uart.write(&[Self::ACK])?;
187 break;
188 }
189 _ => {
190 return Err(XmodemError::UnsupportedMode(format!(
191 "bad start of packet: {byte:?}"
192 ))
193 .into());
194 }
195 };
196
197 let mut bnum = 0u8;
199 let mut bcom = 0u8;
200 uart.read(std::slice::from_mut(&mut bnum))?;
201 uart.read(std::slice::from_mut(&mut bcom))?;
202 let cancel = block != bnum || bnum != 255 - bcom;
203
204 let mut buffer = vec![0; block_len];
206 let mut total = 0;
207 while total < block_len {
208 let n = uart.read(&mut buffer[total..])?;
209 total += n;
210 }
211
212 let mut crc1 = 0u8;
214 let mut crc2 = 0u8;
215 uart.read(std::slice::from_mut(&mut crc1))?;
216 uart.read(std::slice::from_mut(&mut crc2))?;
217 let crc = u16::from_be_bytes([crc1, crc2]);
218
219 if cancel {
221 uart.write(&[Self::CAN, Self::CAN])?;
222 return Err(XmodemError::Cancelled.into());
223 }
224 if Self::crc16(&buffer) == crc {
225 uart.write(&[Self::ACK])?;
227 data.write_all(&buffer)?;
228 block = block.wrapping_add(1);
229 } else {
230 uart.write(&[Self::NAK])?;
231 errors += 1;
232 }
233 if errors >= self.max_errors {
234 return Err(XmodemError::ExhaustedRetries(errors).into());
235 }
236 }
237 Ok(())
238 }
239}
240
241#[cfg(test)]
245mod test {
246 use super::*;
247 use crate::util::testing::{ChildUart, TransferState};
248 use crate::util::tmpfilename;
249
250 #[rustfmt::skip]
251 const GETTYSBURG: &str =
252r#"Four score and seven years ago our fathers brought forth on this
253continent, a new nation, conceived in Liberty, and dedicated to the
254proposition that all men are created equal.
255Now we are engaged in a great civil war, testing whether that nation,
256or any nation so conceived and so dedicated, can long endure. We are met
257on a great battle-field of that war. We have come to dedicate a portion
258of that field, as a final resting place for those who here gave their
259lives that that nation might live. It is altogether fitting and proper
260that we should do this.
261But, in a larger sense, we can not dedicate -- we can not consecrate --
262we can not hallow -- this ground. The brave men, living and dead, who
263struggled here, have consecrated it, far above our poor power to add or
264detract. The world will little note, nor long remember what we say here,
265but it can never forget what they did here. It is for us the living,
266rather, to be dedicated here to the unfinished work which they who
267fought here have thus far so nobly advanced. It is rather for us to be
268here dedicated to the great task remaining before us -- that from these
269honored dead we take increased devotion to that cause for which they gave
270the last full measure of devotion -- that we here highly resolve that
271these dead shall not have died in vain -- that this nation, under God,
272shall have a new birth of freedom -- and that government of the people,
273by the people, for the people, shall not perish from the earth.
274Abraham Lincoln
275November 19, 1863
276"#;
277
278 #[test]
279 fn test_xmodem_send() -> Result<()> {
280 let filename = tmpfilename("test_xmodem_send");
281 let child = ChildUart::spawn(&["rx", "--with-crc", &filename])?;
282 let xmodem = Xmodem::new();
283 let gettysburg = GETTYSBURG.as_bytes();
284 xmodem.send(&child, gettysburg)?;
285 assert!(child.wait()?.success());
286 let result = std::fs::read(&filename)?;
287 assert_eq!(result.len() % 1024, 0);
289 assert!(result.len() >= gettysburg.len());
290 assert_eq!(&result[..gettysburg.len()], gettysburg);
291 Ok(())
292 }
293
294 #[test]
295 fn test_xmodem_send_with_errors() -> Result<()> {
296 let filename = tmpfilename("test_xmodem_send_with_errors");
297 let child = ChildUart::spawn_corrupt(
298 &["rx", "--with-crc", &filename],
299 TransferState::default(),
300 TransferState::new(&[3, 136]),
301 )?;
302 let xmodem = Xmodem {
303 max_errors: 2,
304 pad_byte: 0,
305 block_len: XmodemBlock::Block128,
306 };
307 let gettysburg = GETTYSBURG.as_bytes();
308 let err = xmodem.send(&child, gettysburg);
309 assert!(err.is_err());
310 assert_eq!(err.unwrap_err().to_string(), "Exhausted retries: 2");
311 Ok(())
312 }
313
314 #[test]
315 fn test_xmodem_checksum_mode() -> Result<()> {
316 let filename = tmpfilename("test_xmodem_checksum_mode");
317 let child = ChildUart::spawn(&["rx", &filename])?;
318 let xmodem = Xmodem::new();
319 let gettysburg = GETTYSBURG.as_bytes();
320 let result = xmodem.send(&child, gettysburg);
321 assert!(!child.wait()?.success());
322 assert!(result.is_err());
323 let err = result.unwrap_err().downcast::<XmodemError>().unwrap();
324 assert_eq!(err.to_string(), "Unsupported mode: standard checksums");
325 Ok(())
326 }
327
328 #[test]
329 fn test_xmodem_recv() -> Result<()> {
330 let filename = tmpfilename("test_xmodem_recv");
331 let gettysburg = GETTYSBURG.as_bytes();
332 std::fs::write(&filename, gettysburg)?;
333 let child = ChildUart::spawn(&["sx", &filename])?;
334 let xmodem = Xmodem::new();
335 let mut result = Vec::new();
336 xmodem.receive(&child, &mut result)?;
337 assert!(child.wait()?.success());
338 assert_eq!(result.len() % 128, 0);
340 assert!(result.len() >= gettysburg.len());
341 assert_eq!(&result[..gettysburg.len()], gettysburg);
342 Ok(())
343 }
344
345 #[test]
346 fn test_xmodem1k_recv() -> Result<()> {
347 let filename = tmpfilename("test_xmodem1k_recv");
348 let gettysburg = GETTYSBURG.as_bytes();
349 std::fs::write(&filename, gettysburg)?;
350 let child = ChildUart::spawn(&["sx", "--1k", &filename])?;
351 let xmodem = Xmodem::new();
352 let mut result = Vec::new();
353 xmodem.receive(&child, &mut result)?;
354 assert!(child.wait()?.success());
355 assert_eq!(result.len() % 128, 0);
359 assert!(result.len() >= gettysburg.len());
360 assert_eq!(&result[..gettysburg.len()], gettysburg);
361 Ok(())
362 }
363
364 #[test]
365 fn test_xmodem_recv_with_errors() -> Result<()> {
366 let filename = tmpfilename("test_xmodem_recv_with_errors");
367 let gettysburg = GETTYSBURG.as_bytes();
368 std::fs::write(&filename, gettysburg)?;
369 let child = ChildUart::spawn_corrupt(
370 &["sx", &filename],
371 TransferState::new(&[3, 136]),
372 TransferState::default(),
373 )?;
374 let xmodem = Xmodem {
375 max_errors: 2,
376 pad_byte: 0,
377 block_len: XmodemBlock::Block128,
378 };
379 let mut result = Vec::new();
380 let err = xmodem.receive(&child, &mut result);
381 assert!(err.is_err());
382 assert_eq!(err.unwrap_err().to_string(), "Exhausted retries: 2");
383 Ok(())
384 }
385
386 #[test]
387 fn test_xmodem_recv_with_cancel() -> Result<()> {
388 let filename = tmpfilename("test_xmodem_recv_with_cancel");
389 let gettysburg = GETTYSBURG.as_bytes();
390 std::fs::write(&filename, gettysburg)?;
391 let child = ChildUart::spawn_corrupt(
392 &["sx", &filename],
393 TransferState::new(&[1, 134]),
394 TransferState::default(),
395 )?;
396 let xmodem = Xmodem {
397 max_errors: 2,
398 pad_byte: 0,
399 block_len: XmodemBlock::Block128,
400 };
401 let mut result = Vec::new();
402 let err = xmodem.receive(&child, &mut result);
403 assert!(err.is_err());
404 assert_eq!(err.unwrap_err().to_string(), "Cancelled");
405 Ok(())
406 }
407}