Skip to main content

mcumgr_toolkit/commands/
mod.rs

1/// [File management](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_8.html) group commands
2pub mod fs;
3/// [Application/software image management](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_1.html) group commands
4pub mod image;
5/// [Default/OS management](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_0.html) group commands
6pub mod os;
7/// [Shell management](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_9.html) group commands
8pub mod shell;
9/// [Zephyr management](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_groups/smp_group_63.html) group commands
10pub mod zephyr;
11
12mod macros;
13use macros::impl_mcumgr_command;
14
15use serde::{Deserialize, Serialize};
16
17/// SMP version 2 group based error message
18#[derive(Debug, Deserialize, Eq, PartialEq)]
19pub struct ErrResponseV2 {
20    /// group of the group-based error code
21    pub group: u16,
22    /// contains the index of the group-based error code
23    pub rc: i32,
24}
25
26/// [SMP error message](https://docs.zephyrproject.org/latest/services/device_mgmt/smp_protocol.html#minimal-response-smp-data)
27#[derive(Debug, Deserialize, Eq, PartialEq)]
28pub struct ErrResponse {
29    /// SMP version 1 error code
30    pub rc: Option<i32>,
31    /// SMP version 1 error string
32    pub rsn: Option<String>,
33    /// SMP version 2 error message
34    pub err: Option<ErrResponseV2>,
35}
36
37/// An MCUmgr command that can be executed through [`Connection::execute_command`](crate::connection::Connection::execute_command).
38pub trait McuMgrCommand {
39    /// the data payload type
40    type Payload: Serialize;
41    /// the response type of the command
42    type Response: for<'a> Deserialize<'a>;
43    /// whether this command is a read or write operation
44    fn is_write_operation(&self) -> bool;
45    /// the group ID of the command
46    fn group_id(&self) -> u16;
47    /// the command ID
48    fn command_id(&self) -> u8;
49    /// the data
50    fn data(&self) -> &Self::Payload;
51}
52
53/// Checks if a value is the default value
54fn is_default<T: Default + PartialEq>(val: &T) -> bool {
55    val == &T::default()
56}
57
58fn data_too_large_error() -> std::io::Error {
59    std::io::Error::other(Box::<dyn std::error::Error + Send + Sync>::from(
60        "Serialized data too large".to_string(),
61    ))
62}
63
64// A writer that simply counts the number of bytes written to it
65struct CountingWriter {
66    bytes_written: usize,
67}
68impl CountingWriter {
69    fn new() -> Self {
70        Self { bytes_written: 0 }
71    }
72}
73impl std::io::Write for CountingWriter {
74    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
75        let len = buf.len();
76        self.bytes_written = self
77            .bytes_written
78            .checked_add(len)
79            .ok_or_else(data_too_large_error)?;
80        Ok(len)
81    }
82
83    fn flush(&mut self) -> std::io::Result<()> {
84        Ok(())
85    }
86}
87
88impl_mcumgr_command!((read,  MGMT_GROUP_ID_OS, 0): os::Echo<'_> => os::EchoResponse);
89impl_mcumgr_command!((read,  MGMT_GROUP_ID_OS, 2): os::TaskStatistics => os::TaskStatisticsResponse);
90impl_mcumgr_command!((read,  MGMT_GROUP_ID_OS, 4): os::DateTimeGet => os::DateTimeGetResponse);
91impl_mcumgr_command!((write, MGMT_GROUP_ID_OS, 4): os::DateTimeSet => os::DateTimeSetResponse);
92impl_mcumgr_command!((write, MGMT_GROUP_ID_OS, 5): os::SystemReset => os::SystemResetResponse);
93impl_mcumgr_command!((read,  MGMT_GROUP_ID_OS, 6): os::MCUmgrParameters => os::MCUmgrParametersResponse);
94impl_mcumgr_command!((read,  MGMT_GROUP_ID_OS, 7): os::ApplicationInfo<'_> => os::ApplicationInfoResponse);
95impl_mcumgr_command!((read,  MGMT_GROUP_ID_OS, 8): os::BootloaderInfo => os::BootloaderInfoResponse);
96impl_mcumgr_command!((read,  MGMT_GROUP_ID_OS, 8): os::BootloaderInfoMcubootMode => os::BootloaderInfoMcubootModeResponse);
97
98impl_mcumgr_command!((read,  MGMT_GROUP_ID_IMAGE, 0): image::GetImageState => image::ImageStateResponse);
99impl_mcumgr_command!((write,  MGMT_GROUP_ID_IMAGE, 0): image::SetImageState<'_> => image::ImageStateResponse);
100impl_mcumgr_command!((write,  MGMT_GROUP_ID_IMAGE, 1): image::ImageUpload<'_, '_> => image::ImageUploadResponse);
101impl_mcumgr_command!((write,  MGMT_GROUP_ID_IMAGE, 5): image::ImageErase => image::ImageEraseResponse);
102impl_mcumgr_command!((read,  MGMT_GROUP_ID_IMAGE, 6): image::SlotInfo => image::SlotInfoResponse);
103
104impl_mcumgr_command!((write, MGMT_GROUP_ID_FS, 0): fs::FileUpload<'_, '_> => fs::FileUploadResponse);
105impl_mcumgr_command!((read,  MGMT_GROUP_ID_FS, 0): fs::FileDownload<'_> => fs::FileDownloadResponse);
106impl_mcumgr_command!((read,  MGMT_GROUP_ID_FS, 1): fs::FileStatus<'_> => fs::FileStatusResponse);
107impl_mcumgr_command!((read,  MGMT_GROUP_ID_FS, 2): fs::FileChecksum<'_, '_> => fs::FileChecksumResponse);
108impl_mcumgr_command!((read,  MGMT_GROUP_ID_FS, 3): fs::SupportedFileChecksumTypes => fs::SupportedFileChecksumTypesResponse);
109impl_mcumgr_command!((write, MGMT_GROUP_ID_FS, 4): fs::FileClose => fs::FileCloseResponse);
110
111impl_mcumgr_command!((write, MGMT_GROUP_ID_SHELL, 0): shell::ShellCommandLineExecute<'_> => shell::ShellCommandLineExecuteResponse);
112
113impl_mcumgr_command!((write, ZEPHYR_MGMT_GRP_BASIC, 0): zephyr::EraseStorage => zephyr::EraseStorageResponse);
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use ciborium::cbor;
119
120    #[test]
121    fn decode_error_none() {
122        let mut cbor_data = vec![];
123        ciborium::into_writer(
124            &cbor!({
125                "foo" => 42,
126            })
127            .unwrap(),
128            &mut cbor_data,
129        )
130        .unwrap();
131        let err: ErrResponse = ciborium::from_reader(cbor_data.as_slice()).unwrap();
132        assert_eq!(
133            err,
134            ErrResponse {
135                rc: None,
136                rsn: None,
137                err: None,
138            }
139        );
140    }
141
142    #[test]
143    fn decode_error_v1() {
144        let mut cbor_data = vec![];
145        ciborium::into_writer(
146            &cbor!({
147                "rc" => 10,
148            })
149            .unwrap(),
150            &mut cbor_data,
151        )
152        .unwrap();
153        let err: ErrResponse = ciborium::from_reader(cbor_data.as_slice()).unwrap();
154        assert_eq!(
155            err,
156            ErrResponse {
157                rc: Some(10),
158                rsn: None,
159                err: None,
160            }
161        );
162    }
163
164    #[test]
165    fn decode_error_v1_with_msg() {
166        let mut cbor_data = vec![];
167        ciborium::into_writer(
168            &cbor!({
169                "rc" => 1,
170                "rsn" => "Test Reason!",
171            })
172            .unwrap(),
173            &mut cbor_data,
174        )
175        .unwrap();
176        let err: ErrResponse = ciborium::from_reader(cbor_data.as_slice()).unwrap();
177        assert_eq!(
178            err,
179            ErrResponse {
180                rc: Some(1),
181                rsn: Some("Test Reason!".to_string()),
182                err: None,
183            }
184        );
185    }
186
187    #[test]
188    fn decode_error_v2() {
189        let mut cbor_data = vec![];
190        ciborium::into_writer(
191            &cbor!({
192                "err" => {
193                    "group" => 4,
194                    "rc" => 20,
195                }
196            })
197            .unwrap(),
198            &mut cbor_data,
199        )
200        .unwrap();
201        let err: ErrResponse = ciborium::from_reader(cbor_data.as_slice()).unwrap();
202        assert_eq!(
203            err,
204            ErrResponse {
205                rc: None,
206                rsn: None,
207                err: Some(ErrResponseV2 { group: 4, rc: 20 })
208            }
209        );
210    }
211
212    #[test]
213    fn is_default() {
214        assert!(super::is_default(&0));
215        assert!(!super::is_default(&5));
216    }
217}