1mod firmware_update;
3pub use firmware_update::{
4 FirmwareUpdateError, FirmwareUpdateParams, FirmwareUpdateProgressCallback, FirmwareUpdateStep,
5};
6
7pub mod image_run_state;
9
10use std::{
11 collections::HashMap,
12 io::{self, Read, Write},
13 net::SocketAddr,
14 sync::atomic::AtomicUsize,
15 time::Duration,
16};
17
18use miette::Diagnostic;
19use rand::distr::SampleString;
20use serde::Serialize;
21use sha2::{Digest, Sha256};
22use thiserror::Error;
23
24use crate::{
25 bootloader::BootloaderInfo,
26 commands::{
27 self, fs::file_upload_max_data_chunk_size, image::image_upload_max_data_chunk_size,
28 },
29 connection::{Connection, ExecuteError},
30 transport::{
31 IntoTransport, ReceiveError, SMP_TRANSFER_BUFFER_SIZE,
32 serial::{ConfigurableTimeout, SerialTransport},
33 udp::UdpTransport,
34 },
35};
36
37#[cfg(feature = "ble")]
38use crate::transport::ble::{BleIdentifier, BleRuntimeError};
39
40const ZEPHYR_DEFAULT_SMP_FRAME_SIZE: usize = 384;
44
45pub struct MCUmgrClient {
49 connection: Connection,
50 smp_frame_size: AtomicUsize,
51}
52
53#[derive(Error, Debug, Diagnostic)]
55pub enum MCUmgrClientError {
56 #[error("Command execution failed")]
58 #[diagnostic(code(mcumgr_toolkit::client::execute))]
59 ExecuteError(#[from] ExecuteError),
60 #[error("Received an unexpected offset value")]
62 #[diagnostic(code(mcumgr_toolkit::client::unexpected_offset))]
63 UnexpectedOffset,
64 #[error("Writer returned an error")]
66 #[diagnostic(code(mcumgr_toolkit::client::writer))]
67 WriterError(#[source] io::Error),
68 #[error("Reader returned an error")]
70 #[diagnostic(code(mcumgr_toolkit::client::reader))]
71 ReaderError(#[source] io::Error),
72 #[error("Received data does not match reported size")]
74 #[diagnostic(code(mcumgr_toolkit::client::size_mismatch))]
75 SizeMismatch,
76 #[error("Received data is missing file size information")]
78 #[diagnostic(code(mcumgr_toolkit::client::missing_size))]
79 MissingSize,
80 #[error("Progress callback returned an error")]
82 #[diagnostic(code(mcumgr_toolkit::client::progress_cb_error))]
83 ProgressCallbackError,
84 #[error("SMP frame size too small for this command")]
86 #[diagnostic(code(mcumgr_toolkit::client::framesize_too_small))]
87 FrameSizeTooSmall(#[source] io::Error),
88 #[error("Device reported checksum mismatch")]
90 #[diagnostic(code(mcumgr_toolkit::client::checksum_mismatch_on_device))]
91 ChecksumMismatchOnDevice,
92 #[error("Firmware image does not match given checksum")]
94 #[diagnostic(code(mcumgr_toolkit::client::checksum_mismatch))]
95 ChecksumMismatch,
96 #[error("Failed to set the device timeout")]
98 #[diagnostic(code(mcumgr_toolkit::client::set_timeout))]
99 SetTimeoutFailed(#[source] Box<dyn std::error::Error + Send + Sync>),
100}
101
102impl MCUmgrClientError {
103 pub fn command_not_supported(&self) -> bool {
105 if let Self::ExecuteError(err) = self {
106 err.command_not_supported()
107 } else {
108 false
109 }
110 }
111}
112
113#[derive(Debug, Serialize, Clone, Eq, PartialEq)]
115pub struct UsbSerialPortInfo {
116 pub identifier: String,
118 pub port_name: String,
120 pub port_info: serialport::UsbPortInfo,
122}
123
124#[derive(Serialize, Clone, Eq, PartialEq)]
128#[serde(transparent)]
129pub struct UsbSerialPorts(pub Vec<UsbSerialPortInfo>);
130impl std::fmt::Display for UsbSerialPorts {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 if self.0.is_empty() {
133 writeln!(f)?;
134 write!(f, " - None -")?;
135 return Ok(());
136 }
137
138 for UsbSerialPortInfo {
139 identifier,
140 port_name,
141 port_info,
142 } in &self.0
143 {
144 writeln!(f)?;
145 write!(f, " - {identifier}")?;
146
147 let mut print_port_string = true;
148 let port_string = format!("({port_name})");
149
150 if port_info.manufacturer.is_some() || port_info.product.is_some() {
151 write!(f, " -")?;
152 if let Some(manufacturer) = &port_info.manufacturer {
153 let mut print_manufacturer = true;
154
155 if let Some(product) = &port_info.product {
156 if product.starts_with(manufacturer) {
157 print_manufacturer = false;
158 }
159 }
160
161 if print_manufacturer {
162 write!(f, " {manufacturer}")?;
163 }
164 }
165 if let Some(product) = &port_info.product {
166 write!(f, " {product}")?;
167
168 if product.ends_with(&port_string) {
169 print_port_string = false;
170 }
171 }
172 }
173
174 if print_port_string {
175 write!(f, " {port_string}")?;
176 }
177 }
178 Ok(())
179 }
180}
181impl std::fmt::Debug for UsbSerialPorts {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 std::fmt::Debug::fmt(&self.0, f)
184 }
185}
186
187#[cfg(feature = "ble")]
188fn ble_identifier_to_str<S>(id: &BleIdentifier, ser: S) -> Result<S::Ok, S::Error>
189where
190 S: serde::Serializer,
191{
192 ser.collect_str(id)
193}
194
195#[cfg(feature = "ble")]
197#[derive(Debug, Serialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
198pub struct BleDeviceInfo {
199 #[serde(serialize_with = "ble_identifier_to_str")]
201 pub id: BleIdentifier,
202 pub name: Option<String>,
204 pub rssi: Option<i16>,
206}
207
208#[cfg(feature = "ble")]
212#[derive(Serialize, Clone, Eq, PartialEq)]
213#[serde(transparent)]
214pub struct BleDevices(pub Vec<BleDeviceInfo>);
215
216#[cfg(feature = "ble")]
217impl std::fmt::Display for BleDevices {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 if self.0.is_empty() {
220 writeln!(f)?;
221 write!(f, " - None -")?;
222 return Ok(());
223 }
224
225 for BleDeviceInfo { id, name, rssi } in &self.0 {
226 writeln!(f)?;
227
228 if let Some(name) = name {
229 write!(f, " - {id} - {name:?}")?;
230 } else {
231 write!(f, " - {id} - <unknown>")?;
232 }
233
234 if let Some(rssi) = rssi {
235 write!(f, " ({rssi} dBm)")?;
236 }
237 }
238 Ok(())
239 }
240}
241
242#[cfg(feature = "ble")]
243impl std::fmt::Debug for BleDevices {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 std::fmt::Debug::fmt(&self.0, f)
246 }
247}
248
249#[derive(Error, Debug, Diagnostic)]
251pub enum UdpError {
252 #[error("Failed to open UDP socket")]
254 #[diagnostic(code(mcumgr_toolkit::udp::io_error))]
255 Io(#[from] io::Error),
256}
257
258#[derive(Error, Debug, Diagnostic)]
260pub enum UsbSerialError {
261 #[error("Serialport returned an error")]
263 #[diagnostic(code(mcumgr_toolkit::usb_serial::serialport_error))]
264 SerialPortError(#[from] serialport::Error),
265 #[error("No serial port matched the identifier '{identifier}'\nAvailable ports:\n{available}")]
267 #[diagnostic(code(mcumgr_toolkit::usb_serial::no_matches))]
268 NoMatchingPort {
269 identifier: String,
271 available: UsbSerialPorts,
273 },
274 #[error("Multiple serial ports matched the identifier '{identifier}'\n{ports}")]
276 #[diagnostic(code(mcumgr_toolkit::usb_serial::multiple_matches))]
277 MultipleMatchingPorts {
278 identifier: String,
280 ports: UsbSerialPorts,
282 },
283 #[error("An empty identifier was provided")]
286 #[diagnostic(code(mcumgr_toolkit::usb_serial::empty_identifier))]
287 IdentifierEmpty {
288 ports: UsbSerialPorts,
290 },
291 #[error("The given identifier was not a valid RegEx")]
293 #[diagnostic(code(mcumgr_toolkit::usb_serial::regex_error))]
294 RegexError(#[from] regex::Error),
295}
296
297#[cfg(feature = "ble")]
299#[derive(Error, Debug, Diagnostic)]
300pub enum BleError {
301 #[error("BLE runtime layer returned an error")]
303 #[diagnostic(code(mcumgr_toolkit::ble::runtime))]
304 BleRuntime(#[from] BleRuntimeError),
305 #[error("BLE scanning unexpectedly stopped")]
307 #[diagnostic(code(mcumgr_toolkit::ble::scan_stopped))]
308 ScanStopped,
309 #[error("Device not found\nAvailable devices:\n{available}")]
311 #[diagnostic(code(mcumgr_toolkit::ble::device_not_found))]
312 DeviceNotFound {
313 available: BleDevices,
315 },
316 #[error("An empty identifier was provided")]
319 #[diagnostic(code(mcumgr_toolkit::ble::empty_identifier))]
320 IdentifierEmpty {
321 devices: BleDevices,
323 },
324}
325
326impl MCUmgrClient {
327 pub fn new_from_serial<T: Send + Read + Write + ConfigurableTimeout + 'static>(
340 serial: T,
341 ) -> Self {
342 Self::new_from_transport(SerialTransport::new(serial))
343 }
344
345 pub fn new_from_usb_serial(
363 identifier: impl AsRef<str>,
364 baud_rate: u32,
365 timeout: Duration,
366 ) -> Result<Self, UsbSerialError> {
367 let identifier = identifier.as_ref();
368
369 let ports = serialport::available_ports()?
370 .into_iter()
371 .filter_map(|port| {
372 if let serialport::SerialPortType::UsbPort(port_info) = port.port_type {
373 if let Some(interface) = port_info.interface {
374 Some(UsbSerialPortInfo {
375 identifier: format!(
376 "{:04x}:{:04x}:{}",
377 port_info.vid, port_info.pid, interface
378 ),
379 port_name: port.port_name,
380 port_info,
381 })
382 } else {
383 Some(UsbSerialPortInfo {
384 identifier: format!("{:04x}:{:04x}", port_info.vid, port_info.pid),
385 port_name: port.port_name,
386 port_info,
387 })
388 }
389 } else {
390 None
391 }
392 })
393 .collect::<Vec<_>>();
394
395 if identifier.is_empty() {
396 return Err(UsbSerialError::IdentifierEmpty {
397 ports: UsbSerialPorts(ports),
398 });
399 }
400
401 let port_regex = regex::RegexBuilder::new(identifier)
402 .case_insensitive(true)
403 .unicode(true)
404 .build()?;
405
406 let matches = ports
407 .iter()
408 .filter(|port| {
409 if let Some(m) = port_regex.find(&port.identifier) {
410 m.start() == 0
412 } else {
413 false
414 }
415 })
416 .cloned()
417 .collect::<Vec<_>>();
418
419 if matches.len() > 1 {
420 return Err(UsbSerialError::MultipleMatchingPorts {
421 identifier: identifier.to_string(),
422 ports: UsbSerialPorts(matches),
423 });
424 }
425
426 let port_name = match matches.into_iter().next() {
427 Some(port) => port.port_name,
428 None => {
429 return Err(UsbSerialError::NoMatchingPort {
430 identifier: identifier.to_string(),
431 available: UsbSerialPorts(ports),
432 });
433 }
434 };
435
436 let serial = serialport::new(port_name, baud_rate)
437 .timeout(timeout)
438 .open()?;
439
440 Ok(Self::new_from_serial(serial))
441 }
442
443 #[cfg(feature = "ble")]
451 pub fn new_from_ble(
452 identifier: Option<BleIdentifier>,
453 timeout: Duration,
454 ) -> Result<Self, BleError> {
455 Self::new_from_ble_with_scan_callback(identifier, timeout, || {})
456 }
457
458 #[cfg(feature = "ble")]
469 pub fn new_from_ble_with_scan_callback(
470 identifier: Option<BleIdentifier>,
471 timeout: Duration,
472 on_start_scanning: impl FnOnce(),
473 ) -> Result<Self, BleError> {
474 let scan_timeout = Duration::from_secs(3);
475 let connect_timeout = Duration::from_secs(5).max(timeout);
476 let connection = crate::transport::ble::connect_to_device(
477 identifier,
478 scan_timeout,
479 connect_timeout,
480 on_start_scanning,
481 )?;
482
483 let transport = crate::transport::ble::BleTransport::from_connection(connection, timeout)?;
484 Ok(Self::new_from_transport(transport))
485 }
486
487 pub fn new_from_transport<T: IntoTransport>(transport: T) -> Self {
494 Self {
495 connection: Connection::new(transport),
496 smp_frame_size: ZEPHYR_DEFAULT_SMP_FRAME_SIZE.into(),
497 }
498 }
499
500 pub fn new_from_udp(addr: impl Into<SocketAddr>, timeout: Duration) -> Result<Self, UdpError> {
532 let addr = addr.into();
533 log::debug!("Connecting to {addr} ...");
534 Ok(Self::new_from_transport(UdpTransport::new(addr, timeout)?))
535 }
536
537 pub fn set_frame_size(&self, smp_frame_size: usize) {
542 self.smp_frame_size
543 .store(smp_frame_size, std::sync::atomic::Ordering::SeqCst);
544 }
545
546 pub fn use_auto_frame_size(&self) -> Result<(), MCUmgrClientError> {
550 let mcumgr_params = self
551 .connection
552 .execute_command(&commands::os::MCUmgrParameters)?;
553
554 let frame_size = (mcumgr_params.buf_size as usize)
555 .min(SMP_TRANSFER_BUFFER_SIZE)
556 .min(self.connection.max_transport_frame_size());
557
558 log::debug!("Using frame size {}.", frame_size);
559
560 self.smp_frame_size
561 .store(frame_size, std::sync::atomic::Ordering::SeqCst);
562
563 Ok(())
564 }
565
566 pub fn set_timeout(&self, timeout: Duration) -> Result<(), MCUmgrClientError> {
571 self.connection
572 .set_timeout(timeout)
573 .map_err(MCUmgrClientError::SetTimeoutFailed)
574 }
575
576 pub fn set_retries(&self, retries: u8) {
581 self.connection.set_retries(retries)
582 }
583
584 pub fn check_connection(&self) -> Result<(), MCUmgrClientError> {
592 let random_message = rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 16);
593 let response = self.os_echo(&random_message)?;
594 if random_message == response {
595 Ok(())
596 } else {
597 Err(
598 ExecuteError::ReceiveFailed(crate::transport::ReceiveError::UnexpectedResponse)
599 .into(),
600 )
601 }
602 }
603
604 pub fn firmware_update(
614 &self,
615 firmware: impl AsRef<[u8]>,
616 checksum: Option<[u8; 32]>,
617 params: FirmwareUpdateParams,
618 progress: Option<&mut FirmwareUpdateProgressCallback>,
619 ) -> Result<(), FirmwareUpdateError> {
620 firmware_update::firmware_update(self, firmware, checksum, params, progress)
621 }
622
623 pub fn os_echo(&self, msg: impl AsRef<str>) -> Result<String, MCUmgrClientError> {
627 self.connection
628 .execute_command(&commands::os::Echo { d: msg.as_ref() })
629 .map(|resp| resp.r)
630 .map_err(Into::into)
631 }
632
633 pub fn os_task_statistics(
644 &self,
645 ) -> Result<HashMap<String, commands::os::TaskStatisticsEntry>, MCUmgrClientError> {
646 self.connection
647 .execute_command(&commands::os::TaskStatistics)
648 .map(|resp| {
649 let mut tasks = resp.tasks;
650 for stats in tasks.values_mut() {
651 stats.stkuse = stats.stkuse.map(|val| val * 4);
652 stats.stksiz = stats.stksiz.map(|val| val * 4);
653 }
654 tasks
655 })
656 .map_err(Into::into)
657 }
658
659 pub fn os_memory_pool_statistics(
665 &self,
666 ) -> Result<HashMap<String, commands::os::MemoryPoolStatisticsEntry>, MCUmgrClientError> {
667 self.connection
668 .execute_command(&commands::os::MemoryPoolStatistics)
669 .map(|resp| resp.pools)
670 .map_err(Into::into)
671 }
672
673 pub fn os_set_datetime(
675 &self,
676 datetime: chrono::NaiveDateTime,
677 ) -> Result<(), MCUmgrClientError> {
678 self.connection
679 .execute_command(&commands::os::DateTimeSet { datetime })
680 .map(Into::into)
681 .map_err(Into::into)
682 }
683
684 pub fn os_get_datetime(&self) -> Result<chrono::NaiveDateTime, MCUmgrClientError> {
686 self.connection
687 .execute_command(&commands::os::DateTimeGet)
688 .map(|val| val.datetime)
689 .map_err(Into::into)
690 }
691
692 pub fn os_system_reset(
706 &self,
707 force: bool,
708 boot_mode: Option<u8>,
709 ) -> Result<(), MCUmgrClientError> {
710 self.connection
711 .execute_command(&commands::os::SystemReset { force, boot_mode })
712 .map(Into::into)
713 .map_err(Into::into)
714 }
715
716 pub fn os_mcumgr_parameters(
718 &self,
719 ) -> Result<commands::os::MCUmgrParametersResponse, MCUmgrClientError> {
720 self.connection
721 .execute_command(&commands::os::MCUmgrParameters)
722 .map_err(Into::into)
723 }
724
725 pub fn os_application_info(&self, format: Option<&str>) -> Result<String, MCUmgrClientError> {
737 self.connection
738 .execute_command(&commands::os::ApplicationInfo { format })
739 .map(|resp| resp.output)
740 .map_err(Into::into)
741 }
742
743 pub fn os_bootloader_info(&self) -> Result<BootloaderInfo, MCUmgrClientError> {
745 Ok(
746 match self
747 .connection
748 .execute_command(&commands::os::BootloaderInfo)?
749 .bootloader
750 .as_str()
751 {
752 "MCUboot" => {
753 let mode_data = self
754 .connection
755 .execute_command(&commands::os::BootloaderInfoMcubootMode {})?;
756 BootloaderInfo::MCUboot {
757 mode: mode_data.mode,
758 no_downgrade: mode_data.no_downgrade,
759 }
760 }
761 name => BootloaderInfo::Unknown {
762 name: name.to_string(),
763 },
764 },
765 )
766 }
767
768 pub fn image_get_state(&self) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
770 self.connection
771 .execute_command(&commands::image::GetImageState)
772 .map(|val| val.images)
773 .map_err(Into::into)
774 }
775
776 pub fn image_set_state(
792 &self,
793 hash: Option<&[u8]>,
794 confirm: bool,
795 ) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
796 self.connection
797 .execute_command(&commands::image::SetImageState { hash, confirm })
798 .map(|val| val.images)
799 .map_err(Into::into)
800 }
801
802 pub fn image_upload(
820 &self,
821 data: impl AsRef<[u8]>,
822 image: Option<u32>,
823 checksum: Option<[u8; 32]>,
824 upgrade_only: bool,
825 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
826 ) -> Result<(), MCUmgrClientError> {
827 let first_chunk_size_max = image_upload_max_data_chunk_size(
828 self.smp_frame_size
829 .load(std::sync::atomic::Ordering::SeqCst),
830 true,
831 )
832 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
833 let other_chunk_size_max = image_upload_max_data_chunk_size(
834 self.smp_frame_size
835 .load(std::sync::atomic::Ordering::SeqCst),
836 false,
837 )
838 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
839 log::debug!("Max chunk size: {first_chunk_size_max}, {other_chunk_size_max}");
840
841 let data = data.as_ref();
842
843 let actual_checksum: [u8; 32] = Sha256::digest(data).into();
844 if let Some(checksum) = checksum {
845 if actual_checksum != checksum {
846 return Err(MCUmgrClientError::ChecksumMismatch);
847 }
848 }
849
850 let mut offset = 0;
851 let size = data.len();
852
853 let mut checksum_matched = None;
854
855 loop {
856 let upload_response = if offset == 0 {
857 let current_chunk_size = (size - offset).min(first_chunk_size_max);
858 let chunk_data = &data[offset..offset + current_chunk_size];
859
860 let result = self
861 .connection
862 .execute_command(&commands::image::ImageUpload {
863 image,
864 len: Some(size as u64),
865 off: offset as u64,
866 sha: Some(&actual_checksum),
867 data: chunk_data,
868 upgrade: Some(upgrade_only),
869 });
870
871 if let Err(ExecuteError::ReceiveFailed(ReceiveError::Timeout)) = &result {
872 log::warn!(
873 "Timed out during transfer of first chunk. Consider enabling CONFIG_IMG_ERASE_PROGRESSIVELY."
874 )
875 }
876
877 result?
878 } else {
879 let current_chunk_size = (size - offset).min(other_chunk_size_max);
880 let chunk_data = &data[offset..offset + current_chunk_size];
881
882 self.connection
883 .execute_command(&commands::image::ImageUpload {
884 image: None,
885 len: None,
886 off: offset as u64,
887 sha: None,
888 data: chunk_data,
889 upgrade: None,
890 })?
891 };
892
893 offset = upload_response
894 .off
895 .try_into()
896 .map_err(|_| MCUmgrClientError::UnexpectedOffset)?;
897
898 if offset > size {
899 return Err(MCUmgrClientError::UnexpectedOffset);
900 }
901
902 if let Some(progress) = &mut progress {
903 if !progress(offset as u64, size as u64) {
904 return Err(MCUmgrClientError::ProgressCallbackError);
905 };
906 }
907
908 if let Some(is_match) = upload_response.r#match {
909 checksum_matched = Some(is_match);
910 }
911
912 if offset >= size {
913 break;
914 }
915 }
916
917 if let Some(checksum_matched) = checksum_matched {
918 if !checksum_matched {
919 return Err(MCUmgrClientError::ChecksumMismatchOnDevice);
920 }
921 } else {
922 log::warn!("Device did not perform image checksum verification");
923 }
924
925 Ok(())
926 }
927
928 pub fn image_erase(&self, slot: Option<u32>) -> Result<(), MCUmgrClientError> {
935 self.connection
936 .execute_command(&commands::image::ImageErase { slot })
937 .map(Into::into)
938 .map_err(Into::into)
939 }
940
941 pub fn image_slot_info(
943 &self,
944 ) -> Result<Vec<commands::image::SlotInfoImage>, MCUmgrClientError> {
945 self.connection
946 .execute_command(&commands::image::SlotInfo)
947 .map(|val| val.images)
948 .map_err(Into::into)
949 }
950
951 pub fn stats_get_group_data(
958 &self,
959 name: impl AsRef<str>,
960 ) -> Result<HashMap<String, u64>, MCUmgrClientError> {
961 self.connection
962 .execute_command(&commands::stats::GroupData {
963 name: name.as_ref(),
964 })
965 .map(|val| val.fields)
966 .map_err(Into::into)
967 }
968
969 pub fn stats_list_groups(&self) -> Result<Vec<String>, MCUmgrClientError> {
971 self.connection
972 .execute_command(&commands::stats::ListGroups)
973 .map(|val| val.stat_list)
974 .map_err(Into::into)
975 }
976
977 pub fn settings_read(&self, name: impl AsRef<str>) -> Result<Vec<u8>, MCUmgrClientError> {
990 let name = name.as_ref();
991
992 self.settings_read_ext(name, None).map(|val| val.val)
993 }
994
995 pub fn settings_read_ext(
1005 &self,
1006 name: impl AsRef<str>,
1007 max_size: Option<u32>,
1008 ) -> Result<commands::settings::ReadSettingResponse, MCUmgrClientError> {
1009 let name = name.as_ref();
1010
1011 self.connection
1012 .execute_command(&commands::settings::ReadSetting { name, max_size })
1013 .map_err(Into::into)
1014 }
1015
1016 pub fn settings_write(
1024 &self,
1025 name: impl AsRef<str>,
1026 value: &[u8],
1027 ) -> Result<(), MCUmgrClientError> {
1028 let name = name.as_ref();
1029
1030 self.connection
1031 .execute_command(&commands::settings::WriteSetting { name, val: value })
1032 .map(Into::into)
1033 .map_err(Into::into)
1034 }
1035
1036 pub fn settings_delete(&self, name: impl AsRef<str>) -> Result<(), MCUmgrClientError> {
1043 let name = name.as_ref();
1044
1045 self.connection
1046 .execute_command(&commands::settings::DeleteSetting { name })
1047 .map(Into::into)
1048 .map_err(Into::into)
1049 }
1050
1051 pub fn settings_commit(&self) -> Result<(), MCUmgrClientError> {
1054 self.connection
1055 .execute_command(&commands::settings::CommitSettings)
1056 .map(Into::into)
1057 .map_err(Into::into)
1058 }
1059
1060 pub fn settings_load(&self) -> Result<(), MCUmgrClientError> {
1063 self.connection
1064 .execute_command(&commands::settings::LoadSettings)
1065 .map(Into::into)
1066 .map_err(Into::into)
1067 }
1068
1069 pub fn settings_save(&self, name: Option<impl AsRef<str>>) -> Result<(), MCUmgrClientError> {
1076 let name = name.as_ref().map(|val| val.as_ref());
1077
1078 self.connection
1079 .execute_command(&commands::settings::SaveSettings { name })
1080 .map(Into::into)
1081 .map_err(Into::into)
1082 }
1083
1084 pub fn fs_file_download<T: Write>(
1098 &self,
1099 name: impl AsRef<str>,
1100 mut writer: T,
1101 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1102 ) -> Result<(), MCUmgrClientError> {
1103 let name = name.as_ref();
1104 let response = self
1105 .connection
1106 .execute_command(&commands::fs::FileDownload { name, off: 0 })?;
1107
1108 let file_len = response.len.ok_or(MCUmgrClientError::MissingSize)?;
1109 if response.off != 0 {
1110 return Err(MCUmgrClientError::UnexpectedOffset);
1111 }
1112
1113 let mut offset = 0;
1114
1115 if let Some(progress) = &mut progress {
1116 if !progress(offset, file_len) {
1117 return Err(MCUmgrClientError::ProgressCallbackError);
1118 };
1119 }
1120
1121 writer
1122 .write_all(&response.data)
1123 .map_err(MCUmgrClientError::WriterError)?;
1124 offset += response.data.len() as u64;
1125
1126 if let Some(progress) = &mut progress {
1127 if !progress(offset, file_len) {
1128 return Err(MCUmgrClientError::ProgressCallbackError);
1129 };
1130 }
1131
1132 while offset < file_len {
1133 let response = self
1134 .connection
1135 .execute_command(&commands::fs::FileDownload { name, off: offset })?;
1136
1137 if response.off != offset {
1138 return Err(MCUmgrClientError::UnexpectedOffset);
1139 }
1140
1141 if response.data.is_empty() {
1142 return Err(MCUmgrClientError::SizeMismatch);
1143 }
1144
1145 writer
1146 .write_all(&response.data)
1147 .map_err(MCUmgrClientError::WriterError)?;
1148 offset += response.data.len() as u64;
1149
1150 if let Some(progress) = &mut progress {
1151 if !progress(offset, file_len) {
1152 return Err(MCUmgrClientError::ProgressCallbackError);
1153 };
1154 }
1155 }
1156
1157 if offset != file_len {
1158 return Err(MCUmgrClientError::SizeMismatch);
1159 }
1160
1161 Ok(())
1162 }
1163
1164 pub fn fs_file_upload<T: Read>(
1180 &self,
1181 name: impl AsRef<str>,
1182 mut reader: T,
1183 size: u64,
1184 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1185 ) -> Result<(), MCUmgrClientError> {
1186 let name = name.as_ref();
1187
1188 let chunk_size_max = file_upload_max_data_chunk_size(
1189 self.smp_frame_size
1190 .load(std::sync::atomic::Ordering::SeqCst),
1191 name,
1192 )
1193 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
1194 let mut data_buffer = vec![0u8; chunk_size_max].into_boxed_slice();
1195
1196 let mut offset = 0;
1197
1198 loop {
1199 let current_chunk_size = (size - offset).min(data_buffer.len() as u64) as usize;
1200
1201 let chunk_buffer = &mut data_buffer[..current_chunk_size];
1202 reader
1203 .read_exact(chunk_buffer)
1204 .map_err(MCUmgrClientError::ReaderError)?;
1205
1206 let upload_response = self.connection.execute_command(&commands::fs::FileUpload {
1207 off: offset,
1208 data: chunk_buffer,
1209 name,
1210 len: if offset == 0 { Some(size) } else { None },
1211 })?;
1212
1213 offset += chunk_buffer.len() as u64;
1214
1215 if offset != upload_response.off {
1216 return Err(MCUmgrClientError::UnexpectedOffset);
1217 }
1218
1219 if let Some(progress) = &mut progress {
1220 if !progress(offset, size) {
1221 return Err(MCUmgrClientError::ProgressCallbackError);
1222 };
1223 }
1224
1225 if offset >= size {
1226 break;
1227 }
1228 }
1229
1230 Ok(())
1231 }
1232
1233 pub fn fs_file_status(
1235 &self,
1236 name: impl AsRef<str>,
1237 ) -> Result<commands::fs::FileStatusResponse, MCUmgrClientError> {
1238 self.connection
1239 .execute_command(&commands::fs::FileStatus {
1240 name: name.as_ref(),
1241 })
1242 .map_err(Into::into)
1243 }
1244
1245 pub fn fs_file_checksum(
1257 &self,
1258 name: impl AsRef<str>,
1259 algorithm: Option<impl AsRef<str>>,
1260 offset: u64,
1261 length: Option<u64>,
1262 ) -> Result<commands::fs::FileChecksumResponse, MCUmgrClientError> {
1263 self.connection
1264 .execute_command(&commands::fs::FileChecksum {
1265 name: name.as_ref(),
1266 r#type: algorithm.as_ref().map(AsRef::as_ref),
1267 off: offset,
1268 len: length,
1269 })
1270 .map_err(Into::into)
1271 }
1272
1273 pub fn fs_supported_checksum_types(
1275 &self,
1276 ) -> Result<HashMap<String, commands::fs::FileChecksumProperties>, MCUmgrClientError> {
1277 self.connection
1278 .execute_command(&commands::fs::SupportedFileChecksumTypes)
1279 .map(|val| val.types)
1280 .map_err(Into::into)
1281 }
1282
1283 pub fn fs_file_close(&self) -> Result<(), MCUmgrClientError> {
1285 self.connection
1286 .execute_command(&commands::fs::FileClose)
1287 .map(Into::into)
1288 .map_err(Into::into)
1289 }
1290
1291 pub fn shell_execute(
1303 &self,
1304 argv: &[String],
1305 use_retries: bool,
1306 ) -> Result<(i32, String), MCUmgrClientError> {
1307 let command = commands::shell::ShellCommandLineExecute { argv };
1308
1309 if use_retries {
1310 self.connection.execute_command(&command)
1311 } else {
1312 self.connection.execute_command_without_retries(&command)
1313 }
1314 .map(|ret| (ret.ret, ret.o))
1315 .map_err(Into::into)
1316 }
1317
1318 pub fn enum_get_group_count(&self) -> Result<u16, MCUmgrClientError> {
1325 self.connection
1326 .execute_command(&commands::r#enum::GroupCount)
1327 .map(|ret| ret.count)
1328 .map_err(Into::into)
1329 }
1330
1331 pub fn enum_get_group_ids(&self) -> Result<Vec<u16>, MCUmgrClientError> {
1346 self.connection
1347 .execute_command(&commands::r#enum::ListGroups)
1348 .map(|ret| ret.groups)
1349 .map_err(Into::into)
1350 }
1351
1352 pub fn enum_get_group_id(&self, index: u16) -> Result<u16, MCUmgrClientError> {
1364 self.connection
1365 .execute_command(&commands::r#enum::GroupId { index: Some(index) })
1366 .map(|ret| ret.group)
1367 .map_err(Into::into)
1368 }
1369
1370 pub fn enum_iter_group_ids(&self) -> impl Iterator<Item = Result<u16, MCUmgrClientError>> {
1376 let mut i = 0;
1377 let mut num_elements = None;
1378
1379 std::iter::from_fn(move || -> Option<Result<u16, MCUmgrClientError>> {
1380 let mut num_elements_err = None;
1381 let num_elements =
1382 *num_elements.get_or_insert_with(|| match self.enum_get_group_count() {
1383 Ok(n) => n,
1384 Err(e) => {
1385 num_elements_err = Some(e);
1386 0
1387 }
1388 });
1389 if let Some(err) = num_elements_err {
1390 return Some(Err(err));
1391 }
1392
1393 if i >= num_elements {
1394 None
1395 } else {
1396 Some(match self.enum_get_group_id(i) {
1397 Ok(group_id) => {
1398 i += 1;
1399 Ok(group_id)
1400 }
1401 Err(e) => {
1402 i = num_elements;
1403 Err(e)
1404 }
1405 })
1406 }
1407 })
1408 }
1409
1410 pub fn enum_get_group_details(
1421 &self,
1422 groups: Option<&[u16]>,
1423 ) -> Result<Vec<commands::r#enum::GroupDetailsEntry>, MCUmgrClientError> {
1424 self.connection
1425 .execute_command(&commands::r#enum::GroupDetails { groups })
1426 .map(|ret| ret.groups)
1427 .map_err(Into::into)
1428 }
1429
1430 pub fn zephyr_erase_storage(&self) -> Result<(), MCUmgrClientError> {
1432 self.connection
1433 .execute_command(&commands::zephyr::EraseStorage)
1434 .map(Into::into)
1435 .map_err(Into::into)
1436 }
1437
1438 pub fn raw_command<T: commands::McuMgrCommand>(
1444 &self,
1445 command: &T,
1446 ) -> Result<T::Response, MCUmgrClientError> {
1447 self.connection.execute_command(command).map_err(Into::into)
1448 }
1449}