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