Skip to main content

tinyboot_protocol/
frame.rs

1use core::mem::MaybeUninit;
2
3use crate::crc::{CRC_INIT, crc16};
4use crate::sync::Sync;
5use crate::{Cmd, ReadError, Status};
6
7/// Maximum data payload size per frame.
8pub const MAX_PAYLOAD: usize = 64;
9
10/// Typed Info response data.
11///
12/// Packed to keep alignment ≤ 2 so the `Data` union doesn't force padding
13/// inside `Frame` (data starts at offset 10, not 4-byte aligned).
14#[repr(C, packed)]
15#[derive(Clone, Copy)]
16pub struct InfoData {
17    /// App region capacity in bytes.
18    pub capacity: u32,
19    /// Erase page size in bytes.
20    pub erase_size: u16,
21    /// Boot version (packed 5.5.6, `0xFFFF` = none).
22    pub boot_version: u16,
23    /// App version (packed 5.5.6, `0xFFFF` = none).
24    pub app_version: u16,
25    /// 0 = bootloader, 1 = app.
26    pub mode: u16,
27}
28
29/// Typed Erase request data.
30#[repr(C)]
31#[derive(Clone, Copy)]
32pub struct EraseData {
33    /// Number of bytes to erase (must be aligned to erase size).
34    pub byte_count: u16,
35}
36
37/// Typed Verify response data.
38#[repr(C)]
39#[derive(Clone, Copy)]
40pub struct VerifyData {
41    /// CRC16 of the app region.
42    pub crc: u16,
43}
44
45/// Union-typed data payload.
46///
47/// Provides zero-cost typed access to frame data. Reading fields is unsafe
48/// because the caller must know which variant is active.
49#[repr(C)]
50pub union Data {
51    /// Raw byte access.
52    pub raw: [u8; MAX_PAYLOAD],
53    /// Info response fields.
54    pub info: InfoData,
55    /// Erase request fields.
56    pub erase: EraseData,
57    /// Verify response fields.
58    pub verify: VerifyData,
59}
60
61/// Wire frame: SYNC(2) + CMD(1) + STATUS(1) + ADDR(4) + LEN(2) + DATA(len) + CRC(2)
62///
63/// Used for both requests and responses. For requests, `status` is
64/// [`Status::Request`]. For responses, `cmd` and `addr` echo the request.
65///
66/// Single instance, reused each iteration of the protocol loop.
67#[repr(C)]
68pub struct Frame {
69    sync: Sync,
70    /// Command code.
71    pub cmd: Cmd,
72    /// Response status (always [`Status::Request`] for requests).
73    pub status: Status,
74    /// Flash address (for Write/Erase) or mode selector (for Reset).
75    pub addr: u32,
76    /// Data payload length in bytes (0..64).
77    pub len: u16,
78    /// Payload data (union-typed).
79    pub data: Data,
80    /// CRC16 over the frame body (little-endian).
81    pub crc: [u8; 2],
82}
83
84impl Default for Frame {
85    /// Create a default frame. Data buffer is uninitialized — `read()` or
86    /// caller writes populate it before use.
87    fn default() -> Self {
88        let frame: MaybeUninit<Self> = MaybeUninit::uninit();
89        let mut frame = unsafe { frame.assume_init() };
90        frame.sync = Sync::default();
91        frame.cmd = Cmd::Info;
92        frame.status = Status::Request;
93        frame.addr = 0;
94        frame.len = 0;
95        frame.crc = [0; 2];
96        frame
97    }
98}
99
100impl Frame {
101    fn as_bytes(&self, offset: usize, len: usize) -> &[u8] {
102        debug_assert!(offset + len <= core::mem::size_of::<Self>());
103        unsafe {
104            let ptr = (self as *const Self as *const u8).add(offset);
105            core::slice::from_raw_parts(ptr, len)
106        }
107    }
108
109    fn as_bytes_mut(&mut self, offset: usize, len: usize) -> &mut [u8] {
110        debug_assert!(offset + len <= core::mem::size_of::<Self>());
111        unsafe {
112            let ptr = (self as *mut Self as *mut u8).add(offset);
113            core::slice::from_raw_parts_mut(ptr, len)
114        }
115    }
116
117    /// Send the frame. Two `write_all` calls: body, then CRC.
118    pub fn send<W: embedded_io::Write>(&mut self, w: &mut W) -> Result<(), W::Error> {
119        self.sync = Sync::valid();
120        let body_len = 10 + self.len as usize;
121        self.crc = crc16(CRC_INIT, self.as_bytes(0, body_len)).to_le_bytes();
122        w.write_all(self.as_bytes(0, body_len))?;
123        w.write_all(&self.crc)
124    }
125
126    /// Read one frame from the transport.
127    ///
128    /// Syncs on preamble, reads header + payload, validates CRC.
129    /// Returns `Ok(Status::Ok)` on success, `Ok(Status::*)` for protocol
130    /// errors (CRC, invalid frame, overflow), `Err` only for transport IO.
131    pub fn read<R: embedded_io::Read>(&mut self, r: &mut R) -> Result<Status, ReadError> {
132        self.sync.read(r)?;
133
134        // Read header fields: cmd(1) + status(1) + addr(4) + len(2) = 8 bytes at offset 2
135        r.read_exact(self.as_bytes_mut(2, 8))
136            .map_err(|_| ReadError)?;
137
138        if !Cmd::is_valid(self.as_bytes(2, 1)[0]) || !Status::is_valid(self.as_bytes(3, 1)[0]) {
139            // Remaining payload + CRC bytes are left in the transport. The sync
140            // scanner will skip them as garbage on the next read(). Draining here
141            // is not feasible: the len field is untrusted and the payload could be
142            // up to 64 KB, which exceeds our buffer and time budget on small MCUs.
143            return Ok(Status::Unsupported);
144        }
145
146        let data_len = self.len as usize;
147
148        if data_len > MAX_PAYLOAD {
149            // Same as above — we cannot drain a payload larger than our buffer.
150            // The sync scanner recovers on the next frame.
151            return Ok(Status::PayloadOverflow);
152        }
153
154        // Read data directly into buffer
155        if data_len > 0 {
156            r.read_exact(unsafe { &mut self.data.raw[..data_len] })
157                .map_err(|_| ReadError)?;
158        }
159
160        // Read CRC directly — [u8; 2] has no alignment constraint
161        r.read_exact(&mut self.crc).map_err(|_| ReadError)?;
162
163        // Validate CRC over body
164        if self.crc != crc16(CRC_INIT, self.as_bytes(0, 10 + data_len)).to_le_bytes() {
165            return Ok(Status::CrcMismatch);
166        }
167
168        Ok(Status::Ok)
169    }
170
171    /// Async version of [`send`](Self::send).
172    pub async fn send_async<W: embedded_io_async::Write>(
173        &mut self,
174        w: &mut W,
175    ) -> Result<(), W::Error> {
176        self.sync = Sync::valid();
177        let body_len = 10 + self.len as usize;
178        self.crc = crc16(CRC_INIT, self.as_bytes(0, body_len)).to_le_bytes();
179        w.write_all(self.as_bytes(0, body_len)).await?;
180        w.write_all(&self.crc).await
181    }
182
183    /// Async version of [`read`](Self::read).
184    pub async fn read_async<R: embedded_io_async::Read>(
185        &mut self,
186        r: &mut R,
187    ) -> Result<Status, ReadError> {
188        self.sync.read_async(r).await?;
189
190        r.read_exact(self.as_bytes_mut(2, 8))
191            .await
192            .map_err(|_| ReadError)?;
193
194        if !Cmd::is_valid(self.as_bytes(2, 1)[0]) || !Status::is_valid(self.as_bytes(3, 1)[0]) {
195            // See sync read() for rationale on not draining here.
196            return Ok(Status::Unsupported);
197        }
198
199        let data_len = self.len as usize;
200
201        if data_len > MAX_PAYLOAD {
202            // See sync read() for rationale on not draining here.
203            return Ok(Status::PayloadOverflow);
204        }
205
206        if data_len > 0 {
207            r.read_exact(unsafe { &mut self.data.raw[..data_len] })
208                .await
209                .map_err(|_| ReadError)?;
210        }
211
212        r.read_exact(&mut self.crc).await.map_err(|_| ReadError)?;
213
214        if self.crc != crc16(CRC_INIT, self.as_bytes(0, 10 + data_len)).to_le_bytes() {
215            return Ok(Status::CrcMismatch);
216        }
217
218        Ok(Status::Ok)
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    struct MockReader<'a> {
227        data: &'a [u8],
228        pos: usize,
229    }
230
231    impl<'a> MockReader<'a> {
232        fn new(data: &'a [u8]) -> Self {
233            Self { data, pos: 0 }
234        }
235    }
236
237    impl embedded_io::ErrorType for MockReader<'_> {
238        type Error = core::convert::Infallible;
239    }
240
241    impl embedded_io::Read for MockReader<'_> {
242        fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
243            let n = buf.len().min(self.data.len() - self.pos);
244            buf[..n].copy_from_slice(&self.data[self.pos..self.pos + n]);
245            self.pos += n;
246            Ok(n)
247        }
248    }
249
250    struct Sink {
251        buf: [u8; 512],
252        pos: usize,
253    }
254
255    impl Sink {
256        fn new() -> Self {
257            Self {
258                buf: [0; 512],
259                pos: 0,
260            }
261        }
262        fn written(&self) -> &[u8] {
263            &self.buf[..self.pos]
264        }
265    }
266
267    impl embedded_io::ErrorType for Sink {
268        type Error = core::convert::Infallible;
269    }
270
271    impl embedded_io::Write for Sink {
272        fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
273            let n = buf.len().min(self.buf.len() - self.pos);
274            self.buf[self.pos..self.pos + n].copy_from_slice(&buf[..n]);
275            self.pos += n;
276            Ok(n)
277        }
278        fn flush(&mut self) -> Result<(), Self::Error> {
279            Ok(())
280        }
281    }
282
283    fn frame(cmd: Cmd, status: Status, addr: u32, data: &[u8]) -> Frame {
284        let mut f = Frame {
285            cmd,
286            status,
287            addr,
288            len: data.len() as u16,
289            ..Default::default()
290        };
291        unsafe { f.data.raw[..data.len()].copy_from_slice(data) };
292        f
293    }
294
295    #[test]
296    fn request_round_trip() {
297        let mut frame = frame(Cmd::Write, Status::Request, 0x0800, &[0xDE, 0xAD]);
298
299        let mut sink = Sink::new();
300        frame.send(&mut sink).unwrap();
301
302        let mut frame2 = Frame::default();
303        frame2.read(&mut MockReader::new(sink.written())).unwrap();
304        assert_eq!(frame2.cmd, Cmd::Write);
305        assert_eq!(frame2.len, 2);
306        assert_eq!(frame2.addr, 0x0800);
307        assert_eq!(frame2.status, Status::Request);
308        assert_eq!(unsafe { &frame2.data.raw[..2] }, &[0xDE, 0xAD]);
309        assert_eq!(frame2.crc, frame.crc);
310    }
311
312    #[test]
313    fn response_round_trip() {
314        let mut frame = frame(Cmd::Verify, Status::Ok, 0, &[0x12, 0x34]);
315
316        let mut sink = Sink::new();
317        frame.send(&mut sink).unwrap();
318
319        let mut frame2 = Frame::default();
320        frame2.read(&mut MockReader::new(sink.written())).unwrap();
321        assert_eq!(frame2.cmd, Cmd::Verify);
322        assert_eq!(frame2.status, Status::Ok);
323        assert_eq!(unsafe { &frame2.data.raw[..2] }, &[0x12, 0x34]);
324    }
325
326    #[test]
327    fn request_no_data() {
328        let mut frame = frame(Cmd::Erase, Status::Request, 0, &[]);
329
330        let mut sink = Sink::new();
331        frame.send(&mut sink).unwrap();
332
333        // Frame: SYNC(2) + CMD(1) + STATUS(1) + ADDR(4) + LEN(2) + CRC(2) = 12
334        assert_eq!(sink.written().len(), 12);
335
336        let mut frame2 = Frame::default();
337        frame2.read(&mut MockReader::new(sink.written())).unwrap();
338        assert_eq!(frame2.cmd, Cmd::Erase);
339        assert_eq!(frame2.len, 0);
340    }
341
342    #[test]
343    fn large_addr_round_trip() {
344        let mut frame = frame(Cmd::Write, Status::Request, 0x0001_0800, &[0xAB]);
345
346        let mut sink = Sink::new();
347        frame.send(&mut sink).unwrap();
348
349        let mut frame2 = Frame::default();
350        frame2.read(&mut MockReader::new(sink.written())).unwrap();
351        assert_eq!(frame2.addr, 0x0001_0800);
352    }
353
354    #[test]
355    fn cmd_addr_carry_over() {
356        let mut frame = frame(Cmd::Write, Status::Request, 0x0400, &[0xAB, 0xCD]);
357
358        let mut sink = Sink::new();
359        frame.send(&mut sink).unwrap();
360
361        // "Device" reads the request
362        let mut dev = Frame::default();
363        dev.read(&mut MockReader::new(sink.written())).unwrap();
364
365        // Device sends response — cmd and addr carry over
366        dev.status = Status::Ok;
367        dev.len = 0;
368        let mut resp_sink = Sink::new();
369        dev.send(&mut resp_sink).unwrap();
370
371        // Host reads response
372        let mut host = Frame::default();
373        host.read(&mut MockReader::new(resp_sink.written()))
374            .unwrap();
375        assert_eq!(host.cmd, Cmd::Write);
376        assert_eq!(host.addr, 0x0400);
377        assert_eq!(host.status, Status::Ok);
378    }
379
380    #[test]
381    fn read_bad_cmd() {
382        let mut frame = frame(Cmd::Info, Status::Request, 0, &[]);
383
384        let mut sink = Sink::new();
385        frame.send(&mut sink).unwrap();
386        sink.buf[2] ^= 0xFF; // corrupt CMD byte
387
388        let mut frame2 = Frame::default();
389        assert_eq!(
390            frame2.read(&mut MockReader::new(sink.written())),
391            Ok(Status::Unsupported)
392        );
393    }
394
395    #[test]
396    fn read_after_garbage() {
397        let mut frame = frame(Cmd::Verify, Status::Request, 0, &[]);
398
399        let mut sink = Sink::new();
400        frame.send(&mut sink).unwrap();
401        let frame_len = sink.pos;
402
403        let mut input = [0u8; 4 + 512];
404        input[..4].copy_from_slice(&[0xFF, 0x00, 0xAA, 0x42]);
405        input[4..4 + frame_len].copy_from_slice(&sink.buf[..frame_len]);
406
407        let mut frame2 = Frame::default();
408        assert_eq!(
409            frame2.read(&mut MockReader::new(&input[..4 + frame_len])),
410            Ok(Status::Ok)
411        );
412        assert_eq!(frame2.cmd, Cmd::Verify);
413    }
414
415    #[test]
416    fn read_overflow() {
417        // Build a valid frame, then patch the LEN field to exceed MAX_PAYLOAD.
418        let mut f = frame(Cmd::Write, Status::Request, 0, &[]);
419
420        let mut sink = Sink::new();
421        f.send(&mut sink).unwrap();
422
423        // LEN is at offset 8..10 (little-endian u16). Set to MAX_PAYLOAD + 1.
424        let overflow_len = (MAX_PAYLOAD as u16 + 1).to_le_bytes();
425        sink.buf[8] = overflow_len[0];
426        sink.buf[9] = overflow_len[1];
427
428        let mut frame2 = Frame::default();
429        assert_eq!(
430            frame2.read(&mut MockReader::new(sink.written())),
431            Ok(Status::PayloadOverflow)
432        );
433    }
434}