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::Timeout)) = &result {
724 log::warn!(
725 "Timed out during transfer of first chunk. Consider enabling CONFIG_IMG_ERASE_PROGRESSIVELY."
726 )
727 }
728
729 result?
730 } else {
731 let current_chunk_size = (size - offset).min(other_chunk_size_max);
732 let chunk_data = &data[offset..offset + current_chunk_size];
733
734 self.connection
735 .execute_command(&commands::image::ImageUpload {
736 image: None,
737 len: None,
738 off: offset as u64,
739 sha: None,
740 data: chunk_data,
741 upgrade: None,
742 })?
743 };
744
745 offset = upload_response
746 .off
747 .try_into()
748 .map_err(|_| MCUmgrClientError::UnexpectedOffset)?;
749
750 if offset > size {
751 return Err(MCUmgrClientError::UnexpectedOffset);
752 }
753
754 if let Some(progress) = &mut progress {
755 if !progress(offset as u64, size as u64) {
756 return Err(MCUmgrClientError::ProgressCallbackError);
757 };
758 }
759
760 if let Some(is_match) = upload_response.r#match {
761 checksum_matched = Some(is_match);
762 }
763 }
764
765 if let Some(checksum_matched) = checksum_matched {
766 if !checksum_matched {
767 return Err(MCUmgrClientError::ChecksumMismatchOnDevice);
768 }
769 } else {
770 log::warn!("Device did not perform image checksum verification");
771 }
772
773 Ok(())
774 }
775
776 pub fn image_erase(&self, slot: Option<u32>) -> Result<(), MCUmgrClientError> {
783 self.connection
784 .execute_command(&commands::image::ImageErase { slot })
785 .map(Into::into)
786 .map_err(Into::into)
787 }
788
789 pub fn image_slot_info(
791 &self,
792 ) -> Result<Vec<commands::image::SlotInfoImage>, MCUmgrClientError> {
793 self.connection
794 .execute_command(&commands::image::SlotInfo)
795 .map(|val| val.images)
796 .map_err(Into::into)
797 }
798
799 pub fn stats_get_group_data(
806 &self,
807 name: impl AsRef<str>,
808 ) -> Result<HashMap<String, u64>, MCUmgrClientError> {
809 self.connection
810 .execute_command(&commands::stats::GroupData {
811 name: name.as_ref(),
812 })
813 .map(|val| val.fields)
814 .map_err(Into::into)
815 }
816
817 pub fn stats_list_groups(&self) -> Result<Vec<String>, MCUmgrClientError> {
819 self.connection
820 .execute_command(&commands::stats::ListGroups)
821 .map(|val| val.stat_list)
822 .map_err(Into::into)
823 }
824
825 pub fn settings_read(&self, name: impl AsRef<str>) -> Result<Vec<u8>, MCUmgrClientError> {
838 let name = name.as_ref();
839
840 self.settings_read_ext(name, None).map(|val| val.val)
841 }
842
843 pub fn settings_read_ext(
853 &self,
854 name: impl AsRef<str>,
855 max_size: Option<u32>,
856 ) -> Result<commands::settings::ReadSettingResponse, MCUmgrClientError> {
857 let name = name.as_ref();
858
859 self.connection
860 .execute_command(&commands::settings::ReadSetting { name, max_size })
861 .map_err(Into::into)
862 }
863
864 pub fn settings_write(
872 &self,
873 name: impl AsRef<str>,
874 value: &[u8],
875 ) -> Result<(), MCUmgrClientError> {
876 let name = name.as_ref();
877
878 self.connection
879 .execute_command(&commands::settings::WriteSetting { name, val: value })
880 .map(Into::into)
881 .map_err(Into::into)
882 }
883
884 pub fn settings_delete(&self, name: impl AsRef<str>) -> Result<(), MCUmgrClientError> {
891 let name = name.as_ref();
892
893 self.connection
894 .execute_command(&commands::settings::DeleteSetting { name })
895 .map(Into::into)
896 .map_err(Into::into)
897 }
898
899 pub fn settings_commit(&self) -> Result<(), MCUmgrClientError> {
902 self.connection
903 .execute_command(&commands::settings::CommitSettings)
904 .map(Into::into)
905 .map_err(Into::into)
906 }
907
908 pub fn settings_load(&self) -> Result<(), MCUmgrClientError> {
911 self.connection
912 .execute_command(&commands::settings::LoadSettings)
913 .map(Into::into)
914 .map_err(Into::into)
915 }
916
917 pub fn settings_save(&self, name: Option<impl AsRef<str>>) -> Result<(), MCUmgrClientError> {
924 let name = name.as_ref().map(|val| val.as_ref());
925
926 self.connection
927 .execute_command(&commands::settings::SaveSettings { name })
928 .map(Into::into)
929 .map_err(Into::into)
930 }
931
932 pub fn fs_file_download<T: Write>(
946 &self,
947 name: impl AsRef<str>,
948 mut writer: T,
949 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
950 ) -> Result<(), MCUmgrClientError> {
951 let name = name.as_ref();
952 let response = self
953 .connection
954 .execute_command(&commands::fs::FileDownload { name, off: 0 })?;
955
956 let file_len = response.len.ok_or(MCUmgrClientError::MissingSize)?;
957 if response.off != 0 {
958 return Err(MCUmgrClientError::UnexpectedOffset);
959 }
960
961 let mut offset = 0;
962
963 if let Some(progress) = &mut progress {
964 if !progress(offset, file_len) {
965 return Err(MCUmgrClientError::ProgressCallbackError);
966 };
967 }
968
969 writer
970 .write_all(&response.data)
971 .map_err(MCUmgrClientError::WriterError)?;
972 offset += response.data.len() as u64;
973
974 if let Some(progress) = &mut progress {
975 if !progress(offset, file_len) {
976 return Err(MCUmgrClientError::ProgressCallbackError);
977 };
978 }
979
980 while offset < file_len {
981 let response = self
982 .connection
983 .execute_command(&commands::fs::FileDownload { name, off: offset })?;
984
985 if response.off != offset {
986 return Err(MCUmgrClientError::UnexpectedOffset);
987 }
988
989 writer
990 .write_all(&response.data)
991 .map_err(MCUmgrClientError::WriterError)?;
992 offset += response.data.len() as u64;
993
994 if let Some(progress) = &mut progress {
995 if !progress(offset, file_len) {
996 return Err(MCUmgrClientError::ProgressCallbackError);
997 };
998 }
999 }
1000
1001 if offset != file_len {
1002 return Err(MCUmgrClientError::SizeMismatch);
1003 }
1004
1005 Ok(())
1006 }
1007
1008 pub fn fs_file_upload<T: Read>(
1024 &self,
1025 name: impl AsRef<str>,
1026 mut reader: T,
1027 size: u64,
1028 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1029 ) -> Result<(), MCUmgrClientError> {
1030 let name = name.as_ref();
1031
1032 let chunk_size_max = file_upload_max_data_chunk_size(
1033 self.smp_frame_size
1034 .load(std::sync::atomic::Ordering::SeqCst),
1035 name,
1036 )
1037 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
1038 let mut data_buffer = vec![0u8; chunk_size_max].into_boxed_slice();
1039
1040 let mut offset = 0;
1041
1042 while offset < size {
1043 let current_chunk_size = (size - offset).min(data_buffer.len() as u64) as usize;
1044
1045 let chunk_buffer = &mut data_buffer[..current_chunk_size];
1046 reader
1047 .read_exact(chunk_buffer)
1048 .map_err(MCUmgrClientError::ReaderError)?;
1049
1050 self.connection.execute_command(&commands::fs::FileUpload {
1051 off: offset,
1052 data: chunk_buffer,
1053 name,
1054 len: if offset == 0 { Some(size) } else { None },
1055 })?;
1056
1057 offset += chunk_buffer.len() as u64;
1058
1059 if let Some(progress) = &mut progress {
1060 if !progress(offset, size) {
1061 return Err(MCUmgrClientError::ProgressCallbackError);
1062 };
1063 }
1064 }
1065
1066 Ok(())
1067 }
1068
1069 pub fn fs_file_status(
1071 &self,
1072 name: impl AsRef<str>,
1073 ) -> Result<commands::fs::FileStatusResponse, MCUmgrClientError> {
1074 self.connection
1075 .execute_command(&commands::fs::FileStatus {
1076 name: name.as_ref(),
1077 })
1078 .map_err(Into::into)
1079 }
1080
1081 pub fn fs_file_checksum(
1093 &self,
1094 name: impl AsRef<str>,
1095 algorithm: Option<impl AsRef<str>>,
1096 offset: u64,
1097 length: Option<u64>,
1098 ) -> Result<commands::fs::FileChecksumResponse, MCUmgrClientError> {
1099 self.connection
1100 .execute_command(&commands::fs::FileChecksum {
1101 name: name.as_ref(),
1102 r#type: algorithm.as_ref().map(AsRef::as_ref),
1103 off: offset,
1104 len: length,
1105 })
1106 .map_err(Into::into)
1107 }
1108
1109 pub fn fs_supported_checksum_types(
1111 &self,
1112 ) -> Result<HashMap<String, commands::fs::FileChecksumProperties>, MCUmgrClientError> {
1113 self.connection
1114 .execute_command(&commands::fs::SupportedFileChecksumTypes)
1115 .map(|val| val.types)
1116 .map_err(Into::into)
1117 }
1118
1119 pub fn fs_file_close(&self) -> Result<(), MCUmgrClientError> {
1121 self.connection
1122 .execute_command(&commands::fs::FileClose)
1123 .map(Into::into)
1124 .map_err(Into::into)
1125 }
1126
1127 pub fn shell_execute(
1139 &self,
1140 argv: &[String],
1141 use_retries: bool,
1142 ) -> Result<(i32, String), MCUmgrClientError> {
1143 let command = commands::shell::ShellCommandLineExecute { argv };
1144
1145 if use_retries {
1146 self.connection.execute_command(&command)
1147 } else {
1148 self.connection.execute_command_without_retries(&command)
1149 }
1150 .map(|ret| (ret.ret, ret.o))
1151 .map_err(Into::into)
1152 }
1153
1154 pub fn enum_get_group_count(&self) -> Result<u16, MCUmgrClientError> {
1161 self.connection
1162 .execute_command(&commands::r#enum::GroupCount)
1163 .map(|ret| ret.count)
1164 .map_err(Into::into)
1165 }
1166
1167 pub fn enum_get_group_ids(&self) -> Result<Vec<u16>, MCUmgrClientError> {
1182 self.connection
1183 .execute_command(&commands::r#enum::ListGroups)
1184 .map(|ret| ret.groups)
1185 .map_err(Into::into)
1186 }
1187
1188 pub fn enum_get_group_id(&self, index: u16) -> Result<u16, MCUmgrClientError> {
1200 self.connection
1201 .execute_command(&commands::r#enum::GroupId { index: Some(index) })
1202 .map(|ret| ret.group)
1203 .map_err(Into::into)
1204 }
1205
1206 pub fn enum_iter_group_ids(&self) -> impl Iterator<Item = Result<u16, MCUmgrClientError>> {
1212 let mut i = 0;
1213 let mut num_elements = None;
1214
1215 std::iter::from_fn(move || -> Option<Result<u16, MCUmgrClientError>> {
1216 let mut num_elements_err = None;
1217 let num_elements =
1218 *num_elements.get_or_insert_with(|| match self.enum_get_group_count() {
1219 Ok(n) => n,
1220 Err(e) => {
1221 num_elements_err = Some(e);
1222 0
1223 }
1224 });
1225 if let Some(err) = num_elements_err {
1226 return Some(Err(err));
1227 }
1228
1229 if i >= num_elements {
1230 None
1231 } else {
1232 Some(match self.enum_get_group_id(i) {
1233 Ok(group_id) => {
1234 i += 1;
1235 Ok(group_id)
1236 }
1237 Err(e) => {
1238 i = num_elements;
1239 Err(e)
1240 }
1241 })
1242 }
1243 })
1244 }
1245
1246 pub fn enum_get_group_details(
1257 &self,
1258 groups: Option<&[u16]>,
1259 ) -> Result<Vec<commands::r#enum::GroupDetailsEntry>, MCUmgrClientError> {
1260 self.connection
1261 .execute_command(&commands::r#enum::GroupDetails { groups })
1262 .map(|ret| ret.groups)
1263 .map_err(Into::into)
1264 }
1265
1266 pub fn zephyr_erase_storage(&self) -> Result<(), MCUmgrClientError> {
1268 self.connection
1269 .execute_command(&commands::zephyr::EraseStorage)
1270 .map(Into::into)
1271 .map_err(Into::into)
1272 }
1273
1274 pub fn raw_command<T: commands::McuMgrCommand>(
1280 &self,
1281 command: &T,
1282 ) -> Result<T::Response, MCUmgrClientError> {
1283 self.connection.execute_command(command).map_err(Into::into)
1284 }
1285}