Skip to main content

uboot_shell/
ymodem.rs

1//! YMODEM file transfer protocol implementation.
2//!
3//! This module implements the YMODEM protocol for file transfers over serial connections.
4//! YMODEM is commonly used by U-Boot's `loady` command.
5//!
6//! ## Protocol Overview
7//!
8//! YMODEM transfers files in 128 or 1024 byte blocks with CRC16 error checking.
9//! The protocol supports:
10//!
11//! - File name and size transmission in the first block
12//! - Automatic block size selection (128 or 1024 bytes)
13//! - CRC16-CCITT or checksum error detection
14//! - Retry mechanism for failed transmissions
15
16use std::io::*;
17
18use crate::crc::crc16_ccitt;
19
20/// Start of Header - 128 byte block
21const SOH: u8 = 0x01;
22/// Start of Text - 1024 byte block
23const STX: u8 = 0x02;
24/// End of Transmission
25const EOT: u8 = 0x04;
26/// Acknowledge
27const ACK: u8 = 0x06;
28/// Negative Acknowledge
29const NAK: u8 = 0x15;
30// const CAN: u8 = 0x18; // Cancel
31/// End of File padding character
32const EOF: u8 = 0x1A;
33/// CRC mode request character
34const CRC: u8 = 0x43;
35
36/// YMODEM protocol handler for file transfers.
37///
38/// Implements the YMODEM protocol for sending files over serial connections.
39/// Supports both CRC16 and checksum modes.
40pub struct Ymodem {
41    /// Whether to use CRC16 mode (true) or checksum mode (false)
42    crc_mode: bool,
43    /// Current block number
44    blk: u8,
45    /// Number of remaining retry attempts
46    retries: usize,
47}
48
49impl Ymodem {
50    /// Creates a new YMODEM sender.
51    ///
52    /// # Arguments
53    ///
54    /// * `crc_mode` - Whether to start in CRC16 mode (`true`) or checksum mode (`false`)
55    pub fn new(crc_mode: bool) -> Self {
56        Self {
57            crc_mode,
58            blk: 0,
59            retries: 10,
60        }
61    }
62
63    fn nak(&self) -> u8 {
64        if self.crc_mode { CRC } else { NAK }
65    }
66
67    fn getc<D: Read>(&mut self, dev: &mut D) -> Result<u8> {
68        let mut buff = [0u8; 1];
69        dev.read_exact(&mut buff)?;
70        Ok(buff[0])
71    }
72
73    fn wait_for_start<D: Read>(&mut self, dev: &mut D) -> Result<()> {
74        loop {
75            match self.getc(dev)? {
76                NAK => {
77                    self.crc_mode = false;
78                    return Ok(());
79                }
80                CRC => {
81                    self.crc_mode = true;
82                    return Ok(());
83                }
84                _ => {}
85            }
86        }
87    }
88
89    /// Sends a file over the YMODEM protocol.
90    ///
91    /// # Arguments
92    ///
93    /// * `dev` - The device implementing `Read + Write` (serial stream)
94    /// * `file` - The readable file stream
95    /// * `name` - File name reported to the receiver
96    /// * `size` - File size in bytes
97    /// * `on_progress` - Callback invoked with the total bytes sent so far
98    ///
99    /// # Errors
100    ///
101    /// Returns any I/O error from the underlying device or file stream.
102    pub fn send<D: Write + Read, F: Read>(
103        &mut self,
104        dev: &mut D,
105        file: &mut F,
106        name: &str,
107        size: usize,
108        on_progress: impl Fn(usize),
109    ) -> Result<()> {
110        info!("Sending file: {name}");
111
112        self.send_header(dev, name, size)?;
113
114        let mut buff = [0u8; 1024];
115        let mut send_size = 0;
116
117        while let Ok(n) = file.read(&mut buff) {
118            if n == 0 {
119                break;
120            }
121            self.send_blk(dev, &buff[..n], EOF, false)?;
122            send_size += n;
123            on_progress(send_size);
124        }
125
126        dev.write_all(&[EOT])?;
127        dev.flush()?;
128        self.wait_ack(dev)?;
129
130        self.send_blk(dev, &[0], 0, true)?;
131
132        self.wait_for_start(dev)?;
133        Ok(())
134    }
135
136    fn wait_ack<D: Read>(&mut self, dev: &mut D) -> Result<()> {
137        let nak = self.nak();
138        loop {
139            let c = self.getc(dev)?;
140            match c {
141                ACK => return Ok(()),
142                _ => {
143                    if c == nak {
144                        return Err(Error::new(ErrorKind::BrokenPipe, "NAK"));
145                    }
146                    stdout().write_all(&[c])?;
147                }
148            }
149        }
150    }
151
152    fn send_header<D: Write + Read>(&mut self, dev: &mut D, name: &str, size: usize) -> Result<()> {
153        let mut buff = Vec::new();
154
155        buff.append(&mut name.as_bytes().to_vec());
156
157        buff.push(0);
158
159        buff.append(&mut format!("{}", size).as_bytes().to_vec());
160
161        buff.push(0);
162
163        self.send_blk(dev, &buff, 0, false)
164    }
165
166    fn send_blk<D: Write + Read>(
167        &mut self,
168        dev: &mut D,
169        data: &[u8],
170        pad: u8,
171        last: bool,
172    ) -> Result<()> {
173        let len;
174        let p;
175
176        if data.len() > 128 {
177            len = 1024;
178            p = STX;
179        } else {
180            len = 128;
181            p = SOH;
182        }
183        let blk = if last { 0 } else { self.blk };
184        let mut err = None;
185        loop {
186            if self.retries == 0 {
187                return Err(err.unwrap_or(Error::new(ErrorKind::BrokenPipe, "retry too much")));
188            }
189
190            dev.write_all(&[p, blk, !blk])?;
191
192            let mut buf = vec![pad; len];
193            buf[..data.len()].copy_from_slice(data);
194
195            dev.write_all(&buf)?;
196
197            if self.crc_mode {
198                let chsum = crc16_ccitt(0, &buf);
199                let crc1 = (chsum >> 8) as u8;
200                let crc2 = (chsum & 0xff) as u8;
201
202                dev.write_all(&[crc1, crc2])?;
203            }
204            dev.flush()?;
205
206            match self.wait_ack(dev) {
207                Ok(_) => break,
208                Err(e) => {
209                    err = Some(e);
210                    self.retries -= 1;
211                }
212            }
213        }
214
215        if self.blk == u8::MAX {
216            self.blk = 0;
217        } else {
218            self.blk += 1;
219        }
220
221        Ok(())
222    }
223}