Skip to main content

mcumgr_toolkit/transport/
mod.rs

1use std::time::Duration;
2
3use miette::Diagnostic;
4use thiserror::Error;
5
6use polonius_the_crab::prelude::*;
7
8/// Serial port based transport
9pub mod serial;
10
11/// BLE based transport
12#[cfg(feature = "ble")]
13pub mod ble;
14
15/// UDP based transport
16pub mod udp;
17
18#[derive(Debug, PartialEq, Clone, Copy)]
19struct SmpHeader {
20    ver: u8,
21    op: u8,
22    flags: u8,
23    data_length: u16,
24    group_id: u16,
25    sequence_num: u8,
26    command_id: u8,
27}
28
29impl SmpHeader {
30    fn from_bytes(data: [u8; SMP_HEADER_SIZE]) -> Self {
31        Self {
32            ver: (data[0] >> 3) & 0b11,
33            op: data[0] & 0b111,
34            flags: data[1],
35            data_length: u16::from_be_bytes([data[2], data[3]]),
36            group_id: u16::from_be_bytes([data[4], data[5]]),
37            sequence_num: data[6],
38            command_id: data[7],
39        }
40    }
41    fn to_bytes(self) -> [u8; SMP_HEADER_SIZE] {
42        let [length_0, length_1] = self.data_length.to_be_bytes();
43        let [group_id_0, group_id_1] = self.group_id.to_be_bytes();
44        [
45            ((self.ver & 0b11) << 3) | (self.op & 0b111),
46            self.flags,
47            length_0,
48            length_1,
49            group_id_0,
50            group_id_1,
51            self.sequence_num,
52            self.command_id,
53        ]
54    }
55}
56
57/// Size of the SMP header that precedes every frame's payload
58pub const SMP_HEADER_SIZE: usize = 8;
59
60/// The max size of an SMP body
61///
62/// Limited by the 'size' field in the header, which is a u16
63pub const SMP_BODY_MAX_SIZE: usize = u16::MAX as usize;
64
65/// The max size of a raw SMP frame
66pub const SMP_TRANSFER_BUFFER_SIZE: usize = SMP_HEADER_SIZE + SMP_BODY_MAX_SIZE;
67
68mod smp_op {
69    pub(super) const READ: u8 = 0;
70    pub(super) const READ_RSP: u8 = 1;
71    pub(super) const WRITE: u8 = 2;
72    pub(super) const WRITE_RSP: u8 = 3;
73}
74
75/// Error while sending a command request
76#[derive(Error, Debug, Diagnostic)]
77pub enum SendError {
78    /// A timeout occurred while sending
79    #[error("A timeout occurred")]
80    #[diagnostic(code(mcumgr_toolkit::transport::send::timeout))]
81    Timeout,
82    /// An error occurred in the underlying transport
83    #[error("Transport error")]
84    #[diagnostic(code(mcumgr_toolkit::transport::send::transport))]
85    TransportError(#[source] Box<dyn std::error::Error + Sync + Send + 'static>),
86    /// Unable to send data because it is too big
87    #[error("Given data slice was too big")]
88    #[diagnostic(code(mcumgr_toolkit::transport::send::too_big))]
89    DataTooBig,
90}
91
92/// Error while receiving a command response
93#[derive(Error, Debug, Diagnostic)]
94pub enum ReceiveError {
95    /// A timeout occurred while receiving
96    #[error("A timeout occurred")]
97    #[diagnostic(code(mcumgr_toolkit::transport::recv::timeout))]
98    Timeout,
99    /// An error occurred in the underlying transport
100    #[error("Transport error")]
101    #[diagnostic(code(mcumgr_toolkit::transport::recv::transport))]
102    TransportError(#[source] Box<dyn std::error::Error + Sync + Send + 'static>),
103    /// We received a response that did not fit to our request
104    #[error("Received unexpected response")]
105    #[diagnostic(code(mcumgr_toolkit::transport::recv::unexpected))]
106    UnexpectedResponse,
107    /// The response we received is bigger than the configured MTU
108    #[error("Received frame that exceeds configured MTU")]
109    #[diagnostic(code(mcumgr_toolkit::transport::recv::too_big))]
110    FrameTooBig,
111    /// The response we received is not base64 encoded
112    #[error("Failed to decode base64 data")]
113    #[diagnostic(code(mcumgr_toolkit::transport::recv::base64_decode))]
114    Base64DecodeError(#[from] base64::DecodeSliceError),
115}
116
117impl From<std::io::Error> for SendError {
118    fn from(e: std::io::Error) -> Self {
119        if std::io::ErrorKind::TimedOut == e.kind() {
120            Self::Timeout
121        } else {
122            Self::TransportError(e.into())
123        }
124    }
125}
126
127impl From<std::io::Error> for ReceiveError {
128    fn from(e: std::io::Error) -> Self {
129        if std::io::ErrorKind::TimedOut == e.kind() {
130            Self::Timeout
131        } else {
132            Self::TransportError(e.into())
133        }
134    }
135}
136
137#[cfg(feature = "ble")]
138impl From<btleplug::Error> for SendError {
139    fn from(e: btleplug::Error) -> Self {
140        if let btleplug::Error::TimedOut(_) = e {
141            Self::Timeout
142        } else {
143            Self::TransportError(e.into())
144        }
145    }
146}
147
148#[cfg(feature = "ble")]
149impl From<btleplug::Error> for ReceiveError {
150    fn from(e: btleplug::Error) -> Self {
151        if let btleplug::Error::TimedOut(_) = e {
152            Self::Timeout
153        } else {
154            Self::TransportError(e.into())
155        }
156    }
157}
158
159/// Defines the API of the SMP transport layer
160pub trait Transport {
161    /// Send a raw SMP frame over the bus.
162    ///
163    /// This function must be provided by the implementing struct
164    /// but should not be called directly.
165    fn send_raw_frame(
166        &mut self,
167        header: [u8; SMP_HEADER_SIZE],
168        data: &[u8],
169    ) -> Result<(), SendError>;
170
171    /// Receive a raw SMP frame from the bus.
172    ///
173    /// This function must be provided by the implementing struct
174    /// but should not be called directly.
175    fn recv_raw_frame<'a>(
176        &mut self,
177        buffer: &'a mut [u8; SMP_TRANSFER_BUFFER_SIZE],
178    ) -> Result<&'a [u8], ReceiveError>;
179
180    /// Send an SMP frame over the bus.
181    ///
182    /// # Arguments
183    ///
184    /// * `write_operation` - If the frame contains a write or read operation.
185    /// * `sequence_num` - A sequence number. Must be different every time this function is called.
186    /// * `group_id` - The group ID of the command.
187    /// * `command_id` - The command ID.
188    /// * `data` - The payload data of the command, most likely CBOR encoded.
189    ///
190    /// **IMPORTANT:** Be aware that the entire header + data must fit within one SMP protocol frame.
191    ///
192    fn send_frame(
193        &mut self,
194        write_operation: bool,
195        sequence_num: u8,
196        group_id: u16,
197        command_id: u8,
198        data: &[u8],
199    ) -> Result<(), SendError> {
200        let header = SmpHeader {
201            ver: 0b01,
202            op: if write_operation {
203                smp_op::WRITE
204            } else {
205                smp_op::READ
206            },
207            flags: 0,
208            data_length: data.len().try_into().map_err(|_| SendError::DataTooBig)?,
209            group_id,
210            sequence_num,
211            command_id,
212        };
213
214        let header_data = header.to_bytes();
215
216        self.send_raw_frame(header_data, data)
217    }
218
219    /// Receive an SMP frame from the bus.
220    ///
221    /// # Arguments
222    ///
223    /// * `buffer` - A buffer that the data will be read into.
224    /// * `write_operation` - If this is the response to a write or read operation.
225    /// * `sequence_num` - A sequence number. Must match the sequence_num of the accompanying [`Transport::send_frame`] call.
226    /// * `group_id` - The group ID of the command.
227    /// * `command_id` - The command ID.
228    ///
229    /// # Return
230    ///
231    /// The payload data of the response, most likely CBOR encoded.
232    ///
233    fn receive_frame<'a>(
234        &mut self,
235        mut buffer: &'a mut [u8; SMP_TRANSFER_BUFFER_SIZE],
236        write_operation: bool,
237        sequence_num: u8,
238        group_id: u16,
239        command_id: u8,
240    ) -> Result<&'a [u8], ReceiveError> {
241        polonius_loop!(|buffer| -> Result<&'polonius [u8], ReceiveError> {
242            let frame = polonius_try!(self.recv_raw_frame(buffer));
243
244            let (header_data, data) = match frame.split_first_chunk::<SMP_HEADER_SIZE>() {
245                Some(parts) => parts,
246                None => polonius_break!(Err(ReceiveError::UnexpectedResponse)),
247            };
248
249            let header = SmpHeader::from_bytes(*header_data);
250
251            // Receiving packets with the wrong sequence number is not an error,
252            // they should simply be silently ignored.
253            if header.sequence_num != sequence_num {
254                polonius_continue!();
255            }
256
257            let expected_op = if write_operation {
258                smp_op::WRITE_RSP
259            } else {
260                smp_op::READ_RSP
261            };
262
263            if (header.group_id != group_id)
264                || (header.command_id != command_id)
265                || (header.op != expected_op)
266                || (usize::from(header.data_length) != data.len())
267            {
268                polonius_break!(Err(ReceiveError::UnexpectedResponse));
269            }
270
271            polonius_return!(Ok(data));
272        })
273    }
274
275    /// Changes the communication timeout.
276    ///
277    /// When the device does not respond to packets within the set
278    /// duration, an error will be raised.
279    fn set_timeout(
280        &mut self,
281        timeout: Duration,
282    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
283
284    /// Returns the maximum size of a complete SMP frame supported by this transport.
285    ///
286    /// The size includes the SMP header and body, but excludes transport-specific
287    /// framing and protocol overhead.
288    ///
289    /// `usize::MAX` means that the transport imposes no additional frame-size limit.
290    ///
291    /// Used by [`MCUmgrClient::use_auto_frame_size`](crate::MCUmgrClient::use_auto_frame_size)
292    /// to cap the device-reported buffer size at what the transport can still
293    /// deliver reliably.
294    ///
295    fn max_smp_frame_size(&self) -> usize {
296        usize::MAX
297    }
298}
299
300/// Marks a type to be convertible into a boxed [`Transport`]
301pub trait IntoTransport {
302    /// Converts the object into a generic boxed [`Transport`]
303    fn into_transport(self) -> Box<dyn Transport + Send>;
304}
305
306impl<T> IntoTransport for T
307where
308    T: Transport + Send + 'static,
309{
310    fn into_transport(self) -> Box<dyn Transport + Send> {
311        Box::new(self)
312    }
313}
314
315impl IntoTransport for Box<dyn Transport + Send> {
316    fn into_transport(self) -> Box<dyn Transport + Send> {
317        self
318    }
319}