Skip to main content

mcumgr_toolkit/
connection.rs

1use std::{io::Cursor, sync::Mutex, time::Duration};
2
3use crate::{
4    DEFAULT_RETRIES,
5    commands::{ErrResponse, ErrResponseV2, McuMgrCommand},
6    smp_errors::{DeviceError, MCUmgrErr},
7    transport::{
8        IntoTransport, ReceiveError, SMP_BODY_MAX_SIZE, SMP_TRANSFER_BUFFER_SIZE, SendError,
9        Transport,
10    },
11};
12
13use miette::{Diagnostic, IntoDiagnostic};
14use polonius_the_crab::prelude::*;
15use thiserror::Error;
16
17struct Transceiver {
18    transport: Box<dyn Transport + Send>,
19    next_seqnum: u8,
20    receive_buffer: Box<[u8; SMP_TRANSFER_BUFFER_SIZE]>,
21}
22
23struct Inner {
24    transceiver: Transceiver,
25    send_buffer: Box<[u8; SMP_BODY_MAX_SIZE]>,
26    retries: u8,
27}
28
29/// An SMP protocol layer connection to a device.
30///
31/// In most cases this struct will not be used directly by the user,
32/// but instead it is used indirectly through [`MCUmgrClient`](crate::MCUmgrClient).
33pub struct Connection {
34    inner: Mutex<Inner>,
35}
36
37/// Errors that can happen on SMP protocol level
38#[derive(Error, Debug, Diagnostic)]
39pub enum ExecuteError {
40    /// An error happened on SMP transport level while sending a request
41    #[error("Sending failed")]
42    #[diagnostic(code(mcumgr_toolkit::connection::execute::send))]
43    SendFailed(#[from] SendError),
44    /// An error happened on SMP transport level while receiving a response
45    #[error("Receiving failed")]
46    #[diagnostic(code(mcumgr_toolkit::connection::execute::receive))]
47    ReceiveFailed(#[from] ReceiveError),
48    /// An error happened while CBOR encoding the request payload
49    #[error("CBOR encoding failed")]
50    #[diagnostic(code(mcumgr_toolkit::connection::execute::encode))]
51    EncodeFailed(#[source] Box<dyn miette::Diagnostic + Send + Sync>),
52    /// An error happened while CBOR decoding the response payload
53    #[error("CBOR decoding failed")]
54    #[diagnostic(code(mcumgr_toolkit::connection::execute::decode))]
55    DecodeFailed(#[source] Box<dyn miette::Diagnostic + Send + Sync>),
56    /// The device returned an SMP error
57    #[error("Device returned error code: {0}")]
58    #[diagnostic(code(mcumgr_toolkit::connection::execute::device_error))]
59    ErrorResponse(DeviceError),
60}
61
62impl ExecuteError {
63    /// Checks if the device reported the command as unsupported
64    pub fn command_not_supported(&self) -> bool {
65        if let Self::ErrorResponse(DeviceError::V1 { rc, .. }) = self {
66            *rc == MCUmgrErr::MGMT_ERR_ENOTSUP as i32
67        } else {
68            false
69        }
70    }
71}
72
73impl Transceiver {
74    fn transceive_command(
75        &mut self,
76        write_operation: bool,
77        group_id: u16,
78        command_id: u8,
79        data: &[u8],
80        sequence_num: u8,
81    ) -> Result<&'_ [u8], ExecuteError> {
82        self.transport
83            .send_frame(write_operation, sequence_num, group_id, command_id, data)?;
84
85        self.transport
86            .receive_frame(
87                &mut self.receive_buffer,
88                write_operation,
89                sequence_num,
90                group_id,
91                command_id,
92            )
93            .map_err(Into::into)
94    }
95
96    fn transceive_command_with_retries(
97        &mut self,
98        write_operation: bool,
99        group_id: u16,
100        command_id: u8,
101        data: &[u8],
102        num_retries: u8,
103    ) -> Result<&'_ [u8], ExecuteError> {
104        let mut this = self;
105
106        let mut counter = 0;
107
108        // This is a deliberate deviation from the SMP spec:
109        // The spec specifies that every sequence number is different.
110        // However, reusing the sequence number for retries
111        // has the advantage that responses that would just slightly
112        // time out can be matched to the next retry.
113        //
114        // Both Zephyr and MCUboot impls are compatible with this;
115        // they simply echo the sequence number back in the response.
116        let sequence_num = this.next_seqnum;
117        this.next_seqnum = this.next_seqnum.wrapping_add(1);
118
119        polonius_loop!(|this| -> Result<&'polonius [u8], ExecuteError> {
120            let result =
121                this.transceive_command(write_operation, group_id, command_id, data, sequence_num);
122
123            if counter >= num_retries {
124                polonius_return!(result)
125            }
126            counter += 1;
127
128            match result {
129                Ok(_) => polonius_return!(result),
130                Err(e) => {
131                    let mut lowest_err: &dyn std::error::Error = &e;
132                    while let Some(lower_err) = lowest_err.source() {
133                        lowest_err = lower_err;
134                    }
135                    log::warn!("Retry transmission, error occurred: {lowest_err}");
136                }
137            }
138        })
139    }
140}
141
142impl Connection {
143    /// Creates a new SMP connection
144    pub fn new<T: IntoTransport>(transport: T) -> Self {
145        Self {
146            inner: Mutex::new(Inner {
147                transceiver: Transceiver {
148                    transport: transport.into_transport(),
149                    next_seqnum: rand::random(),
150                    receive_buffer: Box::new([0; SMP_TRANSFER_BUFFER_SIZE]),
151                },
152                send_buffer: Box::new([0; SMP_BODY_MAX_SIZE]),
153                retries: DEFAULT_RETRIES,
154            }),
155        }
156    }
157
158    /// Returns the maximum SMP frame size the underlying transport can
159    /// deliver reliably.
160    pub fn max_transport_frame_size(&self) -> usize {
161        self.inner
162            .lock()
163            .unwrap()
164            .transceiver
165            .transport
166            .max_smp_frame_size()
167    }
168
169    /// Returns how many bytes the device needs in its receive buffer
170    /// in addition to the SMP frame itself.
171    pub fn device_rx_buffer_overhead(&self) -> usize {
172        self.inner
173            .lock()
174            .unwrap()
175            .transceiver
176            .transport
177            .device_rx_buffer_overhead()
178    }
179
180    /// Changes the communication timeout.
181    ///
182    /// When the device does not respond to packets within the set
183    /// duration, an error will be raised.
184    pub fn set_timeout(
185        &self,
186        timeout: Duration,
187    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
188        self.inner
189            .lock()
190            .unwrap()
191            .transceiver
192            .transport
193            .set_timeout(timeout)
194    }
195
196    /// Changes the retry amount.
197    ///
198    /// When the device encounters a transport error, it will retry
199    /// this many times until giving up.
200    pub fn set_retries(&self, retries: u8) {
201        self.inner.lock().unwrap().retries = retries;
202    }
203
204    /// Executes a given CBOR based SMP command.
205    pub fn execute_command<R: McuMgrCommand>(
206        &self,
207        request: &R,
208    ) -> Result<R::Response, ExecuteError> {
209        self.execute_command_impl(request, true)
210    }
211
212    /// Executes a given CBOR based SMP command.
213    ///
214    /// Does not use retries.
215    pub fn execute_command_without_retries<R: McuMgrCommand>(
216        &self,
217        request: &R,
218    ) -> Result<R::Response, ExecuteError> {
219        self.execute_command_impl(request, false)
220    }
221
222    fn execute_command_impl<R: McuMgrCommand>(
223        &self,
224        request: &R,
225        use_retries: bool,
226    ) -> Result<R::Response, ExecuteError> {
227        let mut lock_guard = self.inner.lock().unwrap();
228        let locked_self: &mut Inner = &mut lock_guard;
229
230        let mut cursor = Cursor::new(locked_self.send_buffer.as_mut_slice());
231        ciborium::into_writer(request.data(), &mut cursor)
232            .into_diagnostic()
233            .map_err(Into::into)
234            .map_err(ExecuteError::EncodeFailed)?;
235        let data_size = cursor.position() as usize;
236        let data = &locked_self.send_buffer[..data_size];
237
238        log::debug!("TX data: {}", hex::encode(data));
239
240        let write_operation = request.is_write_operation();
241        let group_id = request.group_id();
242        let command_id = request.command_id();
243
244        let response = locked_self.transceiver.transceive_command_with_retries(
245            write_operation,
246            group_id,
247            command_id,
248            data,
249            if use_retries { locked_self.retries } else { 0 },
250        )?;
251
252        log::debug!("RX data: {}", hex::encode(response));
253
254        let err: ErrResponse = ciborium::from_reader(Cursor::new(response))
255            .into_diagnostic()
256            .map_err(Into::into)
257            .map_err(ExecuteError::DecodeFailed)?;
258
259        if let Some(ErrResponseV2 { rc, group }) = err.err {
260            return Err(ExecuteError::ErrorResponse(DeviceError::V2 { group, rc }));
261        }
262
263        if let Some(rc) = err.rc {
264            if rc != MCUmgrErr::MGMT_ERR_EOK as i32 {
265                return Err(ExecuteError::ErrorResponse(DeviceError::V1 {
266                    rc,
267                    rsn: err.rsn,
268                }));
269            }
270        }
271
272        let decoded_response: R::Response = ciborium::from_reader(Cursor::new(response))
273            .into_diagnostic()
274            .map_err(Into::into)
275            .map_err(ExecuteError::DecodeFailed)?;
276
277        Ok(decoded_response)
278    }
279
280    /// Executes a raw SMP command.
281    ///
282    /// Same as [`Connection::execute_command`], but the payload can be anything and must not
283    /// necessarily be CBOR encoded.
284    ///
285    /// Errors are also not decoded but instead will be returned as raw CBOR data.
286    ///
287    /// Read Zephyr's [SMP Protocol Specification](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_protocol.html)
288    /// for more information.
289    pub fn execute_raw_command(
290        &self,
291        write_operation: bool,
292        group_id: u16,
293        command_id: u8,
294        data: &[u8],
295        use_retries: bool,
296    ) -> Result<Box<[u8]>, ExecuteError> {
297        let mut lock_guard = self.inner.lock().unwrap();
298        let locked_self: &mut Inner = &mut lock_guard;
299
300        locked_self
301            .transceiver
302            .transceive_command_with_retries(
303                write_operation,
304                group_id,
305                command_id,
306                data,
307                if use_retries { locked_self.retries } else { 0 },
308            )
309            .map(|val| val.into())
310    }
311}