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 IntoTransport, ReceiveError, SMP_TRANSFER_BUFFER_SIZE,
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::new_from_transport(SerialTransport::new(serial))
341 }
342
343 pub fn new_from_usb_serial(
361 identifier: impl AsRef<str>,
362 baud_rate: u32,
363 timeout: Duration,
364 ) -> Result<Self, UsbSerialError> {
365 let identifier = identifier.as_ref();
366
367 let ports = serialport::available_ports()?
368 .into_iter()
369 .filter_map(|port| {
370 if let serialport::SerialPortType::UsbPort(port_info) = port.port_type {
371 if let Some(interface) = port_info.interface {
372 Some(UsbSerialPortInfo {
373 identifier: format!(
374 "{:04x}:{:04x}:{}",
375 port_info.vid, port_info.pid, interface
376 ),
377 port_name: port.port_name,
378 port_info,
379 })
380 } else {
381 Some(UsbSerialPortInfo {
382 identifier: format!("{:04x}:{:04x}", port_info.vid, port_info.pid),
383 port_name: port.port_name,
384 port_info,
385 })
386 }
387 } else {
388 None
389 }
390 })
391 .collect::<Vec<_>>();
392
393 if identifier.is_empty() {
394 return Err(UsbSerialError::IdentifierEmpty {
395 ports: UsbSerialPorts(ports),
396 });
397 }
398
399 let port_regex = regex::RegexBuilder::new(identifier)
400 .case_insensitive(true)
401 .unicode(true)
402 .build()?;
403
404 let matches = ports
405 .iter()
406 .filter(|port| {
407 if let Some(m) = port_regex.find(&port.identifier) {
408 m.start() == 0
410 } else {
411 false
412 }
413 })
414 .cloned()
415 .collect::<Vec<_>>();
416
417 if matches.len() > 1 {
418 return Err(UsbSerialError::MultipleMatchingPorts {
419 identifier: identifier.to_string(),
420 ports: UsbSerialPorts(matches),
421 });
422 }
423
424 let port_name = match matches.into_iter().next() {
425 Some(port) => port.port_name,
426 None => {
427 return Err(UsbSerialError::NoMatchingPort {
428 identifier: identifier.to_string(),
429 available: UsbSerialPorts(ports),
430 });
431 }
432 };
433
434 let serial = serialport::new(port_name, baud_rate)
435 .timeout(timeout)
436 .open()?;
437
438 Ok(Self::new_from_serial(serial))
439 }
440
441 #[cfg(feature = "ble")]
449 pub fn new_from_ble(
450 identifier: Option<BleIdentifier>,
451 timeout: Duration,
452 ) -> Result<Self, BleError> {
453 Self::new_from_ble_with_scan_callback(identifier, timeout, || {})
454 }
455
456 #[cfg(feature = "ble")]
467 pub fn new_from_ble_with_scan_callback(
468 identifier: Option<BleIdentifier>,
469 timeout: Duration,
470 on_start_scanning: impl FnOnce(),
471 ) -> Result<Self, BleError> {
472 let scan_timeout = Duration::from_secs(3);
473 let connect_timeout = Duration::from_secs(5).max(timeout);
474 let connection = crate::transport::ble::connect_to_device(
475 identifier,
476 scan_timeout,
477 connect_timeout,
478 on_start_scanning,
479 )?;
480
481 let transport = crate::transport::ble::BleTransport::from_connection(connection, timeout)?;
482 Ok(Self::new_from_transport(transport))
483 }
484
485 pub fn new_from_transport<T: IntoTransport>(transport: T) -> Self {
492 Self {
493 connection: Connection::new(transport),
494 smp_frame_size: ZEPHYR_DEFAULT_SMP_FRAME_SIZE.into(),
495 }
496 }
497
498 pub fn new_from_udp(addr: impl Into<SocketAddr>, timeout: Duration) -> Result<Self, UdpError> {
530 let addr = addr.into();
531 log::debug!("Connecting to {addr} ...");
532 Ok(Self::new_from_transport(UdpTransport::new(addr, timeout)?))
533 }
534
535 pub fn set_frame_size(&self, smp_frame_size: usize) {
540 self.smp_frame_size
541 .store(smp_frame_size, std::sync::atomic::Ordering::SeqCst);
542 }
543
544 pub fn use_auto_frame_size(&self) -> Result<(), MCUmgrClientError> {
548 let mcumgr_params = self
549 .connection
550 .execute_command(&commands::os::MCUmgrParameters)?;
551
552 let frame_size = (mcumgr_params.buf_size as usize)
553 .min(SMP_TRANSFER_BUFFER_SIZE)
554 .min(self.connection.max_transport_frame_size());
555
556 log::debug!("Using frame size {}.", frame_size);
557
558 self.smp_frame_size
559 .store(frame_size, std::sync::atomic::Ordering::SeqCst);
560
561 Ok(())
562 }
563
564 pub fn set_timeout(&self, timeout: Duration) -> Result<(), MCUmgrClientError> {
569 self.connection
570 .set_timeout(timeout)
571 .map_err(MCUmgrClientError::SetTimeoutFailed)
572 }
573
574 pub fn set_retries(&self, retries: u8) {
579 self.connection.set_retries(retries)
580 }
581
582 pub fn check_connection(&self) -> Result<(), MCUmgrClientError> {
590 let random_message = rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 16);
591 let response = self.os_echo(&random_message)?;
592 if random_message == response {
593 Ok(())
594 } else {
595 Err(
596 ExecuteError::ReceiveFailed(crate::transport::ReceiveError::UnexpectedResponse)
597 .into(),
598 )
599 }
600 }
601
602 pub fn firmware_update(
612 &self,
613 firmware: impl AsRef<[u8]>,
614 checksum: Option<[u8; 32]>,
615 params: FirmwareUpdateParams,
616 progress: Option<&mut FirmwareUpdateProgressCallback>,
617 ) -> Result<(), FirmwareUpdateError> {
618 firmware_update::firmware_update(self, firmware, checksum, params, progress)
619 }
620
621 pub fn os_echo(&self, msg: impl AsRef<str>) -> Result<String, MCUmgrClientError> {
625 self.connection
626 .execute_command(&commands::os::Echo { d: msg.as_ref() })
627 .map(|resp| resp.r)
628 .map_err(Into::into)
629 }
630
631 pub fn os_task_statistics(
642 &self,
643 ) -> Result<HashMap<String, commands::os::TaskStatisticsEntry>, MCUmgrClientError> {
644 self.connection
645 .execute_command(&commands::os::TaskStatistics)
646 .map(|resp| {
647 let mut tasks = resp.tasks;
648 for stats in tasks.values_mut() {
649 stats.stkuse = stats.stkuse.map(|val| val * 4);
650 stats.stksiz = stats.stksiz.map(|val| val * 4);
651 }
652 tasks
653 })
654 .map_err(Into::into)
655 }
656
657 pub fn os_memory_pool_statistics(
663 &self,
664 ) -> Result<HashMap<String, commands::os::MemoryPoolStatisticsEntry>, MCUmgrClientError> {
665 self.connection
666 .execute_command(&commands::os::MemoryPoolStatistics)
667 .map(|resp| resp.pools)
668 .map_err(Into::into)
669 }
670
671 pub fn os_set_datetime(
673 &self,
674 datetime: chrono::NaiveDateTime,
675 ) -> Result<(), MCUmgrClientError> {
676 self.connection
677 .execute_command(&commands::os::DateTimeSet { datetime })
678 .map(Into::into)
679 .map_err(Into::into)
680 }
681
682 pub fn os_get_datetime(&self) -> Result<chrono::NaiveDateTime, MCUmgrClientError> {
684 self.connection
685 .execute_command(&commands::os::DateTimeGet)
686 .map(|val| val.datetime)
687 .map_err(Into::into)
688 }
689
690 pub fn os_system_reset(
704 &self,
705 force: bool,
706 boot_mode: Option<u8>,
707 ) -> Result<(), MCUmgrClientError> {
708 self.connection
709 .execute_command(&commands::os::SystemReset { force, boot_mode })
710 .map(Into::into)
711 .map_err(Into::into)
712 }
713
714 pub fn os_mcumgr_parameters(
716 &self,
717 ) -> Result<commands::os::MCUmgrParametersResponse, MCUmgrClientError> {
718 self.connection
719 .execute_command(&commands::os::MCUmgrParameters)
720 .map_err(Into::into)
721 }
722
723 pub fn os_application_info(&self, format: Option<&str>) -> Result<String, MCUmgrClientError> {
735 self.connection
736 .execute_command(&commands::os::ApplicationInfo { format })
737 .map(|resp| resp.output)
738 .map_err(Into::into)
739 }
740
741 pub fn os_bootloader_info(&self) -> Result<BootloaderInfo, MCUmgrClientError> {
743 Ok(
744 match self
745 .connection
746 .execute_command(&commands::os::BootloaderInfo)?
747 .bootloader
748 .as_str()
749 {
750 "MCUboot" => {
751 let mode_data = self
752 .connection
753 .execute_command(&commands::os::BootloaderInfoMcubootMode {})?;
754 BootloaderInfo::MCUboot {
755 mode: mode_data.mode,
756 no_downgrade: mode_data.no_downgrade,
757 }
758 }
759 name => BootloaderInfo::Unknown {
760 name: name.to_string(),
761 },
762 },
763 )
764 }
765
766 pub fn image_get_state(&self) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
768 self.connection
769 .execute_command(&commands::image::GetImageState)
770 .map(|val| val.images)
771 .map_err(Into::into)
772 }
773
774 pub fn image_set_state(
790 &self,
791 hash: Option<&[u8]>,
792 confirm: bool,
793 ) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
794 self.connection
795 .execute_command(&commands::image::SetImageState { hash, confirm })
796 .map(|val| val.images)
797 .map_err(Into::into)
798 }
799
800 pub fn image_upload(
818 &self,
819 data: impl AsRef<[u8]>,
820 image: Option<u32>,
821 checksum: Option<[u8; 32]>,
822 upgrade_only: bool,
823 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
824 ) -> Result<(), MCUmgrClientError> {
825 let first_chunk_size_max = image_upload_max_data_chunk_size(
826 self.smp_frame_size
827 .load(std::sync::atomic::Ordering::SeqCst),
828 true,
829 )
830 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
831 let other_chunk_size_max = image_upload_max_data_chunk_size(
832 self.smp_frame_size
833 .load(std::sync::atomic::Ordering::SeqCst),
834 false,
835 )
836 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
837 log::debug!("Max chunk size: {first_chunk_size_max}, {other_chunk_size_max}");
838
839 let data = data.as_ref();
840
841 let actual_checksum: [u8; 32] = Sha256::digest(data).into();
842 if let Some(checksum) = checksum {
843 if actual_checksum != checksum {
844 return Err(MCUmgrClientError::ChecksumMismatch);
845 }
846 }
847
848 let mut offset = 0;
849 let size = data.len();
850
851 let mut checksum_matched = None;
852
853 while offset < size {
854 let upload_response = if offset == 0 {
855 let current_chunk_size = (size - offset).min(first_chunk_size_max);
856 let chunk_data = &data[offset..offset + current_chunk_size];
857
858 let result = self
859 .connection
860 .execute_command(&commands::image::ImageUpload {
861 image,
862 len: Some(size as u64),
863 off: offset as u64,
864 sha: Some(&actual_checksum),
865 data: chunk_data,
866 upgrade: Some(upgrade_only),
867 });
868
869 if let Err(ExecuteError::ReceiveFailed(ReceiveError::Timeout)) = &result {
870 log::warn!(
871 "Timed out during transfer of first chunk. Consider enabling CONFIG_IMG_ERASE_PROGRESSIVELY."
872 )
873 }
874
875 result?
876 } else {
877 let current_chunk_size = (size - offset).min(other_chunk_size_max);
878 let chunk_data = &data[offset..offset + current_chunk_size];
879
880 self.connection
881 .execute_command(&commands::image::ImageUpload {
882 image: None,
883 len: None,
884 off: offset as u64,
885 sha: None,
886 data: chunk_data,
887 upgrade: None,
888 })?
889 };
890
891 offset = upload_response
892 .off
893 .try_into()
894 .map_err(|_| MCUmgrClientError::UnexpectedOffset)?;
895
896 if offset > size {
897 return Err(MCUmgrClientError::UnexpectedOffset);
898 }
899
900 if let Some(progress) = &mut progress {
901 if !progress(offset as u64, size as u64) {
902 return Err(MCUmgrClientError::ProgressCallbackError);
903 };
904 }
905
906 if let Some(is_match) = upload_response.r#match {
907 checksum_matched = Some(is_match);
908 }
909 }
910
911 if let Some(checksum_matched) = checksum_matched {
912 if !checksum_matched {
913 return Err(MCUmgrClientError::ChecksumMismatchOnDevice);
914 }
915 } else {
916 log::warn!("Device did not perform image checksum verification");
917 }
918
919 Ok(())
920 }
921
922 pub fn image_erase(&self, slot: Option<u32>) -> Result<(), MCUmgrClientError> {
929 self.connection
930 .execute_command(&commands::image::ImageErase { slot })
931 .map(Into::into)
932 .map_err(Into::into)
933 }
934
935 pub fn image_slot_info(
937 &self,
938 ) -> Result<Vec<commands::image::SlotInfoImage>, MCUmgrClientError> {
939 self.connection
940 .execute_command(&commands::image::SlotInfo)
941 .map(|val| val.images)
942 .map_err(Into::into)
943 }
944
945 pub fn stats_get_group_data(
952 &self,
953 name: impl AsRef<str>,
954 ) -> Result<HashMap<String, u64>, MCUmgrClientError> {
955 self.connection
956 .execute_command(&commands::stats::GroupData {
957 name: name.as_ref(),
958 })
959 .map(|val| val.fields)
960 .map_err(Into::into)
961 }
962
963 pub fn stats_list_groups(&self) -> Result<Vec<String>, MCUmgrClientError> {
965 self.connection
966 .execute_command(&commands::stats::ListGroups)
967 .map(|val| val.stat_list)
968 .map_err(Into::into)
969 }
970
971 pub fn settings_read(&self, name: impl AsRef<str>) -> Result<Vec<u8>, MCUmgrClientError> {
984 let name = name.as_ref();
985
986 self.settings_read_ext(name, None).map(|val| val.val)
987 }
988
989 pub fn settings_read_ext(
999 &self,
1000 name: impl AsRef<str>,
1001 max_size: Option<u32>,
1002 ) -> Result<commands::settings::ReadSettingResponse, MCUmgrClientError> {
1003 let name = name.as_ref();
1004
1005 self.connection
1006 .execute_command(&commands::settings::ReadSetting { name, max_size })
1007 .map_err(Into::into)
1008 }
1009
1010 pub fn settings_write(
1018 &self,
1019 name: impl AsRef<str>,
1020 value: &[u8],
1021 ) -> Result<(), MCUmgrClientError> {
1022 let name = name.as_ref();
1023
1024 self.connection
1025 .execute_command(&commands::settings::WriteSetting { name, val: value })
1026 .map(Into::into)
1027 .map_err(Into::into)
1028 }
1029
1030 pub fn settings_delete(&self, name: impl AsRef<str>) -> Result<(), MCUmgrClientError> {
1037 let name = name.as_ref();
1038
1039 self.connection
1040 .execute_command(&commands::settings::DeleteSetting { name })
1041 .map(Into::into)
1042 .map_err(Into::into)
1043 }
1044
1045 pub fn settings_commit(&self) -> Result<(), MCUmgrClientError> {
1048 self.connection
1049 .execute_command(&commands::settings::CommitSettings)
1050 .map(Into::into)
1051 .map_err(Into::into)
1052 }
1053
1054 pub fn settings_load(&self) -> Result<(), MCUmgrClientError> {
1057 self.connection
1058 .execute_command(&commands::settings::LoadSettings)
1059 .map(Into::into)
1060 .map_err(Into::into)
1061 }
1062
1063 pub fn settings_save(&self, name: Option<impl AsRef<str>>) -> Result<(), MCUmgrClientError> {
1070 let name = name.as_ref().map(|val| val.as_ref());
1071
1072 self.connection
1073 .execute_command(&commands::settings::SaveSettings { name })
1074 .map(Into::into)
1075 .map_err(Into::into)
1076 }
1077
1078 pub fn fs_file_download<T: Write>(
1092 &self,
1093 name: impl AsRef<str>,
1094 mut writer: T,
1095 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1096 ) -> Result<(), MCUmgrClientError> {
1097 let name = name.as_ref();
1098 let response = self
1099 .connection
1100 .execute_command(&commands::fs::FileDownload { name, off: 0 })?;
1101
1102 let file_len = response.len.ok_or(MCUmgrClientError::MissingSize)?;
1103 if response.off != 0 {
1104 return Err(MCUmgrClientError::UnexpectedOffset);
1105 }
1106
1107 let mut offset = 0;
1108
1109 if let Some(progress) = &mut progress {
1110 if !progress(offset, file_len) {
1111 return Err(MCUmgrClientError::ProgressCallbackError);
1112 };
1113 }
1114
1115 writer
1116 .write_all(&response.data)
1117 .map_err(MCUmgrClientError::WriterError)?;
1118 offset += response.data.len() as u64;
1119
1120 if let Some(progress) = &mut progress {
1121 if !progress(offset, file_len) {
1122 return Err(MCUmgrClientError::ProgressCallbackError);
1123 };
1124 }
1125
1126 while offset < file_len {
1127 let response = self
1128 .connection
1129 .execute_command(&commands::fs::FileDownload { name, off: offset })?;
1130
1131 if response.off != offset {
1132 return Err(MCUmgrClientError::UnexpectedOffset);
1133 }
1134
1135 writer
1136 .write_all(&response.data)
1137 .map_err(MCUmgrClientError::WriterError)?;
1138 offset += response.data.len() as u64;
1139
1140 if let Some(progress) = &mut progress {
1141 if !progress(offset, file_len) {
1142 return Err(MCUmgrClientError::ProgressCallbackError);
1143 };
1144 }
1145 }
1146
1147 if offset != file_len {
1148 return Err(MCUmgrClientError::SizeMismatch);
1149 }
1150
1151 Ok(())
1152 }
1153
1154 pub fn fs_file_upload<T: Read>(
1170 &self,
1171 name: impl AsRef<str>,
1172 mut reader: T,
1173 size: u64,
1174 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1175 ) -> Result<(), MCUmgrClientError> {
1176 let name = name.as_ref();
1177
1178 let chunk_size_max = file_upload_max_data_chunk_size(
1179 self.smp_frame_size
1180 .load(std::sync::atomic::Ordering::SeqCst),
1181 name,
1182 )
1183 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
1184 let mut data_buffer = vec![0u8; chunk_size_max].into_boxed_slice();
1185
1186 let mut offset = 0;
1187
1188 while offset < size {
1189 let current_chunk_size = (size - offset).min(data_buffer.len() as u64) as usize;
1190
1191 let chunk_buffer = &mut data_buffer[..current_chunk_size];
1192 reader
1193 .read_exact(chunk_buffer)
1194 .map_err(MCUmgrClientError::ReaderError)?;
1195
1196 self.connection.execute_command(&commands::fs::FileUpload {
1197 off: offset,
1198 data: chunk_buffer,
1199 name,
1200 len: if offset == 0 { Some(size) } else { None },
1201 })?;
1202
1203 offset += chunk_buffer.len() as u64;
1204
1205 if let Some(progress) = &mut progress {
1206 if !progress(offset, size) {
1207 return Err(MCUmgrClientError::ProgressCallbackError);
1208 };
1209 }
1210 }
1211
1212 Ok(())
1213 }
1214
1215 pub fn fs_file_status(
1217 &self,
1218 name: impl AsRef<str>,
1219 ) -> Result<commands::fs::FileStatusResponse, MCUmgrClientError> {
1220 self.connection
1221 .execute_command(&commands::fs::FileStatus {
1222 name: name.as_ref(),
1223 })
1224 .map_err(Into::into)
1225 }
1226
1227 pub fn fs_file_checksum(
1239 &self,
1240 name: impl AsRef<str>,
1241 algorithm: Option<impl AsRef<str>>,
1242 offset: u64,
1243 length: Option<u64>,
1244 ) -> Result<commands::fs::FileChecksumResponse, MCUmgrClientError> {
1245 self.connection
1246 .execute_command(&commands::fs::FileChecksum {
1247 name: name.as_ref(),
1248 r#type: algorithm.as_ref().map(AsRef::as_ref),
1249 off: offset,
1250 len: length,
1251 })
1252 .map_err(Into::into)
1253 }
1254
1255 pub fn fs_supported_checksum_types(
1257 &self,
1258 ) -> Result<HashMap<String, commands::fs::FileChecksumProperties>, MCUmgrClientError> {
1259 self.connection
1260 .execute_command(&commands::fs::SupportedFileChecksumTypes)
1261 .map(|val| val.types)
1262 .map_err(Into::into)
1263 }
1264
1265 pub fn fs_file_close(&self) -> Result<(), MCUmgrClientError> {
1267 self.connection
1268 .execute_command(&commands::fs::FileClose)
1269 .map(Into::into)
1270 .map_err(Into::into)
1271 }
1272
1273 pub fn shell_execute(
1285 &self,
1286 argv: &[String],
1287 use_retries: bool,
1288 ) -> Result<(i32, String), MCUmgrClientError> {
1289 let command = commands::shell::ShellCommandLineExecute { argv };
1290
1291 if use_retries {
1292 self.connection.execute_command(&command)
1293 } else {
1294 self.connection.execute_command_without_retries(&command)
1295 }
1296 .map(|ret| (ret.ret, ret.o))
1297 .map_err(Into::into)
1298 }
1299
1300 pub fn enum_get_group_count(&self) -> Result<u16, MCUmgrClientError> {
1307 self.connection
1308 .execute_command(&commands::r#enum::GroupCount)
1309 .map(|ret| ret.count)
1310 .map_err(Into::into)
1311 }
1312
1313 pub fn enum_get_group_ids(&self) -> Result<Vec<u16>, MCUmgrClientError> {
1328 self.connection
1329 .execute_command(&commands::r#enum::ListGroups)
1330 .map(|ret| ret.groups)
1331 .map_err(Into::into)
1332 }
1333
1334 pub fn enum_get_group_id(&self, index: u16) -> Result<u16, MCUmgrClientError> {
1346 self.connection
1347 .execute_command(&commands::r#enum::GroupId { index: Some(index) })
1348 .map(|ret| ret.group)
1349 .map_err(Into::into)
1350 }
1351
1352 pub fn enum_iter_group_ids(&self) -> impl Iterator<Item = Result<u16, MCUmgrClientError>> {
1358 let mut i = 0;
1359 let mut num_elements = None;
1360
1361 std::iter::from_fn(move || -> Option<Result<u16, MCUmgrClientError>> {
1362 let mut num_elements_err = None;
1363 let num_elements =
1364 *num_elements.get_or_insert_with(|| match self.enum_get_group_count() {
1365 Ok(n) => n,
1366 Err(e) => {
1367 num_elements_err = Some(e);
1368 0
1369 }
1370 });
1371 if let Some(err) = num_elements_err {
1372 return Some(Err(err));
1373 }
1374
1375 if i >= num_elements {
1376 None
1377 } else {
1378 Some(match self.enum_get_group_id(i) {
1379 Ok(group_id) => {
1380 i += 1;
1381 Ok(group_id)
1382 }
1383 Err(e) => {
1384 i = num_elements;
1385 Err(e)
1386 }
1387 })
1388 }
1389 })
1390 }
1391
1392 pub fn enum_get_group_details(
1403 &self,
1404 groups: Option<&[u16]>,
1405 ) -> Result<Vec<commands::r#enum::GroupDetailsEntry>, MCUmgrClientError> {
1406 self.connection
1407 .execute_command(&commands::r#enum::GroupDetails { groups })
1408 .map(|ret| ret.groups)
1409 .map_err(Into::into)
1410 }
1411
1412 pub fn zephyr_erase_storage(&self) -> Result<(), MCUmgrClientError> {
1414 self.connection
1415 .execute_command(&commands::zephyr::EraseStorage)
1416 .map(Into::into)
1417 .map_err(Into::into)
1418 }
1419
1420 pub fn raw_command<T: commands::McuMgrCommand>(
1426 &self,
1427 command: &T,
1428 ) -> Result<T::Response, MCUmgrClientError> {
1429 self.connection.execute_command(command).map_err(Into::into)
1430 }
1431}