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 first_chunk_size_max = image_upload_max_data_chunk_size(
680 self.smp_frame_size
681 .load(std::sync::atomic::Ordering::SeqCst),
682 true,
683 )
684 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
685 let other_chunk_size_max = image_upload_max_data_chunk_size(
686 self.smp_frame_size
687 .load(std::sync::atomic::Ordering::SeqCst),
688 false,
689 )
690 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
691 log::debug!("Max chunk size: {first_chunk_size_max}, {other_chunk_size_max}");
692
693 let data = data.as_ref();
694
695 let actual_checksum: [u8; 32] = Sha256::digest(data).into();
696 if let Some(checksum) = checksum {
697 if actual_checksum != checksum {
698 return Err(MCUmgrClientError::ChecksumMismatch);
699 }
700 }
701
702 let mut offset = 0;
703 let size = data.len();
704
705 let mut checksum_matched = None;
706
707 while offset < size {
708 let upload_response = if offset == 0 {
709 let current_chunk_size = (size - offset).min(first_chunk_size_max);
710 let chunk_data = &data[offset..offset + current_chunk_size];
711
712 let result = self
713 .connection
714 .execute_command(&commands::image::ImageUpload {
715 image,
716 len: Some(size as u64),
717 off: offset as u64,
718 sha: Some(&actual_checksum),
719 data: chunk_data,
720 upgrade: Some(upgrade_only),
721 });
722
723 if let Err(ExecuteError::ReceiveFailed(ReceiveError::TransportError(e))) = &result {
724 if let io::ErrorKind::TimedOut = e.kind() {
725 log::warn!(
726 "Timed out during transfer of first chunk. Consider enabling CONFIG_IMG_ERASE_PROGRESSIVELY."
727 )
728 }
729 }
730
731 result?
732 } else {
733 let current_chunk_size = (size - offset).min(other_chunk_size_max);
734 let chunk_data = &data[offset..offset + current_chunk_size];
735
736 self.connection
737 .execute_command(&commands::image::ImageUpload {
738 image: None,
739 len: None,
740 off: offset as u64,
741 sha: None,
742 data: chunk_data,
743 upgrade: None,
744 })?
745 };
746
747 offset = upload_response
748 .off
749 .try_into()
750 .map_err(|_| MCUmgrClientError::UnexpectedOffset)?;
751
752 if offset > size {
753 return Err(MCUmgrClientError::UnexpectedOffset);
754 }
755
756 if let Some(progress) = &mut progress {
757 if !progress(offset as u64, size as u64) {
758 return Err(MCUmgrClientError::ProgressCallbackError);
759 };
760 }
761
762 if let Some(is_match) = upload_response.r#match {
763 checksum_matched = Some(is_match);
764 }
765 }
766
767 if let Some(checksum_matched) = checksum_matched {
768 if !checksum_matched {
769 return Err(MCUmgrClientError::ChecksumMismatchOnDevice);
770 }
771 } else {
772 log::warn!("Device did not perform image checksum verification");
773 }
774
775 Ok(())
776 }
777
778 pub fn image_erase(&self, slot: Option<u32>) -> Result<(), MCUmgrClientError> {
785 self.connection
786 .execute_command(&commands::image::ImageErase { slot })
787 .map(Into::into)
788 .map_err(Into::into)
789 }
790
791 pub fn image_slot_info(
793 &self,
794 ) -> Result<Vec<commands::image::SlotInfoImage>, MCUmgrClientError> {
795 self.connection
796 .execute_command(&commands::image::SlotInfo)
797 .map(|val| val.images)
798 .map_err(Into::into)
799 }
800
801 pub fn stats_get_group_data(
808 &self,
809 name: impl AsRef<str>,
810 ) -> Result<HashMap<String, u64>, MCUmgrClientError> {
811 self.connection
812 .execute_command(&commands::stats::GroupData {
813 name: name.as_ref(),
814 })
815 .map(|val| val.fields)
816 .map_err(Into::into)
817 }
818
819 pub fn stats_list_groups(&self) -> Result<Vec<String>, MCUmgrClientError> {
821 self.connection
822 .execute_command(&commands::stats::ListGroups)
823 .map(|val| val.stat_list)
824 .map_err(Into::into)
825 }
826
827 pub fn settings_read(&self, name: impl AsRef<str>) -> Result<Vec<u8>, MCUmgrClientError> {
840 let name = name.as_ref();
841
842 self.settings_read_ext(name, None).map(|val| val.val)
843 }
844
845 pub fn settings_read_ext(
855 &self,
856 name: impl AsRef<str>,
857 max_size: Option<u32>,
858 ) -> Result<commands::settings::ReadSettingResponse, MCUmgrClientError> {
859 let name = name.as_ref();
860
861 self.connection
862 .execute_command(&commands::settings::ReadSetting { name, max_size })
863 .map_err(Into::into)
864 }
865
866 pub fn settings_write(
874 &self,
875 name: impl AsRef<str>,
876 value: &[u8],
877 ) -> Result<(), MCUmgrClientError> {
878 let name = name.as_ref();
879
880 self.connection
881 .execute_command(&commands::settings::WriteSetting { name, val: value })
882 .map(Into::into)
883 .map_err(Into::into)
884 }
885
886 pub fn settings_delete(&self, name: impl AsRef<str>) -> Result<(), MCUmgrClientError> {
893 let name = name.as_ref();
894
895 self.connection
896 .execute_command(&commands::settings::DeleteSetting { name })
897 .map(Into::into)
898 .map_err(Into::into)
899 }
900
901 pub fn settings_commit(&self) -> Result<(), MCUmgrClientError> {
904 self.connection
905 .execute_command(&commands::settings::CommitSettings)
906 .map(Into::into)
907 .map_err(Into::into)
908 }
909
910 pub fn settings_load(&self) -> Result<(), MCUmgrClientError> {
913 self.connection
914 .execute_command(&commands::settings::LoadSettings)
915 .map(Into::into)
916 .map_err(Into::into)
917 }
918
919 pub fn settings_save(&self, name: Option<impl AsRef<str>>) -> Result<(), MCUmgrClientError> {
926 let name = name.as_ref().map(|val| val.as_ref());
927
928 self.connection
929 .execute_command(&commands::settings::SaveSettings { name })
930 .map(Into::into)
931 .map_err(Into::into)
932 }
933
934 pub fn fs_file_download<T: Write>(
948 &self,
949 name: impl AsRef<str>,
950 mut writer: T,
951 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
952 ) -> Result<(), MCUmgrClientError> {
953 let name = name.as_ref();
954 let response = self
955 .connection
956 .execute_command(&commands::fs::FileDownload { name, off: 0 })?;
957
958 let file_len = response.len.ok_or(MCUmgrClientError::MissingSize)?;
959 if response.off != 0 {
960 return Err(MCUmgrClientError::UnexpectedOffset);
961 }
962
963 let mut offset = 0;
964
965 if let Some(progress) = &mut progress {
966 if !progress(offset, file_len) {
967 return Err(MCUmgrClientError::ProgressCallbackError);
968 };
969 }
970
971 writer
972 .write_all(&response.data)
973 .map_err(MCUmgrClientError::WriterError)?;
974 offset += response.data.len() as u64;
975
976 if let Some(progress) = &mut progress {
977 if !progress(offset, file_len) {
978 return Err(MCUmgrClientError::ProgressCallbackError);
979 };
980 }
981
982 while offset < file_len {
983 let response = self
984 .connection
985 .execute_command(&commands::fs::FileDownload { name, off: offset })?;
986
987 if response.off != offset {
988 return Err(MCUmgrClientError::UnexpectedOffset);
989 }
990
991 writer
992 .write_all(&response.data)
993 .map_err(MCUmgrClientError::WriterError)?;
994 offset += response.data.len() as u64;
995
996 if let Some(progress) = &mut progress {
997 if !progress(offset, file_len) {
998 return Err(MCUmgrClientError::ProgressCallbackError);
999 };
1000 }
1001 }
1002
1003 if offset != file_len {
1004 return Err(MCUmgrClientError::SizeMismatch);
1005 }
1006
1007 Ok(())
1008 }
1009
1010 pub fn fs_file_upload<T: Read>(
1026 &self,
1027 name: impl AsRef<str>,
1028 mut reader: T,
1029 size: u64,
1030 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1031 ) -> Result<(), MCUmgrClientError> {
1032 let name = name.as_ref();
1033
1034 let chunk_size_max = file_upload_max_data_chunk_size(
1035 self.smp_frame_size
1036 .load(std::sync::atomic::Ordering::SeqCst),
1037 name,
1038 )
1039 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
1040 let mut data_buffer = vec![0u8; chunk_size_max].into_boxed_slice();
1041
1042 let mut offset = 0;
1043
1044 while offset < size {
1045 let current_chunk_size = (size - offset).min(data_buffer.len() as u64) as usize;
1046
1047 let chunk_buffer = &mut data_buffer[..current_chunk_size];
1048 reader
1049 .read_exact(chunk_buffer)
1050 .map_err(MCUmgrClientError::ReaderError)?;
1051
1052 self.connection.execute_command(&commands::fs::FileUpload {
1053 off: offset,
1054 data: chunk_buffer,
1055 name,
1056 len: if offset == 0 { Some(size) } else { None },
1057 })?;
1058
1059 offset += chunk_buffer.len() as u64;
1060
1061 if let Some(progress) = &mut progress {
1062 if !progress(offset, size) {
1063 return Err(MCUmgrClientError::ProgressCallbackError);
1064 };
1065 }
1066 }
1067
1068 Ok(())
1069 }
1070
1071 pub fn fs_file_status(
1073 &self,
1074 name: impl AsRef<str>,
1075 ) -> Result<commands::fs::FileStatusResponse, MCUmgrClientError> {
1076 self.connection
1077 .execute_command(&commands::fs::FileStatus {
1078 name: name.as_ref(),
1079 })
1080 .map_err(Into::into)
1081 }
1082
1083 pub fn fs_file_checksum(
1095 &self,
1096 name: impl AsRef<str>,
1097 algorithm: Option<impl AsRef<str>>,
1098 offset: u64,
1099 length: Option<u64>,
1100 ) -> Result<commands::fs::FileChecksumResponse, MCUmgrClientError> {
1101 self.connection
1102 .execute_command(&commands::fs::FileChecksum {
1103 name: name.as_ref(),
1104 r#type: algorithm.as_ref().map(AsRef::as_ref),
1105 off: offset,
1106 len: length,
1107 })
1108 .map_err(Into::into)
1109 }
1110
1111 pub fn fs_supported_checksum_types(
1113 &self,
1114 ) -> Result<HashMap<String, commands::fs::FileChecksumProperties>, MCUmgrClientError> {
1115 self.connection
1116 .execute_command(&commands::fs::SupportedFileChecksumTypes)
1117 .map(|val| val.types)
1118 .map_err(Into::into)
1119 }
1120
1121 pub fn fs_file_close(&self) -> Result<(), MCUmgrClientError> {
1123 self.connection
1124 .execute_command(&commands::fs::FileClose)
1125 .map(Into::into)
1126 .map_err(Into::into)
1127 }
1128
1129 pub fn shell_execute(
1141 &self,
1142 argv: &[String],
1143 use_retries: bool,
1144 ) -> Result<(i32, String), MCUmgrClientError> {
1145 let command = commands::shell::ShellCommandLineExecute { argv };
1146
1147 if use_retries {
1148 self.connection.execute_command(&command)
1149 } else {
1150 self.connection.execute_command_without_retries(&command)
1151 }
1152 .map(|ret| (ret.ret, ret.o))
1153 .map_err(Into::into)
1154 }
1155
1156 pub fn enum_get_group_count(&self) -> Result<u16, MCUmgrClientError> {
1163 self.connection
1164 .execute_command(&commands::r#enum::GroupCount)
1165 .map(|ret| ret.count)
1166 .map_err(Into::into)
1167 }
1168
1169 pub fn enum_get_group_ids(&self) -> Result<Vec<u16>, MCUmgrClientError> {
1184 self.connection
1185 .execute_command(&commands::r#enum::ListGroups)
1186 .map(|ret| ret.groups)
1187 .map_err(Into::into)
1188 }
1189
1190 pub fn enum_get_group_id(&self, index: u16) -> Result<u16, MCUmgrClientError> {
1202 self.connection
1203 .execute_command(&commands::r#enum::GroupId { index: Some(index) })
1204 .map(|ret| ret.group)
1205 .map_err(Into::into)
1206 }
1207
1208 pub fn enum_iter_group_ids(&self) -> impl Iterator<Item = Result<u16, MCUmgrClientError>> {
1214 let mut i = 0;
1215 let mut num_elements = None;
1216
1217 std::iter::from_fn(move || -> Option<Result<u16, MCUmgrClientError>> {
1218 let mut num_elements_err = None;
1219 let num_elements =
1220 *num_elements.get_or_insert_with(|| match self.enum_get_group_count() {
1221 Ok(n) => n,
1222 Err(e) => {
1223 num_elements_err = Some(e);
1224 0
1225 }
1226 });
1227 if let Some(err) = num_elements_err {
1228 return Some(Err(err));
1229 }
1230
1231 if i >= num_elements {
1232 None
1233 } else {
1234 Some(match self.enum_get_group_id(i) {
1235 Ok(group_id) => {
1236 i += 1;
1237 Ok(group_id)
1238 }
1239 Err(e) => {
1240 i = num_elements;
1241 Err(e)
1242 }
1243 })
1244 }
1245 })
1246 }
1247
1248 pub fn enum_get_group_details(
1259 &self,
1260 groups: Option<&[u16]>,
1261 ) -> Result<Vec<commands::r#enum::GroupDetailsEntry>, MCUmgrClientError> {
1262 self.connection
1263 .execute_command(&commands::r#enum::GroupDetails { groups })
1264 .map(|ret| ret.groups)
1265 .map_err(Into::into)
1266 }
1267
1268 pub fn zephyr_erase_storage(&self) -> Result<(), MCUmgrClientError> {
1270 self.connection
1271 .execute_command(&commands::zephyr::EraseStorage)
1272 .map(Into::into)
1273 .map_err(Into::into)
1274 }
1275
1276 pub fn raw_command<T: commands::McuMgrCommand>(
1282 &self,
1283 command: &T,
1284 ) -> Result<T::Response, MCUmgrClientError> {
1285 self.connection.execute_command(command).map_err(Into::into)
1286 }
1287}