Skip to main content

mcumgr_toolkit/transport/
mod.rs

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