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    ) -> Result<&'_ [u8], ExecuteError> {
81        let sequence_num = self.next_seqnum;
82        self.next_seqnum = self.next_seqnum.wrapping_add(1);
83
84        self.transport
85            .send_frame(write_operation, sequence_num, group_id, command_id, data)?;
86
87        self.transport
88            .receive_frame(
89                &mut self.receive_buffer,
90                write_operation,
91                sequence_num,
92                group_id,
93                command_id,
94            )
95            .map_err(Into::into)
96    }
97
98    fn transceive_command_with_retries(
99        &mut self,
100        write_operation: bool,
101        group_id: u16,
102        command_id: u8,
103        data: &[u8],
104        num_retries: u8,
105    ) -> Result<&'_ [u8], ExecuteError> {
106        let mut this = self;
107
108        let mut counter = 0;
109
110        polonius_loop!(|this| -> Result<&'polonius [u8], ExecuteError> {
111            let result = this.transceive_command(write_operation, group_id, command_id, data);
112
113            if counter >= num_retries {
114                polonius_return!(result)
115            }
116            counter += 1;
117
118            match result {
119                Ok(_) => polonius_return!(result),
120                Err(e) => {
121                    let mut lowest_err: &dyn std::error::Error = &e;
122                    while let Some(lower_err) = lowest_err.source() {
123                        lowest_err = lower_err;
124                    }
125                    log::warn!("Retry transmission, error occurred: {lowest_err}");
126                }
127            }
128        })
129    }
130}
131
132impl Connection {
133    /// Creates a new SMP connection
134    pub fn new<T: IntoTransport>(transport: T) -> Self {
135        Self {
136            inner: Mutex::new(Inner {
137                transceiver: Transceiver {
138                    transport: transport.into_transport(),
139                    next_seqnum: rand::random(),
140                    receive_buffer: Box::new([0; SMP_TRANSFER_BUFFER_SIZE]),
141                },
142                send_buffer: Box::new([0; SMP_BODY_MAX_SIZE]),
143                retries: DEFAULT_RETRIES,
144            }),
145        }
146    }
147
148    /// Returns the maximum SMP frame size the underlying transport can
149    /// deliver reliably.
150    pub fn max_transport_frame_size(&self) -> usize {
151        self.inner
152            .lock()
153            .unwrap()
154            .transceiver
155            .transport
156            .max_smp_frame_size()
157    }
158
159    /// Changes the communication timeout.
160    ///
161    /// When the device does not respond to packets within the set
162    /// duration, an error will be raised.
163    pub fn set_timeout(
164        &self,
165        timeout: Duration,
166    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
167        self.inner
168            .lock()
169            .unwrap()
170            .transceiver
171            .transport
172            .set_timeout(timeout)
173    }
174
175    /// Changes the retry amount.
176    ///
177    /// When the device encounters a transport error, it will retry
178    /// this many times until giving up.
179    pub fn set_retries(&self, retries: u8) {
180        self.inner.lock().unwrap().retries = retries;
181    }
182
183    /// Executes a given CBOR based SMP command.
184    pub fn execute_command<R: McuMgrCommand>(
185        &self,
186        request: &R,
187    ) -> Result<R::Response, ExecuteError> {
188        self.execute_command_impl(request, true)
189    }
190
191    /// Executes a given CBOR based SMP command.
192    ///
193    /// Does not use retries.
194    pub fn execute_command_without_retries<R: McuMgrCommand>(
195        &self,
196        request: &R,
197    ) -> Result<R::Response, ExecuteError> {
198        self.execute_command_impl(request, false)
199    }
200
201    fn execute_command_impl<R: McuMgrCommand>(
202        &self,
203        request: &R,
204        use_retries: bool,
205    ) -> Result<R::Response, ExecuteError> {
206        let mut lock_guard = self.inner.lock().unwrap();
207        let locked_self: &mut Inner = &mut lock_guard;
208
209        let mut cursor = Cursor::new(locked_self.send_buffer.as_mut_slice());
210        ciborium::into_writer(request.data(), &mut cursor)
211            .into_diagnostic()
212            .map_err(Into::into)
213            .map_err(ExecuteError::EncodeFailed)?;
214        let data_size = cursor.position() as usize;
215        let data = &locked_self.send_buffer[..data_size];
216
217        log::debug!("TX data: {}", hex::encode(data));
218
219        let write_operation = request.is_write_operation();
220        let group_id = request.group_id();
221        let command_id = request.command_id();
222
223        let response = locked_self.transceiver.transceive_command_with_retries(
224            write_operation,
225            group_id,
226            command_id,
227            data,
228            if use_retries { locked_self.retries } else { 0 },
229        )?;
230
231        log::debug!("RX data: {}", hex::encode(response));
232
233        let err: ErrResponse = ciborium::from_reader(Cursor::new(response))
234            .into_diagnostic()
235            .map_err(Into::into)
236            .map_err(ExecuteError::DecodeFailed)?;
237
238        if let Some(ErrResponseV2 { rc, group }) = err.err {
239            return Err(ExecuteError::ErrorResponse(DeviceError::V2 { group, rc }));
240        }
241
242        if let Some(rc) = err.rc {
243            if rc != MCUmgrErr::MGMT_ERR_EOK as i32 {
244                return Err(ExecuteError::ErrorResponse(DeviceError::V1 {
245                    rc,
246                    rsn: err.rsn,
247                }));
248            }
249        }
250
251        let decoded_response: R::Response = ciborium::from_reader(Cursor::new(response))
252            .into_diagnostic()
253            .map_err(Into::into)
254            .map_err(ExecuteError::DecodeFailed)?;
255
256        Ok(decoded_response)
257    }
258
259    /// Executes a raw SMP command.
260    ///
261    /// Same as [`Connection::execute_command`], but the payload can be anything and must not
262    /// necessarily be CBOR encoded.
263    ///
264    /// Errors are also not decoded but instead will be returned as raw CBOR data.
265    ///
266    /// Read Zephyr's [SMP Protocol Specification](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_protocol.html)
267    /// for more information.
268    pub fn execute_raw_command(
269        &self,
270        write_operation: bool,
271        group_id: u16,
272        command_id: u8,
273        data: &[u8],
274        use_retries: bool,
275    ) -> Result<Box<[u8]>, ExecuteError> {
276        let mut lock_guard = self.inner.lock().unwrap();
277        let locked_self: &mut Inner = &mut lock_guard;
278
279        locked_self
280            .transceiver
281            .transceive_command_with_retries(
282                write_operation,
283                group_id,
284                command_id,
285                data,
286                if use_retries { locked_self.retries } else { 0 },
287            )
288            .map(|val| val.into())
289    }
290}