Skip to main content

mcumgr_toolkit/
connection.rs

1use std::{io::Cursor, sync::Mutex, time::Duration};
2
3use crate::{
4    commands::{ErrResponse, ErrResponseV2, McuMgrCommand},
5    smp_errors::{DeviceError, MCUmgrErr},
6    transport::{ReceiveError, SendError, Transport},
7};
8
9use miette::{Diagnostic, IntoDiagnostic};
10use thiserror::Error;
11
12struct Inner {
13    transport: Box<dyn Transport + Send>,
14    next_seqnum: u8,
15    transport_buffer: Box<[u8; u16::MAX as usize]>,
16}
17
18/// An SMP protocol layer connection to a device.
19///
20/// In most cases this struct will not be used directly by the user,
21/// but instead it is used indirectly through [`MCUmgrClient`](crate::MCUmgrClient).
22pub struct Connection {
23    inner: Mutex<Inner>,
24}
25
26/// Errors that can happen on SMP protocol level
27#[derive(Error, Debug, Diagnostic)]
28pub enum ExecuteError {
29    /// An error happened on SMP transport level while sending a request
30    #[error("Sending failed")]
31    #[diagnostic(code(mcumgr_toolkit::connection::execute::send))]
32    SendFailed(#[from] SendError),
33    /// An error happened on SMP transport level while receiving a response
34    #[error("Receiving failed")]
35    #[diagnostic(code(mcumgr_toolkit::connection::execute::receive))]
36    ReceiveFailed(#[from] ReceiveError),
37    /// An error happened while CBOR encoding the request payload
38    #[error("CBOR encoding failed")]
39    #[diagnostic(code(mcumgr_toolkit::connection::execute::encode))]
40    EncodeFailed(#[source] Box<dyn miette::Diagnostic + Send + Sync>),
41    /// An error happened while CBOR decoding the response payload
42    #[error("CBOR decoding failed")]
43    #[diagnostic(code(mcumgr_toolkit::connection::execute::decode))]
44    DecodeFailed(#[source] Box<dyn miette::Diagnostic + Send + Sync>),
45    /// The device returned an SMP error
46    #[error("Device returned error code: {0}")]
47    #[diagnostic(code(mcumgr_toolkit::connection::execute::device_error))]
48    ErrorResponse(DeviceError),
49}
50
51impl ExecuteError {
52    /// Checks if the device reported the command as unsupported
53    pub fn command_not_supported(&self) -> bool {
54        if let Self::ErrorResponse(DeviceError::V1 { rc, .. }) = self {
55            *rc == MCUmgrErr::MGMT_ERR_ENOTSUP as i32
56        } else {
57            false
58        }
59    }
60}
61
62impl Connection {
63    /// Creates a new SMP
64    pub fn new<T: Transport + Send + 'static>(transport: T) -> Self {
65        Self {
66            inner: Mutex::new(Inner {
67                transport: Box::new(transport),
68                next_seqnum: rand::random(),
69                transport_buffer: Box::new([0; u16::MAX as usize]),
70            }),
71        }
72    }
73
74    /// Changes the communication timeout.
75    ///
76    /// When the device does not respond to packets within the set
77    /// duration, an error will be raised.
78    pub fn set_timeout(&self, timeout: Duration) -> Result<(), miette::Report> {
79        self.inner.lock().unwrap().transport.set_timeout(timeout)
80    }
81
82    /// Executes a given CBOR based SMP command.
83    pub fn execute_command<R: McuMgrCommand>(
84        &self,
85        request: &R,
86    ) -> Result<R::Response, ExecuteError> {
87        let mut lock_guard = self.inner.lock().unwrap();
88        let locked_self: &mut Inner = &mut lock_guard;
89
90        let mut cursor = Cursor::new(locked_self.transport_buffer.as_mut_slice());
91        ciborium::into_writer(request.data(), &mut cursor)
92            .into_diagnostic()
93            .map_err(Into::into)
94            .map_err(ExecuteError::EncodeFailed)?;
95        let data_size = cursor.position() as usize;
96        let data = &locked_self.transport_buffer[..data_size];
97
98        log::debug!("TX data: {}", hex::encode(data));
99
100        let sequence_num = locked_self.next_seqnum;
101        locked_self.next_seqnum = locked_self.next_seqnum.wrapping_add(1);
102
103        let write_operation = request.is_write_operation();
104        let group_id = request.group_id();
105        let command_id = request.command_id();
106
107        locked_self.transport.send_frame(
108            write_operation,
109            sequence_num,
110            group_id,
111            command_id,
112            data,
113        )?;
114
115        let response = locked_self.transport.receive_frame(
116            &mut locked_self.transport_buffer,
117            write_operation,
118            sequence_num,
119            group_id,
120            command_id,
121        )?;
122
123        log::debug!("RX data: {}", hex::encode(response));
124
125        let err: ErrResponse = ciborium::from_reader(Cursor::new(response))
126            .into_diagnostic()
127            .map_err(Into::into)
128            .map_err(ExecuteError::DecodeFailed)?;
129
130        if let Some(ErrResponseV2 { rc, group }) = err.err {
131            return Err(ExecuteError::ErrorResponse(DeviceError::V2 { group, rc }));
132        }
133
134        if let Some(rc) = err.rc {
135            if rc != MCUmgrErr::MGMT_ERR_EOK as i32 {
136                return Err(ExecuteError::ErrorResponse(DeviceError::V1 {
137                    rc,
138                    rsn: err.rsn,
139                }));
140            }
141        }
142
143        let decoded_response: R::Response = ciborium::from_reader(Cursor::new(response))
144            .into_diagnostic()
145            .map_err(Into::into)
146            .map_err(ExecuteError::DecodeFailed)?;
147
148        Ok(decoded_response)
149    }
150
151    /// Executes a raw SMP command.
152    ///
153    /// Same as [`Connection::execute_command`], but the payload can be anything and must not
154    /// necessarily be CBOR encoded.
155    ///
156    /// Errors are also not decoded but instead will be returned as raw CBOR data.
157    ///
158    /// Read Zephyr's [SMP Protocol Specification](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_protocol.html)
159    /// for more information.
160    pub fn execute_raw_command(
161        &self,
162        write_operation: bool,
163        group_id: u16,
164        command_id: u8,
165        data: &[u8],
166    ) -> Result<Box<[u8]>, ExecuteError> {
167        let mut lock_guard = self.inner.lock().unwrap();
168        let locked_self: &mut Inner = &mut lock_guard;
169
170        let sequence_num = locked_self.next_seqnum;
171        locked_self.next_seqnum = locked_self.next_seqnum.wrapping_add(1);
172
173        locked_self.transport.send_frame(
174            write_operation,
175            sequence_num,
176            group_id,
177            command_id,
178            data,
179        )?;
180
181        locked_self
182            .transport
183            .receive_frame(
184                &mut locked_self.transport_buffer,
185                write_operation,
186                sequence_num,
187                group_id,
188                command_id,
189            )
190            .map_err(Into::into)
191            .map(|val| val.into())
192    }
193}