Skip to main content

tinyboot_core/
protocol.rs

1use crate::ringbuf::RingBuf;
2use crate::traits::boot::{BootCtl, BootMetaStore, Platform, Storage, Transport};
3use crate::traits::{BootMode, BootState};
4use tinyboot_protocol::crc::{CRC_INIT, crc16};
5use tinyboot_protocol::frame::{Frame, InfoData, VerifyData};
6use tinyboot_protocol::{Cmd, ReadError, Status};
7
8/// Protocol dispatcher with write buffering.
9///
10/// Incoming write data is accumulated in a ring buffer and flushed to storage
11/// in page-sized chunks using fast page programming. The host must send a
12/// `Flush` command to commit any remaining partial page before `Verify`,
13/// or before skipping to a non-sequential address.
14pub struct Dispatcher<'a, T: Transport, S: Storage, B: BootMetaStore, C: BootCtl, const BUF: usize>
15{
16    /// Mutable reference to the platform peripherals.
17    pub platform: &'a mut Platform<T, S, B, C>,
18    /// Reusable frame buffer.
19    pub frame: Frame,
20    /// Write buffer. Sized for 2 × page size.
21    buf: RingBuf<BUF>,
22    /// Expected address of the next sequential write. `None` = accept any.
23    next_addr: Option<u32>,
24}
25
26impl<'a, T: Transport, S: Storage, B: BootMetaStore, C: BootCtl, const BUF: usize>
27    Dispatcher<'a, T, S, B, C, BUF>
28{
29    /// Create a new dispatcher for the given platform.
30    pub fn new(platform: &'a mut Platform<T, S, B, C>) -> Self {
31        Self {
32            platform,
33            frame: Frame::default(),
34            buf: RingBuf::default(),
35            next_addr: None,
36        }
37    }
38
39    /// Write `n` bytes from the buffer to storage, deriving the address from `next_addr`.
40    fn write_buf(&mut self, next: u32, n: usize) {
41        let addr = next - self.buf.len() as u32;
42        let data = self.buf.peek(n);
43        if self.platform.storage.write(addr, data).is_err() {
44            self.frame.status = Status::WriteError;
45        }
46        self.buf.consume(n);
47    }
48
49    /// Read a frame, dispatch the command, and send the response.
50    /// Returns Err only for transport IO errors. Frame-level errors
51    /// (bad CRC, invalid frame) are silently skipped.
52    pub fn dispatch(&mut self) -> Result<(), ReadError> {
53        let status = self.frame.read(&mut self.platform.transport)?;
54
55        if status != Status::Ok {
56            self.frame.len = 0;
57            self.frame.status = status;
58            return self
59                .frame
60                .send(&mut self.platform.transport)
61                .map_err(|_| ReadError);
62        }
63
64        let data_len = self.frame.len as usize;
65        let capacity = self.platform.storage.capacity() as u32;
66        let erase_size = S::ERASE_SIZE as u32;
67        let write_size = S::WRITE_SIZE as u32;
68        let state = self.platform.boot_meta.boot_state();
69        self.frame.len = 0;
70        self.frame.status = Status::Ok;
71
72        match self.frame.cmd {
73            Cmd::Info => {
74                self.frame.len = 12;
75                let app_sz = self.platform.boot_meta.app_size();
76                let app_ver = if app_sz != 0xFFFF_FFFF {
77                    // SAFETY: app_size != 0xFFFFFFFF means meta was previously written
78                    // by a Verify that validated app_size against capacity.
79                    let base = self.platform.storage.as_slice().as_ptr();
80                    unsafe { base.add(app_sz as usize - 2).cast::<u16>().read_volatile() }
81                } else {
82                    0xFFFF
83                };
84                let boot_version = crate::tinyboot_version();
85                self.frame.data.info = InfoData {
86                    capacity,
87                    erase_size: erase_size as u16,
88                    boot_version,
89                    app_version: app_ver,
90                    mode: 0,
91                };
92            }
93            Cmd::Erase => {
94                let addr = self.frame.addr;
95                let byte_count = unsafe { self.frame.data.erase }.byte_count as u32;
96                if !addr.is_multiple_of(erase_size)
97                    || !byte_count.is_multiple_of(erase_size)
98                    || byte_count == 0
99                    || addr + byte_count > capacity
100                {
101                    self.frame.status = Status::AddrOutOfBounds;
102                } else {
103                    // State transitions for erase
104                    match state {
105                        // Idle → Updating: step down state byte
106                        BootState::Idle => {
107                            if self.platform.boot_meta.advance().is_err() {
108                                self.frame.status = Status::WriteError;
109                            }
110                        }
111                        // Validating → Updating: app failed, reflashing
112                        BootState::Validating => {
113                            if self
114                                .platform
115                                .boot_meta
116                                .refresh(0xFFFF, BootState::Updating, 0xFFFF_FFFF)
117                                .is_err()
118                            {
119                                self.frame.status = Status::WriteError;
120                            }
121                        }
122                        // Updating → Updating: no state change
123                        BootState::Updating => {}
124                    }
125                    if self.frame.status == Status::Ok
126                        && self
127                            .platform
128                            .storage
129                            .erase(addr, addr + byte_count)
130                            .is_err()
131                    {
132                        self.frame.status = Status::WriteError;
133                    }
134                }
135            }
136            Cmd::Write => {
137                if state != BootState::Updating {
138                    self.frame.status = Status::Unsupported;
139                } else {
140                    let addr = self.frame.addr;
141                    if addr + data_len as u32 > capacity
142                        || (self.next_addr.is_none() && !addr.is_multiple_of(write_size))
143                        || self.next_addr.is_some_and(|n| n != addr)
144                    {
145                        self.frame.status = Status::AddrOutOfBounds;
146                    } else {
147                        // SAFETY: data_len <= MAX_PAYLOAD validated by frame.read()
148                        self.buf
149                            .push(unsafe { self.frame.data.raw.get_unchecked(..data_len) });
150                        let next = addr + data_len as u32;
151                        self.next_addr = Some(next);
152                        // Flush full page
153                        if self.buf.len() >= S::WRITE_SIZE {
154                            self.write_buf(next, S::WRITE_SIZE);
155                        }
156                    }
157                }
158            }
159            Cmd::Verify => {
160                if state != BootState::Updating {
161                    self.frame.status = Status::Unsupported;
162                } else {
163                    let app_size = self.frame.addr;
164                    let sz = app_size as usize;
165                    if sz == 0 || sz > capacity as usize {
166                        self.frame.status = Status::AddrOutOfBounds;
167                    } else {
168                        // SAFETY: sz bounds-checked against capacity above.
169                        let crc = crc16(CRC_INIT, unsafe {
170                            self.platform.storage.as_slice().get_unchecked(..sz)
171                        });
172                        self.frame.len = 2;
173                        self.frame.data.verify = VerifyData { crc };
174                        if self
175                            .platform
176                            .boot_meta
177                            .refresh(crc, BootState::Validating, app_size)
178                            .is_err()
179                        {
180                            self.frame.status = Status::WriteError;
181                        }
182                    }
183                }
184            }
185            Cmd::Reset => {
186                let _ = self.frame.send(&mut self.platform.transport);
187                let mode = if self.frame.addr == 1 {
188                    BootMode::Bootloader
189                } else {
190                    BootMode::App
191                };
192                self.platform.ctl.system_reset(mode);
193            }
194            Cmd::Flush => {
195                if let Some(next) = self.next_addr {
196                    if !self.buf.is_empty() {
197                        self.write_buf(next, self.buf.len());
198                    }
199                    self.next_addr = None;
200                }
201            }
202        }
203
204        self.frame
205            .send(&mut self.platform.transport)
206            .map_err(|_| ReadError)
207    }
208}