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(
79        &self,
80        timeout: Duration,
81    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
82        self.inner.lock().unwrap().transport.set_timeout(timeout)
83    }
84
85    /// Executes a given CBOR based SMP command.
86    pub fn execute_command<R: McuMgrCommand>(
87        &self,
88        request: &R,
89    ) -> Result<R::Response, ExecuteError> {
90        let mut lock_guard = self.inner.lock().unwrap();
91        let locked_self: &mut Inner = &mut lock_guard;
92
93        let mut cursor = Cursor::new(locked_self.transport_buffer.as_mut_slice());
94        ciborium::into_writer(request.data(), &mut cursor)
95            .into_diagnostic()
96            .map_err(Into::into)
97            .map_err(ExecuteError::EncodeFailed)?;
98        let data_size = cursor.position() as usize;
99        let data = &locked_self.transport_buffer[..data_size];
100
101        log::debug!("TX data: {}", hex::encode(data));
102
103        let sequence_num = locked_self.next_seqnum;
104        locked_self.next_seqnum = locked_self.next_seqnum.wrapping_add(1);
105
106        let write_operation = request.is_write_operation();
107        let group_id = request.group_id();
108        let command_id = request.command_id();
109
110        locked_self.transport.send_frame(
111            write_operation,
112            sequence_num,
113            group_id,
114            command_id,
115            data,
116        )?;
117
118        let response = locked_self.transport.receive_frame(
119            &mut locked_self.transport_buffer,
120            write_operation,
121            sequence_num,
122            group_id,
123            command_id,
124        )?;
125
126        log::debug!("RX data: {}", hex::encode(response));
127
128        let err: ErrResponse = ciborium::from_reader(Cursor::new(response))
129            .into_diagnostic()
130            .map_err(Into::into)
131            .map_err(ExecuteError::DecodeFailed)?;
132
133        if let Some(ErrResponseV2 { rc, group }) = err.err {
134            return Err(ExecuteError::ErrorResponse(DeviceError::V2 { group, rc }));
135        }
136
137        if let Some(rc) = err.rc {
138            if rc != MCUmgrErr::MGMT_ERR_EOK as i32 {
139                return Err(ExecuteError::ErrorResponse(DeviceError::V1 {
140                    rc,
141                    rsn: err.rsn,
142                }));
143            }
144        }
145
146        let decoded_response: R::Response = ciborium::from_reader(Cursor::new(response))
147            .into_diagnostic()
148            .map_err(Into::into)
149            .map_err(ExecuteError::DecodeFailed)?;
150
151        Ok(decoded_response)
152    }
153
154    /// Executes a raw SMP command.
155    ///
156    /// Same as [`Connection::execute_command`], but the payload can be anything and must not
157    /// necessarily be CBOR encoded.
158    ///
159    /// Errors are also not decoded but instead will be returned as raw CBOR data.
160    ///
161    /// Read Zephyr's [SMP Protocol Specification](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_protocol.html)
162    /// for more information.
163    pub fn execute_raw_command(
164        &self,
165        write_operation: bool,
166        group_id: u16,
167        command_id: u8,
168        data: &[u8],
169    ) -> Result<Box<[u8]>, ExecuteError> {
170        let mut lock_guard = self.inner.lock().unwrap();
171        let locked_self: &mut Inner = &mut lock_guard;
172
173        let sequence_num = locked_self.next_seqnum;
174        locked_self.next_seqnum = locked_self.next_seqnum.wrapping_add(1);
175
176        locked_self.transport.send_frame(
177            write_operation,
178            sequence_num,
179            group_id,
180            command_id,
181            data,
182        )?;
183
184        locked_self
185            .transport
186            .receive_frame(
187                &mut locked_self.transport_buffer,
188                write_operation,
189                sequence_num,
190                group_id,
191                command_id,
192            )
193            .map_err(Into::into)
194            .map(|val| val.into())
195    }
196}