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
35const ZEPHYR_DEFAULT_SMP_FRAME_SIZE: usize = 384;
39
40pub struct MCUmgrClient {
44 connection: Connection,
45 smp_frame_size: AtomicUsize,
46}
47
48#[derive(Error, Debug, Diagnostic)]
50pub enum MCUmgrClientError {
51 #[error("Command execution failed")]
53 #[diagnostic(code(mcumgr_toolkit::client::execute))]
54 ExecuteError(#[from] ExecuteError),
55 #[error("Received an unexpected offset value")]
57 #[diagnostic(code(mcumgr_toolkit::client::unexpected_offset))]
58 UnexpectedOffset,
59 #[error("Writer returned an error")]
61 #[diagnostic(code(mcumgr_toolkit::client::writer))]
62 WriterError(#[source] io::Error),
63 #[error("Reader returned an error")]
65 #[diagnostic(code(mcumgr_toolkit::client::reader))]
66 ReaderError(#[source] io::Error),
67 #[error("Received data does not match reported size")]
69 #[diagnostic(code(mcumgr_toolkit::client::size_mismatch))]
70 SizeMismatch,
71 #[error("Received data is missing file size information")]
73 #[diagnostic(code(mcumgr_toolkit::client::missing_size))]
74 MissingSize,
75 #[error("Progress callback returned an error")]
77 #[diagnostic(code(mcumgr_toolkit::client::progress_cb_error))]
78 ProgressCallbackError,
79 #[error("SMP frame size too small for this command")]
81 #[diagnostic(code(mcumgr_toolkit::client::framesize_too_small))]
82 FrameSizeTooSmall(#[source] io::Error),
83 #[error("Device reported checksum mismatch")]
85 #[diagnostic(code(mcumgr_toolkit::client::checksum_mismatch_on_device))]
86 ChecksumMismatchOnDevice,
87 #[error("Firmware image does not match given checksum")]
89 #[diagnostic(code(mcumgr_toolkit::client::checksum_mismatch))]
90 ChecksumMismatch,
91 #[error("Failed to set the device timeout")]
93 #[diagnostic(code(mcumgr_toolkit::client::set_timeout))]
94 SetTimeoutFailed(#[source] Box<dyn std::error::Error + Send + Sync>),
95}
96
97impl MCUmgrClientError {
98 pub fn command_not_supported(&self) -> bool {
100 if let Self::ExecuteError(err) = self {
101 err.command_not_supported()
102 } else {
103 false
104 }
105 }
106}
107
108#[derive(Debug, Serialize, Clone, Eq, PartialEq)]
110pub struct UsbSerialPortInfo {
111 pub identifier: String,
113 pub port_name: String,
115 pub port_info: serialport::UsbPortInfo,
117}
118
119#[derive(Serialize, Clone, Eq, PartialEq)]
123#[serde(transparent)]
124pub struct UsbSerialPorts(pub Vec<UsbSerialPortInfo>);
125impl std::fmt::Display for UsbSerialPorts {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 if self.0.is_empty() {
128 writeln!(f)?;
129 write!(f, " - None -")?;
130 return Ok(());
131 }
132
133 for UsbSerialPortInfo {
134 identifier,
135 port_name,
136 port_info,
137 } in &self.0
138 {
139 writeln!(f)?;
140 write!(f, " - {identifier}")?;
141
142 let mut print_port_string = true;
143 let port_string = format!("({port_name})");
144
145 if port_info.manufacturer.is_some() || port_info.product.is_some() {
146 write!(f, " -")?;
147 if let Some(manufacturer) = &port_info.manufacturer {
148 let mut print_manufacturer = true;
149
150 if let Some(product) = &port_info.product {
151 if product.starts_with(manufacturer) {
152 print_manufacturer = false;
153 }
154 }
155
156 if print_manufacturer {
157 write!(f, " {manufacturer}")?;
158 }
159 }
160 if let Some(product) = &port_info.product {
161 write!(f, " {product}")?;
162
163 if product.ends_with(&port_string) {
164 print_port_string = false;
165 }
166 }
167 }
168
169 if print_port_string {
170 write!(f, " {port_string}")?;
171 }
172 }
173 Ok(())
174 }
175}
176impl std::fmt::Debug for UsbSerialPorts {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 std::fmt::Debug::fmt(&self.0, f)
179 }
180}
181
182#[derive(Error, Debug, Diagnostic)]
184pub enum UdpError {
185 #[error("Failed to open UDP socket")]
187 #[diagnostic(code(mcumgr_toolkit::udp::io_error))]
188 Io(#[from] io::Error),
189}
190
191#[derive(Error, Debug, Diagnostic)]
193pub enum UsbSerialError {
194 #[error("Serialport returned an error")]
196 #[diagnostic(code(mcumgr_toolkit::usb_serial::serialport_error))]
197 SerialPortError(#[from] serialport::Error),
198 #[error("No serial port matched the identifier '{identifier}'\nAvailable ports:\n{available}")]
200 #[diagnostic(code(mcumgr_toolkit::usb_serial::no_matches))]
201 NoMatchingPort {
202 identifier: String,
204 available: UsbSerialPorts,
206 },
207 #[error("Multiple serial ports matched the identifier '{identifier}'\n{ports}")]
209 #[diagnostic(code(mcumgr_toolkit::usb_serial::multiple_matches))]
210 MultipleMatchingPorts {
211 identifier: String,
213 ports: UsbSerialPorts,
215 },
216 #[error("An empty identifier was provided")]
219 #[diagnostic(code(mcumgr_toolkit::usb_serial::empty_identifier))]
220 IdentifierEmpty {
221 ports: UsbSerialPorts,
223 },
224 #[error("The given identifier was not a valid RegEx")]
226 #[diagnostic(code(mcumgr_toolkit::usb_serial::regex_error))]
227 RegexError(#[from] regex::Error),
228}
229
230impl MCUmgrClient {
231 pub fn new_from_serial<T: Send + Read + Write + ConfigurableTimeout + 'static>(
244 serial: T,
245 ) -> Self {
246 Self {
247 connection: Connection::new(SerialTransport::new(serial)),
248 smp_frame_size: ZEPHYR_DEFAULT_SMP_FRAME_SIZE.into(),
249 }
250 }
251
252 pub fn new_from_usb_serial(
270 identifier: impl AsRef<str>,
271 baud_rate: u32,
272 timeout: Duration,
273 ) -> Result<Self, UsbSerialError> {
274 let identifier = identifier.as_ref();
275
276 let ports = serialport::available_ports()?
277 .into_iter()
278 .filter_map(|port| {
279 if let serialport::SerialPortType::UsbPort(port_info) = port.port_type {
280 if let Some(interface) = port_info.interface {
281 Some(UsbSerialPortInfo {
282 identifier: format!(
283 "{:04x}:{:04x}:{}",
284 port_info.vid, port_info.pid, interface
285 ),
286 port_name: port.port_name,
287 port_info,
288 })
289 } else {
290 Some(UsbSerialPortInfo {
291 identifier: format!("{:04x}:{:04x}", port_info.vid, port_info.pid),
292 port_name: port.port_name,
293 port_info,
294 })
295 }
296 } else {
297 None
298 }
299 })
300 .collect::<Vec<_>>();
301
302 if identifier.is_empty() {
303 return Err(UsbSerialError::IdentifierEmpty {
304 ports: UsbSerialPorts(ports),
305 });
306 }
307
308 let port_regex = regex::RegexBuilder::new(identifier)
309 .case_insensitive(true)
310 .unicode(true)
311 .build()?;
312
313 let matches = ports
314 .iter()
315 .filter(|port| {
316 if let Some(m) = port_regex.find(&port.identifier) {
317 m.start() == 0
319 } else {
320 false
321 }
322 })
323 .cloned()
324 .collect::<Vec<_>>();
325
326 if matches.len() > 1 {
327 return Err(UsbSerialError::MultipleMatchingPorts {
328 identifier: identifier.to_string(),
329 ports: UsbSerialPorts(matches),
330 });
331 }
332
333 let port_name = match matches.into_iter().next() {
334 Some(port) => port.port_name,
335 None => {
336 return Err(UsbSerialError::NoMatchingPort {
337 identifier: identifier.to_string(),
338 available: UsbSerialPorts(ports),
339 });
340 }
341 };
342
343 let serial = serialport::new(port_name, baud_rate)
344 .timeout(timeout)
345 .open()?;
346
347 Ok(Self::new_from_serial(serial))
348 }
349
350 pub fn new_from_udp(addr: impl Into<SocketAddr>, timeout: Duration) -> Result<Self, UdpError> {
382 let addr = addr.into();
383 log::debug!("Connecting to {addr} ...");
384 Ok(Self {
385 connection: Connection::new(UdpTransport::new(addr, timeout)?),
386 smp_frame_size: ZEPHYR_DEFAULT_SMP_FRAME_SIZE.into(),
387 })
388 }
389
390 pub fn set_frame_size(&self, smp_frame_size: usize) {
395 self.smp_frame_size
396 .store(smp_frame_size, std::sync::atomic::Ordering::SeqCst);
397 }
398
399 pub fn use_auto_frame_size(&self) -> Result<(), MCUmgrClientError> {
403 let mcumgr_params = self
404 .connection
405 .execute_command(&commands::os::MCUmgrParameters)?;
406
407 let frame_size =
408 (mcumgr_params.buf_size as usize).min(self.connection.max_transport_frame_size());
409
410 log::debug!("Using frame size {}.", frame_size);
411
412 self.smp_frame_size
413 .store(frame_size, std::sync::atomic::Ordering::SeqCst);
414
415 Ok(())
416 }
417
418 pub fn set_timeout(&self, timeout: Duration) -> Result<(), MCUmgrClientError> {
423 self.connection
424 .set_timeout(timeout)
425 .map_err(MCUmgrClientError::SetTimeoutFailed)
426 }
427
428 pub fn set_retries(&self, retries: u8) {
433 self.connection.set_retries(retries)
434 }
435
436 pub fn check_connection(&self) -> Result<(), MCUmgrClientError> {
444 let random_message = rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 16);
445 let response = self.os_echo(&random_message)?;
446 if random_message == response {
447 Ok(())
448 } else {
449 Err(
450 ExecuteError::ReceiveFailed(crate::transport::ReceiveError::UnexpectedResponse)
451 .into(),
452 )
453 }
454 }
455
456 pub fn firmware_update(
466 &self,
467 firmware: impl AsRef<[u8]>,
468 checksum: Option<[u8; 32]>,
469 params: FirmwareUpdateParams,
470 progress: Option<&mut FirmwareUpdateProgressCallback>,
471 ) -> Result<(), FirmwareUpdateError> {
472 firmware_update::firmware_update(self, firmware, checksum, params, progress)
473 }
474
475 pub fn os_echo(&self, msg: impl AsRef<str>) -> Result<String, MCUmgrClientError> {
479 self.connection
480 .execute_command(&commands::os::Echo { d: msg.as_ref() })
481 .map(|resp| resp.r)
482 .map_err(Into::into)
483 }
484
485 pub fn os_task_statistics(
496 &self,
497 ) -> Result<HashMap<String, commands::os::TaskStatisticsEntry>, MCUmgrClientError> {
498 self.connection
499 .execute_command(&commands::os::TaskStatistics)
500 .map(|resp| {
501 let mut tasks = resp.tasks;
502 for (_, stats) in tasks.iter_mut() {
503 stats.stkuse = stats.stkuse.map(|val| val * 4);
504 stats.stksiz = stats.stksiz.map(|val| val * 4);
505 }
506 tasks
507 })
508 .map_err(Into::into)
509 }
510
511 pub fn os_memory_pool_statistics(
517 &self,
518 ) -> Result<HashMap<String, commands::os::MemoryPoolStatisticsEntry>, MCUmgrClientError> {
519 self.connection
520 .execute_command(&commands::os::MemoryPoolStatistics)
521 .map(|resp| resp.pools)
522 .map_err(Into::into)
523 }
524
525 pub fn os_set_datetime(
527 &self,
528 datetime: chrono::NaiveDateTime,
529 ) -> Result<(), MCUmgrClientError> {
530 self.connection
531 .execute_command(&commands::os::DateTimeSet { datetime })
532 .map(Into::into)
533 .map_err(Into::into)
534 }
535
536 pub fn os_get_datetime(&self) -> Result<chrono::NaiveDateTime, MCUmgrClientError> {
538 self.connection
539 .execute_command(&commands::os::DateTimeGet)
540 .map(|val| val.datetime)
541 .map_err(Into::into)
542 }
543
544 pub fn os_system_reset(
558 &self,
559 force: bool,
560 boot_mode: Option<u8>,
561 ) -> Result<(), MCUmgrClientError> {
562 self.connection
563 .execute_command(&commands::os::SystemReset { force, boot_mode })
564 .map(Into::into)
565 .map_err(Into::into)
566 }
567
568 pub fn os_mcumgr_parameters(
570 &self,
571 ) -> Result<commands::os::MCUmgrParametersResponse, MCUmgrClientError> {
572 self.connection
573 .execute_command(&commands::os::MCUmgrParameters)
574 .map_err(Into::into)
575 }
576
577 pub fn os_application_info(&self, format: Option<&str>) -> Result<String, MCUmgrClientError> {
589 self.connection
590 .execute_command(&commands::os::ApplicationInfo { format })
591 .map(|resp| resp.output)
592 .map_err(Into::into)
593 }
594
595 pub fn os_bootloader_info(&self) -> Result<BootloaderInfo, MCUmgrClientError> {
597 Ok(
598 match self
599 .connection
600 .execute_command(&commands::os::BootloaderInfo)?
601 .bootloader
602 .as_str()
603 {
604 "MCUboot" => {
605 let mode_data = self
606 .connection
607 .execute_command(&commands::os::BootloaderInfoMcubootMode {})?;
608 BootloaderInfo::MCUboot {
609 mode: mode_data.mode,
610 no_downgrade: mode_data.no_downgrade,
611 }
612 }
613 name => BootloaderInfo::Unknown {
614 name: name.to_string(),
615 },
616 },
617 )
618 }
619
620 pub fn image_get_state(&self) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
622 self.connection
623 .execute_command(&commands::image::GetImageState)
624 .map(|val| val.images)
625 .map_err(Into::into)
626 }
627
628 pub fn image_set_state(
644 &self,
645 hash: Option<&[u8]>,
646 confirm: bool,
647 ) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
648 self.connection
649 .execute_command(&commands::image::SetImageState { hash, confirm })
650 .map(|val| val.images)
651 .map_err(Into::into)
652 }
653
654 pub fn image_upload(
672 &self,
673 data: impl AsRef<[u8]>,
674 image: Option<u32>,
675 checksum: Option<[u8; 32]>,
676 upgrade_only: bool,
677 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
678 ) -> Result<(), MCUmgrClientError> {
679 let chunk_size_max = image_upload_max_data_chunk_size(
680 self.smp_frame_size
681 .load(std::sync::atomic::Ordering::SeqCst),
682 )
683 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
684
685 let data = data.as_ref();
686
687 let actual_checksum: [u8; 32] = Sha256::digest(data).into();
688 if let Some(checksum) = checksum {
689 if actual_checksum != checksum {
690 return Err(MCUmgrClientError::ChecksumMismatch);
691 }
692 }
693
694 let mut offset = 0;
695 let size = data.len();
696
697 let mut checksum_matched = None;
698
699 while offset < size {
700 let current_chunk_size = (size - offset).min(chunk_size_max);
701 let chunk_data = &data[offset..offset + current_chunk_size];
702
703 let upload_response = if offset == 0 {
704 let result = self
705 .connection
706 .execute_command(&commands::image::ImageUpload {
707 image,
708 len: Some(size as u64),
709 off: offset as u64,
710 sha: Some(&actual_checksum),
711 data: chunk_data,
712 upgrade: Some(upgrade_only),
713 });
714
715 if let Err(ExecuteError::ReceiveFailed(ReceiveError::TransportError(e))) = &result {
716 if let io::ErrorKind::TimedOut = e.kind() {
717 log::warn!(
718 "Timed out during transfer of first chunk. Consider enabling CONFIG_IMG_ERASE_PROGRESSIVELY."
719 )
720 }
721 }
722
723 result?
724 } else {
725 self.connection
726 .execute_command(&commands::image::ImageUpload {
727 image: None,
728 len: None,
729 off: offset as u64,
730 sha: None,
731 data: chunk_data,
732 upgrade: None,
733 })?
734 };
735
736 offset = upload_response
737 .off
738 .try_into()
739 .map_err(|_| MCUmgrClientError::UnexpectedOffset)?;
740
741 if offset > size {
742 return Err(MCUmgrClientError::UnexpectedOffset);
743 }
744
745 if let Some(progress) = &mut progress {
746 if !progress(offset as u64, size as u64) {
747 return Err(MCUmgrClientError::ProgressCallbackError);
748 };
749 }
750
751 if let Some(is_match) = upload_response.r#match {
752 checksum_matched = Some(is_match);
753 }
754 }
755
756 if let Some(checksum_matched) = checksum_matched {
757 if !checksum_matched {
758 return Err(MCUmgrClientError::ChecksumMismatchOnDevice);
759 }
760 } else {
761 log::warn!("Device did not perform image checksum verification");
762 }
763
764 Ok(())
765 }
766
767 pub fn image_erase(&self, slot: Option<u32>) -> Result<(), MCUmgrClientError> {
774 self.connection
775 .execute_command(&commands::image::ImageErase { slot })
776 .map(Into::into)
777 .map_err(Into::into)
778 }
779
780 pub fn image_slot_info(
782 &self,
783 ) -> Result<Vec<commands::image::SlotInfoImage>, MCUmgrClientError> {
784 self.connection
785 .execute_command(&commands::image::SlotInfo)
786 .map(|val| val.images)
787 .map_err(Into::into)
788 }
789
790 pub fn stats_get_group_data(
797 &self,
798 name: impl AsRef<str>,
799 ) -> Result<HashMap<String, u64>, MCUmgrClientError> {
800 self.connection
801 .execute_command(&commands::stats::GroupData {
802 name: name.as_ref(),
803 })
804 .map(|val| val.fields)
805 .map_err(Into::into)
806 }
807
808 pub fn stats_list_groups(&self) -> Result<Vec<String>, MCUmgrClientError> {
810 self.connection
811 .execute_command(&commands::stats::ListGroups)
812 .map(|val| val.stat_list)
813 .map_err(Into::into)
814 }
815
816 pub fn settings_read(&self, name: impl AsRef<str>) -> Result<Vec<u8>, MCUmgrClientError> {
829 let name = name.as_ref();
830
831 self.settings_read_ext(name, None).map(|val| val.val)
832 }
833
834 pub fn settings_read_ext(
844 &self,
845 name: impl AsRef<str>,
846 max_size: Option<u32>,
847 ) -> Result<commands::settings::ReadSettingResponse, MCUmgrClientError> {
848 let name = name.as_ref();
849
850 self.connection
851 .execute_command(&commands::settings::ReadSetting { name, max_size })
852 .map_err(Into::into)
853 }
854
855 pub fn settings_write(
863 &self,
864 name: impl AsRef<str>,
865 value: &[u8],
866 ) -> Result<(), MCUmgrClientError> {
867 let name = name.as_ref();
868
869 self.connection
870 .execute_command(&commands::settings::WriteSetting { name, val: value })
871 .map(Into::into)
872 .map_err(Into::into)
873 }
874
875 pub fn settings_delete(&self, name: impl AsRef<str>) -> Result<(), MCUmgrClientError> {
882 let name = name.as_ref();
883
884 self.connection
885 .execute_command(&commands::settings::DeleteSetting { name })
886 .map(Into::into)
887 .map_err(Into::into)
888 }
889
890 pub fn settings_commit(&self) -> Result<(), MCUmgrClientError> {
893 self.connection
894 .execute_command(&commands::settings::CommitSettings)
895 .map(Into::into)
896 .map_err(Into::into)
897 }
898
899 pub fn settings_load(&self) -> Result<(), MCUmgrClientError> {
902 self.connection
903 .execute_command(&commands::settings::LoadSettings)
904 .map(Into::into)
905 .map_err(Into::into)
906 }
907
908 pub fn settings_save(&self, name: Option<impl AsRef<str>>) -> Result<(), MCUmgrClientError> {
915 let name = name.as_ref().map(|val| val.as_ref());
916
917 self.connection
918 .execute_command(&commands::settings::SaveSettings { name })
919 .map(Into::into)
920 .map_err(Into::into)
921 }
922
923 pub fn fs_file_download<T: Write>(
937 &self,
938 name: impl AsRef<str>,
939 mut writer: T,
940 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
941 ) -> Result<(), MCUmgrClientError> {
942 let name = name.as_ref();
943 let response = self
944 .connection
945 .execute_command(&commands::fs::FileDownload { name, off: 0 })?;
946
947 let file_len = response.len.ok_or(MCUmgrClientError::MissingSize)?;
948 if response.off != 0 {
949 return Err(MCUmgrClientError::UnexpectedOffset);
950 }
951
952 let mut offset = 0;
953
954 if let Some(progress) = &mut progress {
955 if !progress(offset, file_len) {
956 return Err(MCUmgrClientError::ProgressCallbackError);
957 };
958 }
959
960 writer
961 .write_all(&response.data)
962 .map_err(MCUmgrClientError::WriterError)?;
963 offset += response.data.len() as u64;
964
965 if let Some(progress) = &mut progress {
966 if !progress(offset, file_len) {
967 return Err(MCUmgrClientError::ProgressCallbackError);
968 };
969 }
970
971 while offset < file_len {
972 let response = self
973 .connection
974 .execute_command(&commands::fs::FileDownload { name, off: offset })?;
975
976 if response.off != offset {
977 return Err(MCUmgrClientError::UnexpectedOffset);
978 }
979
980 writer
981 .write_all(&response.data)
982 .map_err(MCUmgrClientError::WriterError)?;
983 offset += response.data.len() as u64;
984
985 if let Some(progress) = &mut progress {
986 if !progress(offset, file_len) {
987 return Err(MCUmgrClientError::ProgressCallbackError);
988 };
989 }
990 }
991
992 if offset != file_len {
993 return Err(MCUmgrClientError::SizeMismatch);
994 }
995
996 Ok(())
997 }
998
999 pub fn fs_file_upload<T: Read>(
1015 &self,
1016 name: impl AsRef<str>,
1017 mut reader: T,
1018 size: u64,
1019 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1020 ) -> Result<(), MCUmgrClientError> {
1021 let name = name.as_ref();
1022
1023 let chunk_size_max = file_upload_max_data_chunk_size(
1024 self.smp_frame_size
1025 .load(std::sync::atomic::Ordering::SeqCst),
1026 name,
1027 )
1028 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
1029 let mut data_buffer = vec![0u8; chunk_size_max].into_boxed_slice();
1030
1031 let mut offset = 0;
1032
1033 while offset < size {
1034 let current_chunk_size = (size - offset).min(data_buffer.len() as u64) as usize;
1035
1036 let chunk_buffer = &mut data_buffer[..current_chunk_size];
1037 reader
1038 .read_exact(chunk_buffer)
1039 .map_err(MCUmgrClientError::ReaderError)?;
1040
1041 self.connection.execute_command(&commands::fs::FileUpload {
1042 off: offset,
1043 data: chunk_buffer,
1044 name,
1045 len: if offset == 0 { Some(size) } else { None },
1046 })?;
1047
1048 offset += chunk_buffer.len() as u64;
1049
1050 if let Some(progress) = &mut progress {
1051 if !progress(offset, size) {
1052 return Err(MCUmgrClientError::ProgressCallbackError);
1053 };
1054 }
1055 }
1056
1057 Ok(())
1058 }
1059
1060 pub fn fs_file_status(
1062 &self,
1063 name: impl AsRef<str>,
1064 ) -> Result<commands::fs::FileStatusResponse, MCUmgrClientError> {
1065 self.connection
1066 .execute_command(&commands::fs::FileStatus {
1067 name: name.as_ref(),
1068 })
1069 .map_err(Into::into)
1070 }
1071
1072 pub fn fs_file_checksum(
1084 &self,
1085 name: impl AsRef<str>,
1086 algorithm: Option<impl AsRef<str>>,
1087 offset: u64,
1088 length: Option<u64>,
1089 ) -> Result<commands::fs::FileChecksumResponse, MCUmgrClientError> {
1090 self.connection
1091 .execute_command(&commands::fs::FileChecksum {
1092 name: name.as_ref(),
1093 r#type: algorithm.as_ref().map(AsRef::as_ref),
1094 off: offset,
1095 len: length,
1096 })
1097 .map_err(Into::into)
1098 }
1099
1100 pub fn fs_supported_checksum_types(
1102 &self,
1103 ) -> Result<HashMap<String, commands::fs::FileChecksumProperties>, MCUmgrClientError> {
1104 self.connection
1105 .execute_command(&commands::fs::SupportedFileChecksumTypes)
1106 .map(|val| val.types)
1107 .map_err(Into::into)
1108 }
1109
1110 pub fn fs_file_close(&self) -> Result<(), MCUmgrClientError> {
1112 self.connection
1113 .execute_command(&commands::fs::FileClose)
1114 .map(Into::into)
1115 .map_err(Into::into)
1116 }
1117
1118 pub fn shell_execute(
1130 &self,
1131 argv: &[String],
1132 use_retries: bool,
1133 ) -> Result<(i32, String), MCUmgrClientError> {
1134 let command = commands::shell::ShellCommandLineExecute { argv };
1135
1136 if use_retries {
1137 self.connection.execute_command(&command)
1138 } else {
1139 self.connection.execute_command_without_retries(&command)
1140 }
1141 .map(|ret| (ret.ret, ret.o))
1142 .map_err(Into::into)
1143 }
1144
1145 pub fn enum_get_group_count(&self) -> Result<u16, MCUmgrClientError> {
1152 self.connection
1153 .execute_command(&commands::r#enum::GroupCount)
1154 .map(|ret| ret.count)
1155 .map_err(Into::into)
1156 }
1157
1158 pub fn enum_get_group_ids(&self) -> Result<Vec<u16>, MCUmgrClientError> {
1173 self.connection
1174 .execute_command(&commands::r#enum::ListGroups)
1175 .map(|ret| ret.groups)
1176 .map_err(Into::into)
1177 }
1178
1179 pub fn enum_get_group_id(&self, index: u16) -> Result<u16, MCUmgrClientError> {
1191 self.connection
1192 .execute_command(&commands::r#enum::GroupId { index: Some(index) })
1193 .map(|ret| ret.group)
1194 .map_err(Into::into)
1195 }
1196
1197 pub fn enum_iter_group_ids(&self) -> impl Iterator<Item = Result<u16, MCUmgrClientError>> {
1203 let mut i = 0;
1204 let mut num_elements = None;
1205
1206 std::iter::from_fn(move || -> Option<Result<u16, MCUmgrClientError>> {
1207 let mut num_elements_err = None;
1208 let num_elements =
1209 *num_elements.get_or_insert_with(|| match self.enum_get_group_count() {
1210 Ok(n) => n,
1211 Err(e) => {
1212 num_elements_err = Some(e);
1213 0
1214 }
1215 });
1216 if let Some(err) = num_elements_err {
1217 return Some(Err(err));
1218 }
1219
1220 if i >= num_elements {
1221 None
1222 } else {
1223 Some(match self.enum_get_group_id(i) {
1224 Ok(group_id) => {
1225 i += 1;
1226 Ok(group_id)
1227 }
1228 Err(e) => {
1229 i = num_elements;
1230 Err(e)
1231 }
1232 })
1233 }
1234 })
1235 }
1236
1237 pub fn enum_get_group_details(
1248 &self,
1249 groups: Option<&[u16]>,
1250 ) -> Result<Vec<commands::r#enum::GroupDetailsEntry>, MCUmgrClientError> {
1251 self.connection
1252 .execute_command(&commands::r#enum::GroupDetails { groups })
1253 .map(|ret| ret.groups)
1254 .map_err(Into::into)
1255 }
1256
1257 pub fn zephyr_erase_storage(&self) -> Result<(), MCUmgrClientError> {
1259 self.connection
1260 .execute_command(&commands::zephyr::EraseStorage)
1261 .map(Into::into)
1262 .map_err(Into::into)
1263 }
1264
1265 pub fn raw_command<T: commands::McuMgrCommand>(
1271 &self,
1272 command: &T,
1273 ) -> Result<T::Response, MCUmgrClientError> {
1274 self.connection.execute_command(command).map_err(Into::into)
1275 }
1276}