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