1mod firmware_update;
3
4pub use firmware_update::{
5 FirmwareUpdateError, FirmwareUpdateParams, FirmwareUpdateProgressCallback, FirmwareUpdateStep,
6};
7
8use std::{
9 collections::HashMap,
10 io::{self, Read, Write},
11 net::SocketAddr,
12 sync::atomic::AtomicUsize,
13 time::Duration,
14};
15
16use miette::Diagnostic;
17use rand::distr::SampleString;
18use serde::Serialize;
19use sha2::{Digest, Sha256};
20use thiserror::Error;
21
22use crate::{
23 bootloader::BootloaderInfo,
24 commands::{
25 self, fs::file_upload_max_data_chunk_size, image::image_upload_max_data_chunk_size,
26 },
27 connection::{Connection, ExecuteError},
28 transport::{
29 ReceiveError,
30 serial::{ConfigurableTimeout, SerialTransport},
31 udp::UdpTransport,
32 },
33};
34
35#[cfg(feature = "ble")]
36use crate::transport::ble::{BleIdentifier, BleRuntimeError};
37
38const ZEPHYR_DEFAULT_SMP_FRAME_SIZE: usize = 384;
42
43pub struct MCUmgrClient {
47 connection: Connection,
48 smp_frame_size: AtomicUsize,
49}
50
51#[derive(Error, Debug, Diagnostic)]
53pub enum MCUmgrClientError {
54 #[error("Command execution failed")]
56 #[diagnostic(code(mcumgr_toolkit::client::execute))]
57 ExecuteError(#[from] ExecuteError),
58 #[error("Received an unexpected offset value")]
60 #[diagnostic(code(mcumgr_toolkit::client::unexpected_offset))]
61 UnexpectedOffset,
62 #[error("Writer returned an error")]
64 #[diagnostic(code(mcumgr_toolkit::client::writer))]
65 WriterError(#[source] io::Error),
66 #[error("Reader returned an error")]
68 #[diagnostic(code(mcumgr_toolkit::client::reader))]
69 ReaderError(#[source] io::Error),
70 #[error("Received data does not match reported size")]
72 #[diagnostic(code(mcumgr_toolkit::client::size_mismatch))]
73 SizeMismatch,
74 #[error("Received data is missing file size information")]
76 #[diagnostic(code(mcumgr_toolkit::client::missing_size))]
77 MissingSize,
78 #[error("Progress callback returned an error")]
80 #[diagnostic(code(mcumgr_toolkit::client::progress_cb_error))]
81 ProgressCallbackError,
82 #[error("SMP frame size too small for this command")]
84 #[diagnostic(code(mcumgr_toolkit::client::framesize_too_small))]
85 FrameSizeTooSmall(#[source] io::Error),
86 #[error("Device reported checksum mismatch")]
88 #[diagnostic(code(mcumgr_toolkit::client::checksum_mismatch_on_device))]
89 ChecksumMismatchOnDevice,
90 #[error("Firmware image does not match given checksum")]
92 #[diagnostic(code(mcumgr_toolkit::client::checksum_mismatch))]
93 ChecksumMismatch,
94 #[error("Failed to set the device timeout")]
96 #[diagnostic(code(mcumgr_toolkit::client::set_timeout))]
97 SetTimeoutFailed(#[source] Box<dyn std::error::Error + Send + Sync>),
98}
99
100impl MCUmgrClientError {
101 pub fn command_not_supported(&self) -> bool {
103 if let Self::ExecuteError(err) = self {
104 err.command_not_supported()
105 } else {
106 false
107 }
108 }
109}
110
111#[derive(Debug, Serialize, Clone, Eq, PartialEq)]
113pub struct UsbSerialPortInfo {
114 pub identifier: String,
116 pub port_name: String,
118 pub port_info: serialport::UsbPortInfo,
120}
121
122#[derive(Serialize, Clone, Eq, PartialEq)]
126#[serde(transparent)]
127pub struct UsbSerialPorts(pub Vec<UsbSerialPortInfo>);
128impl std::fmt::Display for UsbSerialPorts {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 if self.0.is_empty() {
131 writeln!(f)?;
132 write!(f, " - None -")?;
133 return Ok(());
134 }
135
136 for UsbSerialPortInfo {
137 identifier,
138 port_name,
139 port_info,
140 } in &self.0
141 {
142 writeln!(f)?;
143 write!(f, " - {identifier}")?;
144
145 let mut print_port_string = true;
146 let port_string = format!("({port_name})");
147
148 if port_info.manufacturer.is_some() || port_info.product.is_some() {
149 write!(f, " -")?;
150 if let Some(manufacturer) = &port_info.manufacturer {
151 let mut print_manufacturer = true;
152
153 if let Some(product) = &port_info.product {
154 if product.starts_with(manufacturer) {
155 print_manufacturer = false;
156 }
157 }
158
159 if print_manufacturer {
160 write!(f, " {manufacturer}")?;
161 }
162 }
163 if let Some(product) = &port_info.product {
164 write!(f, " {product}")?;
165
166 if product.ends_with(&port_string) {
167 print_port_string = false;
168 }
169 }
170 }
171
172 if print_port_string {
173 write!(f, " {port_string}")?;
174 }
175 }
176 Ok(())
177 }
178}
179impl std::fmt::Debug for UsbSerialPorts {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 std::fmt::Debug::fmt(&self.0, f)
182 }
183}
184
185#[cfg(feature = "ble")]
186fn ble_identifier_to_str<S>(id: &BleIdentifier, ser: S) -> Result<S::Ok, S::Error>
187where
188 S: serde::Serializer,
189{
190 ser.collect_str(id)
191}
192
193#[cfg(feature = "ble")]
195#[derive(Debug, Serialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
196pub struct BleDeviceInfo {
197 #[serde(serialize_with = "ble_identifier_to_str")]
199 pub id: BleIdentifier,
200 pub name: Option<String>,
202 pub rssi: Option<i16>,
204}
205
206#[cfg(feature = "ble")]
210#[derive(Serialize, Clone, Eq, PartialEq)]
211#[serde(transparent)]
212pub struct BleDevices(pub Vec<BleDeviceInfo>);
213
214#[cfg(feature = "ble")]
215impl std::fmt::Display for BleDevices {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 if self.0.is_empty() {
218 writeln!(f)?;
219 write!(f, " - None -")?;
220 return Ok(());
221 }
222
223 for BleDeviceInfo { id, name, rssi } in &self.0 {
224 writeln!(f)?;
225
226 if let Some(name) = name {
227 write!(f, " - {id} - {name:?}")?;
228 } else {
229 write!(f, " - {id} - <unknown>")?;
230 }
231
232 if let Some(rssi) = rssi {
233 write!(f, " ({rssi} dBm)")?;
234 }
235 }
236 Ok(())
237 }
238}
239
240#[cfg(feature = "ble")]
241impl std::fmt::Debug for BleDevices {
242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 std::fmt::Debug::fmt(&self.0, f)
244 }
245}
246
247#[derive(Error, Debug, Diagnostic)]
249pub enum UdpError {
250 #[error("Failed to open UDP socket")]
252 #[diagnostic(code(mcumgr_toolkit::udp::io_error))]
253 Io(#[from] io::Error),
254}
255
256#[derive(Error, Debug, Diagnostic)]
258pub enum UsbSerialError {
259 #[error("Serialport returned an error")]
261 #[diagnostic(code(mcumgr_toolkit::usb_serial::serialport_error))]
262 SerialPortError(#[from] serialport::Error),
263 #[error("No serial port matched the identifier '{identifier}'\nAvailable ports:\n{available}")]
265 #[diagnostic(code(mcumgr_toolkit::usb_serial::no_matches))]
266 NoMatchingPort {
267 identifier: String,
269 available: UsbSerialPorts,
271 },
272 #[error("Multiple serial ports matched the identifier '{identifier}'\n{ports}")]
274 #[diagnostic(code(mcumgr_toolkit::usb_serial::multiple_matches))]
275 MultipleMatchingPorts {
276 identifier: String,
278 ports: UsbSerialPorts,
280 },
281 #[error("An empty identifier was provided")]
284 #[diagnostic(code(mcumgr_toolkit::usb_serial::empty_identifier))]
285 IdentifierEmpty {
286 ports: UsbSerialPorts,
288 },
289 #[error("The given identifier was not a valid RegEx")]
291 #[diagnostic(code(mcumgr_toolkit::usb_serial::regex_error))]
292 RegexError(#[from] regex::Error),
293}
294
295#[cfg(feature = "ble")]
297#[derive(Error, Debug, Diagnostic)]
298pub enum BleError {
299 #[error("BLE runtime layer returned an error")]
301 #[diagnostic(code(mcumgr_toolkit::ble::runtime))]
302 BleRuntime(#[from] BleRuntimeError),
303 #[error("BLE scanning unexpectedly stopped")]
305 #[diagnostic(code(mcumgr_toolkit::ble::scan_stopped))]
306 ScanStopped,
307 #[error("Device not found\nAvailable devices:\n{available}")]
309 #[diagnostic(code(mcumgr_toolkit::ble::device_not_found))]
310 DeviceNotFound {
311 available: BleDevices,
313 },
314 #[error("An empty identifier was provided")]
317 #[diagnostic(code(mcumgr_toolkit::ble::empty_identifier))]
318 IdentifierEmpty {
319 devices: BleDevices,
321 },
322}
323
324impl MCUmgrClient {
325 pub fn new_from_serial<T: Send + Read + Write + ConfigurableTimeout + 'static>(
338 serial: T,
339 ) -> Self {
340 Self {
341 connection: Connection::new(SerialTransport::new(serial)),
342 smp_frame_size: ZEPHYR_DEFAULT_SMP_FRAME_SIZE.into(),
343 }
344 }
345
346 pub fn new_from_usb_serial(
364 identifier: impl AsRef<str>,
365 baud_rate: u32,
366 timeout: Duration,
367 ) -> Result<Self, UsbSerialError> {
368 let identifier = identifier.as_ref();
369
370 let ports = serialport::available_ports()?
371 .into_iter()
372 .filter_map(|port| {
373 if let serialport::SerialPortType::UsbPort(port_info) = port.port_type {
374 if let Some(interface) = port_info.interface {
375 Some(UsbSerialPortInfo {
376 identifier: format!(
377 "{:04x}:{:04x}:{}",
378 port_info.vid, port_info.pid, interface
379 ),
380 port_name: port.port_name,
381 port_info,
382 })
383 } else {
384 Some(UsbSerialPortInfo {
385 identifier: format!("{:04x}:{:04x}", port_info.vid, port_info.pid),
386 port_name: port.port_name,
387 port_info,
388 })
389 }
390 } else {
391 None
392 }
393 })
394 .collect::<Vec<_>>();
395
396 if identifier.is_empty() {
397 return Err(UsbSerialError::IdentifierEmpty {
398 ports: UsbSerialPorts(ports),
399 });
400 }
401
402 let port_regex = regex::RegexBuilder::new(identifier)
403 .case_insensitive(true)
404 .unicode(true)
405 .build()?;
406
407 let matches = ports
408 .iter()
409 .filter(|port| {
410 if let Some(m) = port_regex.find(&port.identifier) {
411 m.start() == 0
413 } else {
414 false
415 }
416 })
417 .cloned()
418 .collect::<Vec<_>>();
419
420 if matches.len() > 1 {
421 return Err(UsbSerialError::MultipleMatchingPorts {
422 identifier: identifier.to_string(),
423 ports: UsbSerialPorts(matches),
424 });
425 }
426
427 let port_name = match matches.into_iter().next() {
428 Some(port) => port.port_name,
429 None => {
430 return Err(UsbSerialError::NoMatchingPort {
431 identifier: identifier.to_string(),
432 available: UsbSerialPorts(ports),
433 });
434 }
435 };
436
437 let serial = serialport::new(port_name, baud_rate)
438 .timeout(timeout)
439 .open()?;
440
441 Ok(Self::new_from_serial(serial))
442 }
443
444 #[cfg(feature = "ble")]
452 pub fn new_from_ble(
453 identifier: Option<BleIdentifier>,
454 timeout: Duration,
455 ) -> Result<Self, BleError> {
456 Self::new_from_ble_with_scan_callback(identifier, timeout, || {})
457 }
458
459 #[cfg(feature = "ble")]
470 pub fn new_from_ble_with_scan_callback(
471 identifier: Option<BleIdentifier>,
472 timeout: Duration,
473 on_start_scanning: impl FnOnce(),
474 ) -> Result<Self, BleError> {
475 let scan_timeout = Duration::from_secs(3);
476 let connect_timeout = Duration::from_secs(5).max(timeout);
477 let connection = crate::transport::ble::connect_to_device(
478 identifier,
479 scan_timeout,
480 connect_timeout,
481 on_start_scanning,
482 )?;
483
484 let transport = crate::transport::ble::BleTransport::from_connection(connection, timeout)?;
485 Ok(Self {
486 connection: Connection::new(transport),
487 smp_frame_size: ZEPHYR_DEFAULT_SMP_FRAME_SIZE.into(),
488 })
489 }
490
491 pub fn new_from_udp(addr: impl Into<SocketAddr>, timeout: Duration) -> Result<Self, UdpError> {
523 let addr = addr.into();
524 log::debug!("Connecting to {addr} ...");
525 Ok(Self {
526 connection: Connection::new(UdpTransport::new(addr, timeout)?),
527 smp_frame_size: ZEPHYR_DEFAULT_SMP_FRAME_SIZE.into(),
528 })
529 }
530
531 pub fn set_frame_size(&self, smp_frame_size: usize) {
536 self.smp_frame_size
537 .store(smp_frame_size, std::sync::atomic::Ordering::SeqCst);
538 }
539
540 pub fn use_auto_frame_size(&self) -> Result<(), MCUmgrClientError> {
544 let mcumgr_params = self
545 .connection
546 .execute_command(&commands::os::MCUmgrParameters)?;
547
548 let frame_size =
549 (mcumgr_params.buf_size as usize).min(self.connection.max_transport_frame_size());
550
551 log::debug!("Using frame size {}.", frame_size);
552
553 self.smp_frame_size
554 .store(frame_size, std::sync::atomic::Ordering::SeqCst);
555
556 Ok(())
557 }
558
559 pub fn set_timeout(&self, timeout: Duration) -> Result<(), MCUmgrClientError> {
564 self.connection
565 .set_timeout(timeout)
566 .map_err(MCUmgrClientError::SetTimeoutFailed)
567 }
568
569 pub fn set_retries(&self, retries: u8) {
574 self.connection.set_retries(retries)
575 }
576
577 pub fn check_connection(&self) -> Result<(), MCUmgrClientError> {
585 let random_message = rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 16);
586 let response = self.os_echo(&random_message)?;
587 if random_message == response {
588 Ok(())
589 } else {
590 Err(
591 ExecuteError::ReceiveFailed(crate::transport::ReceiveError::UnexpectedResponse)
592 .into(),
593 )
594 }
595 }
596
597 pub fn firmware_update(
607 &self,
608 firmware: impl AsRef<[u8]>,
609 checksum: Option<[u8; 32]>,
610 params: FirmwareUpdateParams,
611 progress: Option<&mut FirmwareUpdateProgressCallback>,
612 ) -> Result<(), FirmwareUpdateError> {
613 firmware_update::firmware_update(self, firmware, checksum, params, progress)
614 }
615
616 pub fn os_echo(&self, msg: impl AsRef<str>) -> Result<String, MCUmgrClientError> {
620 self.connection
621 .execute_command(&commands::os::Echo { d: msg.as_ref() })
622 .map(|resp| resp.r)
623 .map_err(Into::into)
624 }
625
626 pub fn os_task_statistics(
637 &self,
638 ) -> Result<HashMap<String, commands::os::TaskStatisticsEntry>, MCUmgrClientError> {
639 self.connection
640 .execute_command(&commands::os::TaskStatistics)
641 .map(|resp| {
642 let mut tasks = resp.tasks;
643 for stats in tasks.values_mut() {
644 stats.stkuse = stats.stkuse.map(|val| val * 4);
645 stats.stksiz = stats.stksiz.map(|val| val * 4);
646 }
647 tasks
648 })
649 .map_err(Into::into)
650 }
651
652 pub fn os_memory_pool_statistics(
658 &self,
659 ) -> Result<HashMap<String, commands::os::MemoryPoolStatisticsEntry>, MCUmgrClientError> {
660 self.connection
661 .execute_command(&commands::os::MemoryPoolStatistics)
662 .map(|resp| resp.pools)
663 .map_err(Into::into)
664 }
665
666 pub fn os_set_datetime(
668 &self,
669 datetime: chrono::NaiveDateTime,
670 ) -> Result<(), MCUmgrClientError> {
671 self.connection
672 .execute_command(&commands::os::DateTimeSet { datetime })
673 .map(Into::into)
674 .map_err(Into::into)
675 }
676
677 pub fn os_get_datetime(&self) -> Result<chrono::NaiveDateTime, MCUmgrClientError> {
679 self.connection
680 .execute_command(&commands::os::DateTimeGet)
681 .map(|val| val.datetime)
682 .map_err(Into::into)
683 }
684
685 pub fn os_system_reset(
699 &self,
700 force: bool,
701 boot_mode: Option<u8>,
702 ) -> Result<(), MCUmgrClientError> {
703 self.connection
704 .execute_command(&commands::os::SystemReset { force, boot_mode })
705 .map(Into::into)
706 .map_err(Into::into)
707 }
708
709 pub fn os_mcumgr_parameters(
711 &self,
712 ) -> Result<commands::os::MCUmgrParametersResponse, MCUmgrClientError> {
713 self.connection
714 .execute_command(&commands::os::MCUmgrParameters)
715 .map_err(Into::into)
716 }
717
718 pub fn os_application_info(&self, format: Option<&str>) -> Result<String, MCUmgrClientError> {
730 self.connection
731 .execute_command(&commands::os::ApplicationInfo { format })
732 .map(|resp| resp.output)
733 .map_err(Into::into)
734 }
735
736 pub fn os_bootloader_info(&self) -> Result<BootloaderInfo, MCUmgrClientError> {
738 Ok(
739 match self
740 .connection
741 .execute_command(&commands::os::BootloaderInfo)?
742 .bootloader
743 .as_str()
744 {
745 "MCUboot" => {
746 let mode_data = self
747 .connection
748 .execute_command(&commands::os::BootloaderInfoMcubootMode {})?;
749 BootloaderInfo::MCUboot {
750 mode: mode_data.mode,
751 no_downgrade: mode_data.no_downgrade,
752 }
753 }
754 name => BootloaderInfo::Unknown {
755 name: name.to_string(),
756 },
757 },
758 )
759 }
760
761 pub fn image_get_state(&self) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
763 self.connection
764 .execute_command(&commands::image::GetImageState)
765 .map(|val| val.images)
766 .map_err(Into::into)
767 }
768
769 pub fn image_set_state(
785 &self,
786 hash: Option<&[u8]>,
787 confirm: bool,
788 ) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
789 self.connection
790 .execute_command(&commands::image::SetImageState { hash, confirm })
791 .map(|val| val.images)
792 .map_err(Into::into)
793 }
794
795 pub fn image_upload(
813 &self,
814 data: impl AsRef<[u8]>,
815 image: Option<u32>,
816 checksum: Option<[u8; 32]>,
817 upgrade_only: bool,
818 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
819 ) -> Result<(), MCUmgrClientError> {
820 let first_chunk_size_max = image_upload_max_data_chunk_size(
821 self.smp_frame_size
822 .load(std::sync::atomic::Ordering::SeqCst),
823 true,
824 )
825 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
826 let other_chunk_size_max = image_upload_max_data_chunk_size(
827 self.smp_frame_size
828 .load(std::sync::atomic::Ordering::SeqCst),
829 false,
830 )
831 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
832 log::debug!("Max chunk size: {first_chunk_size_max}, {other_chunk_size_max}");
833
834 let data = data.as_ref();
835
836 let actual_checksum: [u8; 32] = Sha256::digest(data).into();
837 if let Some(checksum) = checksum {
838 if actual_checksum != checksum {
839 return Err(MCUmgrClientError::ChecksumMismatch);
840 }
841 }
842
843 let mut offset = 0;
844 let size = data.len();
845
846 let mut checksum_matched = None;
847
848 while offset < size {
849 let upload_response = if offset == 0 {
850 let current_chunk_size = (size - offset).min(first_chunk_size_max);
851 let chunk_data = &data[offset..offset + current_chunk_size];
852
853 let result = self
854 .connection
855 .execute_command(&commands::image::ImageUpload {
856 image,
857 len: Some(size as u64),
858 off: offset as u64,
859 sha: Some(&actual_checksum),
860 data: chunk_data,
861 upgrade: Some(upgrade_only),
862 });
863
864 if let Err(ExecuteError::ReceiveFailed(ReceiveError::Timeout)) = &result {
865 log::warn!(
866 "Timed out during transfer of first chunk. Consider enabling CONFIG_IMG_ERASE_PROGRESSIVELY."
867 )
868 }
869
870 result?
871 } else {
872 let current_chunk_size = (size - offset).min(other_chunk_size_max);
873 let chunk_data = &data[offset..offset + current_chunk_size];
874
875 self.connection
876 .execute_command(&commands::image::ImageUpload {
877 image: None,
878 len: None,
879 off: offset as u64,
880 sha: None,
881 data: chunk_data,
882 upgrade: None,
883 })?
884 };
885
886 offset = upload_response
887 .off
888 .try_into()
889 .map_err(|_| MCUmgrClientError::UnexpectedOffset)?;
890
891 if offset > size {
892 return Err(MCUmgrClientError::UnexpectedOffset);
893 }
894
895 if let Some(progress) = &mut progress {
896 if !progress(offset as u64, size as u64) {
897 return Err(MCUmgrClientError::ProgressCallbackError);
898 };
899 }
900
901 if let Some(is_match) = upload_response.r#match {
902 checksum_matched = Some(is_match);
903 }
904 }
905
906 if let Some(checksum_matched) = checksum_matched {
907 if !checksum_matched {
908 return Err(MCUmgrClientError::ChecksumMismatchOnDevice);
909 }
910 } else {
911 log::warn!("Device did not perform image checksum verification");
912 }
913
914 Ok(())
915 }
916
917 pub fn image_erase(&self, slot: Option<u32>) -> Result<(), MCUmgrClientError> {
924 self.connection
925 .execute_command(&commands::image::ImageErase { slot })
926 .map(Into::into)
927 .map_err(Into::into)
928 }
929
930 pub fn image_slot_info(
932 &self,
933 ) -> Result<Vec<commands::image::SlotInfoImage>, MCUmgrClientError> {
934 self.connection
935 .execute_command(&commands::image::SlotInfo)
936 .map(|val| val.images)
937 .map_err(Into::into)
938 }
939
940 pub fn stats_get_group_data(
947 &self,
948 name: impl AsRef<str>,
949 ) -> Result<HashMap<String, u64>, MCUmgrClientError> {
950 self.connection
951 .execute_command(&commands::stats::GroupData {
952 name: name.as_ref(),
953 })
954 .map(|val| val.fields)
955 .map_err(Into::into)
956 }
957
958 pub fn stats_list_groups(&self) -> Result<Vec<String>, MCUmgrClientError> {
960 self.connection
961 .execute_command(&commands::stats::ListGroups)
962 .map(|val| val.stat_list)
963 .map_err(Into::into)
964 }
965
966 pub fn settings_read(&self, name: impl AsRef<str>) -> Result<Vec<u8>, MCUmgrClientError> {
979 let name = name.as_ref();
980
981 self.settings_read_ext(name, None).map(|val| val.val)
982 }
983
984 pub fn settings_read_ext(
994 &self,
995 name: impl AsRef<str>,
996 max_size: Option<u32>,
997 ) -> Result<commands::settings::ReadSettingResponse, MCUmgrClientError> {
998 let name = name.as_ref();
999
1000 self.connection
1001 .execute_command(&commands::settings::ReadSetting { name, max_size })
1002 .map_err(Into::into)
1003 }
1004
1005 pub fn settings_write(
1013 &self,
1014 name: impl AsRef<str>,
1015 value: &[u8],
1016 ) -> Result<(), MCUmgrClientError> {
1017 let name = name.as_ref();
1018
1019 self.connection
1020 .execute_command(&commands::settings::WriteSetting { name, val: value })
1021 .map(Into::into)
1022 .map_err(Into::into)
1023 }
1024
1025 pub fn settings_delete(&self, name: impl AsRef<str>) -> Result<(), MCUmgrClientError> {
1032 let name = name.as_ref();
1033
1034 self.connection
1035 .execute_command(&commands::settings::DeleteSetting { name })
1036 .map(Into::into)
1037 .map_err(Into::into)
1038 }
1039
1040 pub fn settings_commit(&self) -> Result<(), MCUmgrClientError> {
1043 self.connection
1044 .execute_command(&commands::settings::CommitSettings)
1045 .map(Into::into)
1046 .map_err(Into::into)
1047 }
1048
1049 pub fn settings_load(&self) -> Result<(), MCUmgrClientError> {
1052 self.connection
1053 .execute_command(&commands::settings::LoadSettings)
1054 .map(Into::into)
1055 .map_err(Into::into)
1056 }
1057
1058 pub fn settings_save(&self, name: Option<impl AsRef<str>>) -> Result<(), MCUmgrClientError> {
1065 let name = name.as_ref().map(|val| val.as_ref());
1066
1067 self.connection
1068 .execute_command(&commands::settings::SaveSettings { name })
1069 .map(Into::into)
1070 .map_err(Into::into)
1071 }
1072
1073 pub fn fs_file_download<T: Write>(
1087 &self,
1088 name: impl AsRef<str>,
1089 mut writer: T,
1090 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1091 ) -> Result<(), MCUmgrClientError> {
1092 let name = name.as_ref();
1093 let response = self
1094 .connection
1095 .execute_command(&commands::fs::FileDownload { name, off: 0 })?;
1096
1097 let file_len = response.len.ok_or(MCUmgrClientError::MissingSize)?;
1098 if response.off != 0 {
1099 return Err(MCUmgrClientError::UnexpectedOffset);
1100 }
1101
1102 let mut offset = 0;
1103
1104 if let Some(progress) = &mut progress {
1105 if !progress(offset, file_len) {
1106 return Err(MCUmgrClientError::ProgressCallbackError);
1107 };
1108 }
1109
1110 writer
1111 .write_all(&response.data)
1112 .map_err(MCUmgrClientError::WriterError)?;
1113 offset += response.data.len() as u64;
1114
1115 if let Some(progress) = &mut progress {
1116 if !progress(offset, file_len) {
1117 return Err(MCUmgrClientError::ProgressCallbackError);
1118 };
1119 }
1120
1121 while offset < file_len {
1122 let response = self
1123 .connection
1124 .execute_command(&commands::fs::FileDownload { name, off: offset })?;
1125
1126 if response.off != offset {
1127 return Err(MCUmgrClientError::UnexpectedOffset);
1128 }
1129
1130 writer
1131 .write_all(&response.data)
1132 .map_err(MCUmgrClientError::WriterError)?;
1133 offset += response.data.len() as u64;
1134
1135 if let Some(progress) = &mut progress {
1136 if !progress(offset, file_len) {
1137 return Err(MCUmgrClientError::ProgressCallbackError);
1138 };
1139 }
1140 }
1141
1142 if offset != file_len {
1143 return Err(MCUmgrClientError::SizeMismatch);
1144 }
1145
1146 Ok(())
1147 }
1148
1149 pub fn fs_file_upload<T: Read>(
1165 &self,
1166 name: impl AsRef<str>,
1167 mut reader: T,
1168 size: u64,
1169 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1170 ) -> Result<(), MCUmgrClientError> {
1171 let name = name.as_ref();
1172
1173 let chunk_size_max = file_upload_max_data_chunk_size(
1174 self.smp_frame_size
1175 .load(std::sync::atomic::Ordering::SeqCst),
1176 name,
1177 )
1178 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
1179 let mut data_buffer = vec![0u8; chunk_size_max].into_boxed_slice();
1180
1181 let mut offset = 0;
1182
1183 while offset < size {
1184 let current_chunk_size = (size - offset).min(data_buffer.len() as u64) as usize;
1185
1186 let chunk_buffer = &mut data_buffer[..current_chunk_size];
1187 reader
1188 .read_exact(chunk_buffer)
1189 .map_err(MCUmgrClientError::ReaderError)?;
1190
1191 self.connection.execute_command(&commands::fs::FileUpload {
1192 off: offset,
1193 data: chunk_buffer,
1194 name,
1195 len: if offset == 0 { Some(size) } else { None },
1196 })?;
1197
1198 offset += chunk_buffer.len() as u64;
1199
1200 if let Some(progress) = &mut progress {
1201 if !progress(offset, size) {
1202 return Err(MCUmgrClientError::ProgressCallbackError);
1203 };
1204 }
1205 }
1206
1207 Ok(())
1208 }
1209
1210 pub fn fs_file_status(
1212 &self,
1213 name: impl AsRef<str>,
1214 ) -> Result<commands::fs::FileStatusResponse, MCUmgrClientError> {
1215 self.connection
1216 .execute_command(&commands::fs::FileStatus {
1217 name: name.as_ref(),
1218 })
1219 .map_err(Into::into)
1220 }
1221
1222 pub fn fs_file_checksum(
1234 &self,
1235 name: impl AsRef<str>,
1236 algorithm: Option<impl AsRef<str>>,
1237 offset: u64,
1238 length: Option<u64>,
1239 ) -> Result<commands::fs::FileChecksumResponse, MCUmgrClientError> {
1240 self.connection
1241 .execute_command(&commands::fs::FileChecksum {
1242 name: name.as_ref(),
1243 r#type: algorithm.as_ref().map(AsRef::as_ref),
1244 off: offset,
1245 len: length,
1246 })
1247 .map_err(Into::into)
1248 }
1249
1250 pub fn fs_supported_checksum_types(
1252 &self,
1253 ) -> Result<HashMap<String, commands::fs::FileChecksumProperties>, MCUmgrClientError> {
1254 self.connection
1255 .execute_command(&commands::fs::SupportedFileChecksumTypes)
1256 .map(|val| val.types)
1257 .map_err(Into::into)
1258 }
1259
1260 pub fn fs_file_close(&self) -> Result<(), MCUmgrClientError> {
1262 self.connection
1263 .execute_command(&commands::fs::FileClose)
1264 .map(Into::into)
1265 .map_err(Into::into)
1266 }
1267
1268 pub fn shell_execute(
1280 &self,
1281 argv: &[String],
1282 use_retries: bool,
1283 ) -> Result<(i32, String), MCUmgrClientError> {
1284 let command = commands::shell::ShellCommandLineExecute { argv };
1285
1286 if use_retries {
1287 self.connection.execute_command(&command)
1288 } else {
1289 self.connection.execute_command_without_retries(&command)
1290 }
1291 .map(|ret| (ret.ret, ret.o))
1292 .map_err(Into::into)
1293 }
1294
1295 pub fn enum_get_group_count(&self) -> Result<u16, MCUmgrClientError> {
1302 self.connection
1303 .execute_command(&commands::r#enum::GroupCount)
1304 .map(|ret| ret.count)
1305 .map_err(Into::into)
1306 }
1307
1308 pub fn enum_get_group_ids(&self) -> Result<Vec<u16>, MCUmgrClientError> {
1323 self.connection
1324 .execute_command(&commands::r#enum::ListGroups)
1325 .map(|ret| ret.groups)
1326 .map_err(Into::into)
1327 }
1328
1329 pub fn enum_get_group_id(&self, index: u16) -> Result<u16, MCUmgrClientError> {
1341 self.connection
1342 .execute_command(&commands::r#enum::GroupId { index: Some(index) })
1343 .map(|ret| ret.group)
1344 .map_err(Into::into)
1345 }
1346
1347 pub fn enum_iter_group_ids(&self) -> impl Iterator<Item = Result<u16, MCUmgrClientError>> {
1353 let mut i = 0;
1354 let mut num_elements = None;
1355
1356 std::iter::from_fn(move || -> Option<Result<u16, MCUmgrClientError>> {
1357 let mut num_elements_err = None;
1358 let num_elements =
1359 *num_elements.get_or_insert_with(|| match self.enum_get_group_count() {
1360 Ok(n) => n,
1361 Err(e) => {
1362 num_elements_err = Some(e);
1363 0
1364 }
1365 });
1366 if let Some(err) = num_elements_err {
1367 return Some(Err(err));
1368 }
1369
1370 if i >= num_elements {
1371 None
1372 } else {
1373 Some(match self.enum_get_group_id(i) {
1374 Ok(group_id) => {
1375 i += 1;
1376 Ok(group_id)
1377 }
1378 Err(e) => {
1379 i = num_elements;
1380 Err(e)
1381 }
1382 })
1383 }
1384 })
1385 }
1386
1387 pub fn enum_get_group_details(
1398 &self,
1399 groups: Option<&[u16]>,
1400 ) -> Result<Vec<commands::r#enum::GroupDetailsEntry>, MCUmgrClientError> {
1401 self.connection
1402 .execute_command(&commands::r#enum::GroupDetails { groups })
1403 .map(|ret| ret.groups)
1404 .map_err(Into::into)
1405 }
1406
1407 pub fn zephyr_erase_storage(&self) -> Result<(), MCUmgrClientError> {
1409 self.connection
1410 .execute_command(&commands::zephyr::EraseStorage)
1411 .map(Into::into)
1412 .map_err(Into::into)
1413 }
1414
1415 pub fn raw_command<T: commands::McuMgrCommand>(
1421 &self,
1422 command: &T,
1423 ) -> Result<T::Response, MCUmgrClientError> {
1424 self.connection.execute_command(command).map_err(Into::into)
1425 }
1426}