Skip to main content

mcumgr_toolkit/transport/
mod.rs

1use std::{io, 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    /// An error occurred in the underlying transport
65    #[error("Transport error")]
66    #[diagnostic(code(mcumgr_toolkit::transport::send::transport))]
67    TransportError(#[from] io::Error),
68    /// Unable to send data because it is too big
69    #[error("Given data slice was too big")]
70    #[diagnostic(code(mcumgr_toolkit::transport::send::too_big))]
71    DataTooBig,
72}
73
74/// Error while receiving a command response
75#[derive(Error, Debug, Diagnostic)]
76pub enum ReceiveError {
77    /// An error occurred in the underlying transport
78    #[error("Transport error")]
79    #[diagnostic(code(mcumgr_toolkit::transport::recv::transport))]
80    TransportError(#[from] io::Error),
81    /// We received a response that did not fit to our request
82    #[error("Received unexpected response")]
83    #[diagnostic(code(mcumgr_toolkit::transport::recv::unexpected))]
84    UnexpectedResponse,
85    /// The response we received is bigger than the configured MTU
86    #[error("Received frame that exceeds configured MTU")]
87    #[diagnostic(code(mcumgr_toolkit::transport::recv::too_big))]
88    FrameTooBig,
89    /// The response we received is not base64 encoded
90    #[error("Failed to decode base64 data")]
91    #[diagnostic(code(mcumgr_toolkit::transport::recv::base64_decode))]
92    Base64DecodeError(#[from] base64::DecodeSliceError),
93}
94
95/// Defines the API of the SMP transport layer
96pub trait Transport {
97    /// Send a raw SMP frame over the bus.
98    ///
99    /// This function must be provided by the implementing struct
100    /// but should not be called directly.
101    fn send_raw_frame(
102        &mut self,
103        header: [u8; SMP_HEADER_SIZE],
104        data: &[u8],
105    ) -> Result<(), SendError>;
106
107    /// Receive a raw SMP frame from the bus.
108    ///
109    /// This function must be provided by the implementing struct
110    /// but should not be called directly.
111    fn recv_raw_frame<'a>(
112        &mut self,
113        buffer: &'a mut [u8; SMP_TRANSFER_BUFFER_SIZE],
114    ) -> Result<&'a [u8], ReceiveError>;
115
116    /// Send an SMP frame over the bus.
117    ///
118    /// # Arguments
119    ///
120    /// * `write_operation` - If the frame contains a write or read operation.
121    /// * `sequence_num` - A sequence number. Must be different every time this function is called.
122    /// * `group_id` - The group ID of the command.
123    /// * `command_id` - The command ID.
124    /// * `data` - The payload data of the command, most likely CBOR encoded.
125    ///
126    /// **IMPORTANT:** Be aware that the entire header + data must fit within one SMP protocol frame.
127    ///
128    fn send_frame(
129        &mut self,
130        write_operation: bool,
131        sequence_num: u8,
132        group_id: u16,
133        command_id: u8,
134        data: &[u8],
135    ) -> Result<(), SendError> {
136        let header = SmpHeader {
137            ver: 0b01,
138            op: if write_operation {
139                smp_op::WRITE
140            } else {
141                smp_op::READ
142            },
143            flags: 0,
144            data_length: data.len().try_into().map_err(|_| SendError::DataTooBig)?,
145            group_id,
146            sequence_num,
147            command_id,
148        };
149
150        let header_data = header.to_bytes();
151
152        self.send_raw_frame(header_data, data)
153    }
154
155    /// Receive an SMP frame from the bus.
156    ///
157    /// # Arguments
158    ///
159    /// * `buffer` - A buffer that the data will be read into.
160    /// * `write_operation` - If this is the response to a write or read operation.
161    /// * `sequence_num` - A sequence number. Must match the sequence_num of the accompanying [`Transport::send_frame`] call.
162    /// * `group_id` - The group ID of the command.
163    /// * `command_id` - The command ID.
164    ///
165    /// # Return
166    ///
167    /// The payload data of the response, most likely CBOR encoded.
168    ///
169    fn receive_frame<'a>(
170        &mut self,
171        buffer: &'a mut [u8; SMP_TRANSFER_BUFFER_SIZE],
172        write_operation: bool,
173        sequence_num: u8,
174        group_id: u16,
175        command_id: u8,
176    ) -> Result<&'a [u8], ReceiveError> {
177        let data_size = loop {
178            let frame = self.recv_raw_frame(buffer)?;
179
180            let (header_data, data) = frame
181                .split_first_chunk::<SMP_HEADER_SIZE>()
182                .ok_or(ReceiveError::UnexpectedResponse)?;
183
184            let header = SmpHeader::from_bytes(*header_data);
185
186            let expected_op = if write_operation {
187                smp_op::WRITE_RSP
188            } else {
189                smp_op::READ_RSP
190            };
191
192            // Receiving packets with the wrong sequence number is not an error,
193            // they should simply be silently ignored.
194            if header.sequence_num != sequence_num {
195                continue;
196            }
197
198            if (header.group_id != group_id)
199                || (header.command_id != command_id)
200                || (header.op != expected_op)
201                || (usize::from(header.data_length) != data.len())
202            {
203                return Err(ReceiveError::UnexpectedResponse);
204            }
205
206            break data.len();
207        };
208
209        Ok(&buffer[SMP_HEADER_SIZE..SMP_HEADER_SIZE + data_size])
210    }
211
212    /// Changes the communication timeout.
213    ///
214    /// When the device does not respond to packets within the set
215    /// duration, an error will be raised.
216    fn set_timeout(
217        &mut self,
218        timeout: Duration,
219    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
220
221    /// Returns the maximum SMP frame size this transport can carry in one shot.
222    ///
223    /// Used by [`MCUmgrClient::use_auto_frame_size`](crate::MCUmgrClient::use_auto_frame_size)
224    /// to cap the device-reported buffer size at what the transport can still
225    /// deliver reliably.
226    ///
227    /// The default (`usize::MAX`) means no transport-level cap — suitable for
228    /// stream-based transports like serial that handle large frames via chunking.
229    fn max_smp_frame_size(&self) -> usize {
230        usize::MAX
231    }
232}