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 fn usable_frame_size(&self) -> usize {
569 self.smp_frame_size
570 .load(std::sync::atomic::Ordering::SeqCst)
571 .saturating_sub(self.connection.device_rx_buffer_overhead())
572 }
573
574 pub fn set_timeout(&self, timeout: Duration) -> Result<(), MCUmgrClientError> {
579 self.connection
580 .set_timeout(timeout)
581 .map_err(MCUmgrClientError::SetTimeoutFailed)
582 }
583
584 pub fn set_retries(&self, retries: u8) {
589 self.connection.set_retries(retries)
590 }
591
592 pub fn check_connection(&self) -> Result<(), MCUmgrClientError> {
600 let random_message = rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 16);
601 let response = self.os_echo(&random_message)?;
602 if random_message == response {
603 Ok(())
604 } else {
605 Err(
606 ExecuteError::ReceiveFailed(crate::transport::ReceiveError::UnexpectedResponse)
607 .into(),
608 )
609 }
610 }
611
612 pub fn firmware_update(
622 &self,
623 firmware: impl AsRef<[u8]>,
624 checksum: Option<[u8; 32]>,
625 params: FirmwareUpdateParams,
626 progress: Option<&mut FirmwareUpdateProgressCallback>,
627 ) -> Result<(), FirmwareUpdateError> {
628 firmware_update::firmware_update(self, firmware, checksum, params, progress)
629 }
630
631 pub fn os_echo(&self, msg: impl AsRef<str>) -> Result<String, MCUmgrClientError> {
635 self.connection
636 .execute_command(&commands::os::Echo { d: msg.as_ref() })
637 .map(|resp| resp.r)
638 .map_err(Into::into)
639 }
640
641 pub fn os_task_statistics(
652 &self,
653 ) -> Result<HashMap<String, commands::os::TaskStatisticsEntry>, MCUmgrClientError> {
654 self.connection
655 .execute_command(&commands::os::TaskStatistics)
656 .map(|resp| {
657 let mut tasks = resp.tasks;
658 for stats in tasks.values_mut() {
659 stats.stkuse = stats.stkuse.map(|val| val * 4);
660 stats.stksiz = stats.stksiz.map(|val| val * 4);
661 }
662 tasks
663 })
664 .map_err(Into::into)
665 }
666
667 pub fn os_memory_pool_statistics(
673 &self,
674 ) -> Result<HashMap<String, commands::os::MemoryPoolStatisticsEntry>, MCUmgrClientError> {
675 self.connection
676 .execute_command(&commands::os::MemoryPoolStatistics)
677 .map(|resp| resp.pools)
678 .map_err(Into::into)
679 }
680
681 pub fn os_set_datetime(
683 &self,
684 datetime: chrono::NaiveDateTime,
685 ) -> Result<(), MCUmgrClientError> {
686 self.connection
687 .execute_command(&commands::os::DateTimeSet { datetime })
688 .map(Into::into)
689 .map_err(Into::into)
690 }
691
692 pub fn os_get_datetime(&self) -> Result<chrono::NaiveDateTime, MCUmgrClientError> {
694 self.connection
695 .execute_command(&commands::os::DateTimeGet)
696 .map(|val| val.datetime)
697 .map_err(Into::into)
698 }
699
700 pub fn os_system_reset(
714 &self,
715 force: bool,
716 boot_mode: Option<u8>,
717 ) -> Result<(), MCUmgrClientError> {
718 self.connection
719 .execute_command(&commands::os::SystemReset { force, boot_mode })
720 .map(Into::into)
721 .map_err(Into::into)
722 }
723
724 pub fn os_mcumgr_parameters(
726 &self,
727 ) -> Result<commands::os::MCUmgrParametersResponse, MCUmgrClientError> {
728 self.connection
729 .execute_command(&commands::os::MCUmgrParameters)
730 .map_err(Into::into)
731 }
732
733 pub fn os_application_info(&self, format: Option<&str>) -> Result<String, MCUmgrClientError> {
745 self.connection
746 .execute_command(&commands::os::ApplicationInfo { format })
747 .map(|resp| resp.output)
748 .map_err(Into::into)
749 }
750
751 pub fn os_bootloader_info(&self) -> Result<BootloaderInfo, MCUmgrClientError> {
753 Ok(
754 match self
755 .connection
756 .execute_command(&commands::os::BootloaderInfo)?
757 .bootloader
758 .as_str()
759 {
760 "MCUboot" => {
761 let mode_data = self
762 .connection
763 .execute_command(&commands::os::BootloaderInfoMcubootMode {})?;
764 BootloaderInfo::MCUboot {
765 mode: mode_data.mode,
766 no_downgrade: mode_data.no_downgrade,
767 }
768 }
769 name => BootloaderInfo::Unknown {
770 name: name.to_string(),
771 },
772 },
773 )
774 }
775
776 pub fn image_get_state(&self) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
778 self.connection
779 .execute_command(&commands::image::GetImageState)
780 .map(|val| val.images)
781 .map_err(Into::into)
782 }
783
784 pub fn image_set_state(
800 &self,
801 hash: Option<&[u8]>,
802 confirm: bool,
803 ) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
804 self.connection
805 .execute_command(&commands::image::SetImageState { hash, confirm })
806 .map(|val| val.images)
807 .map_err(Into::into)
808 }
809
810 pub fn image_upload(
828 &self,
829 data: impl AsRef<[u8]>,
830 image: Option<u32>,
831 checksum: Option<[u8; 32]>,
832 upgrade_only: bool,
833 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
834 ) -> Result<(), MCUmgrClientError> {
835 let first_chunk_size_max = image_upload_max_data_chunk_size(self.usable_frame_size(), true)
836 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
837 let other_chunk_size_max =
838 image_upload_max_data_chunk_size(self.usable_frame_size(), false)
839 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
840 log::debug!("Max chunk size: {first_chunk_size_max}, {other_chunk_size_max}");
841
842 let data = data.as_ref();
843
844 let actual_checksum: [u8; 32] = Sha256::digest(data).into();
845 if let Some(checksum) = checksum {
846 if actual_checksum != checksum {
847 return Err(MCUmgrClientError::ChecksumMismatch);
848 }
849 }
850
851 let mut offset = 0;
852 let size = data.len();
853
854 let mut checksum_matched = None;
855
856 loop {
857 let upload_response = if offset == 0 {
858 let current_chunk_size = (size - offset).min(first_chunk_size_max);
859 let chunk_data = &data[offset..offset + current_chunk_size];
860
861 let result = self
862 .connection
863 .execute_command(&commands::image::ImageUpload {
864 image,
865 len: Some(size as u64),
866 off: offset as u64,
867 sha: Some(&actual_checksum),
868 data: chunk_data,
869 upgrade: Some(upgrade_only),
870 });
871
872 if let Err(ExecuteError::ReceiveFailed(ReceiveError::Timeout)) = &result {
873 log::warn!(
874 "Timed out during transfer of first chunk. Consider enabling CONFIG_IMG_ERASE_PROGRESSIVELY."
875 )
876 }
877
878 result?
879 } else {
880 let current_chunk_size = (size - offset).min(other_chunk_size_max);
881 let chunk_data = &data[offset..offset + current_chunk_size];
882
883 self.connection
884 .execute_command(&commands::image::ImageUpload {
885 image: None,
886 len: None,
887 off: offset as u64,
888 sha: None,
889 data: chunk_data,
890 upgrade: None,
891 })?
892 };
893
894 offset = upload_response
895 .off
896 .try_into()
897 .map_err(|_| MCUmgrClientError::UnexpectedOffset)?;
898
899 if offset > size {
900 return Err(MCUmgrClientError::UnexpectedOffset);
901 }
902
903 if let Some(progress) = &mut progress {
904 if !progress(offset as u64, size as u64) {
905 return Err(MCUmgrClientError::ProgressCallbackError);
906 };
907 }
908
909 if let Some(is_match) = upload_response.r#match {
910 checksum_matched = Some(is_match);
911 }
912
913 if offset >= size {
914 break;
915 }
916 }
917
918 if let Some(checksum_matched) = checksum_matched {
919 if !checksum_matched {
920 return Err(MCUmgrClientError::ChecksumMismatchOnDevice);
921 }
922 } else {
923 log::warn!("Device did not perform image checksum verification");
924 }
925
926 Ok(())
927 }
928
929 pub fn image_erase(&self, slot: Option<u32>) -> Result<(), MCUmgrClientError> {
936 self.connection
937 .execute_command(&commands::image::ImageErase { slot })
938 .map(Into::into)
939 .map_err(Into::into)
940 }
941
942 pub fn image_slot_info(
944 &self,
945 ) -> Result<Vec<commands::image::SlotInfoImage>, MCUmgrClientError> {
946 self.connection
947 .execute_command(&commands::image::SlotInfo)
948 .map(|val| val.images)
949 .map_err(Into::into)
950 }
951
952 pub fn stats_get_group_data(
959 &self,
960 name: impl AsRef<str>,
961 ) -> Result<HashMap<String, u64>, MCUmgrClientError> {
962 self.connection
963 .execute_command(&commands::stats::GroupData {
964 name: name.as_ref(),
965 })
966 .map(|val| val.fields)
967 .map_err(Into::into)
968 }
969
970 pub fn stats_list_groups(&self) -> Result<Vec<String>, MCUmgrClientError> {
972 self.connection
973 .execute_command(&commands::stats::ListGroups)
974 .map(|val| val.stat_list)
975 .map_err(Into::into)
976 }
977
978 pub fn settings_read(&self, name: impl AsRef<str>) -> Result<Vec<u8>, MCUmgrClientError> {
991 let name = name.as_ref();
992
993 self.settings_read_ext(name, None).map(|val| val.val)
994 }
995
996 pub fn settings_read_ext(
1006 &self,
1007 name: impl AsRef<str>,
1008 max_size: Option<u32>,
1009 ) -> Result<commands::settings::ReadSettingResponse, MCUmgrClientError> {
1010 let name = name.as_ref();
1011
1012 self.connection
1013 .execute_command(&commands::settings::ReadSetting { name, max_size })
1014 .map_err(Into::into)
1015 }
1016
1017 pub fn settings_write(
1025 &self,
1026 name: impl AsRef<str>,
1027 value: &[u8],
1028 ) -> Result<(), MCUmgrClientError> {
1029 let name = name.as_ref();
1030
1031 self.connection
1032 .execute_command(&commands::settings::WriteSetting { name, val: value })
1033 .map(Into::into)
1034 .map_err(Into::into)
1035 }
1036
1037 pub fn settings_delete(&self, name: impl AsRef<str>) -> Result<(), MCUmgrClientError> {
1044 let name = name.as_ref();
1045
1046 self.connection
1047 .execute_command(&commands::settings::DeleteSetting { name })
1048 .map(Into::into)
1049 .map_err(Into::into)
1050 }
1051
1052 pub fn settings_commit(&self) -> Result<(), MCUmgrClientError> {
1055 self.connection
1056 .execute_command(&commands::settings::CommitSettings)
1057 .map(Into::into)
1058 .map_err(Into::into)
1059 }
1060
1061 pub fn settings_load(&self) -> Result<(), MCUmgrClientError> {
1064 self.connection
1065 .execute_command(&commands::settings::LoadSettings)
1066 .map(Into::into)
1067 .map_err(Into::into)
1068 }
1069
1070 pub fn settings_save(&self, name: Option<impl AsRef<str>>) -> Result<(), MCUmgrClientError> {
1077 let name = name.as_ref().map(|val| val.as_ref());
1078
1079 self.connection
1080 .execute_command(&commands::settings::SaveSettings { name })
1081 .map(Into::into)
1082 .map_err(Into::into)
1083 }
1084
1085 pub fn fs_file_download<T: Write>(
1099 &self,
1100 name: impl AsRef<str>,
1101 mut writer: T,
1102 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1103 ) -> Result<(), MCUmgrClientError> {
1104 let name = name.as_ref();
1105 let response = self
1106 .connection
1107 .execute_command(&commands::fs::FileDownload { name, off: 0 })?;
1108
1109 let file_len = response.len.ok_or(MCUmgrClientError::MissingSize)?;
1110 if response.off != 0 {
1111 return Err(MCUmgrClientError::UnexpectedOffset);
1112 }
1113
1114 let mut offset = 0;
1115
1116 if let Some(progress) = &mut progress {
1117 if !progress(offset, file_len) {
1118 return Err(MCUmgrClientError::ProgressCallbackError);
1119 };
1120 }
1121
1122 writer
1123 .write_all(&response.data)
1124 .map_err(MCUmgrClientError::WriterError)?;
1125 offset += response.data.len() as u64;
1126
1127 if let Some(progress) = &mut progress {
1128 if !progress(offset, file_len) {
1129 return Err(MCUmgrClientError::ProgressCallbackError);
1130 };
1131 }
1132
1133 while offset < file_len {
1134 let response = self
1135 .connection
1136 .execute_command(&commands::fs::FileDownload { name, off: offset })?;
1137
1138 if response.off != offset {
1139 return Err(MCUmgrClientError::UnexpectedOffset);
1140 }
1141
1142 if response.data.is_empty() {
1143 return Err(MCUmgrClientError::SizeMismatch);
1144 }
1145
1146 writer
1147 .write_all(&response.data)
1148 .map_err(MCUmgrClientError::WriterError)?;
1149 offset += response.data.len() as u64;
1150
1151 if let Some(progress) = &mut progress {
1152 if !progress(offset, file_len) {
1153 return Err(MCUmgrClientError::ProgressCallbackError);
1154 };
1155 }
1156 }
1157
1158 if offset != file_len {
1159 return Err(MCUmgrClientError::SizeMismatch);
1160 }
1161
1162 Ok(())
1163 }
1164
1165 pub fn fs_file_upload<T: Read>(
1181 &self,
1182 name: impl AsRef<str>,
1183 mut reader: T,
1184 size: u64,
1185 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1186 ) -> Result<(), MCUmgrClientError> {
1187 let name = name.as_ref();
1188
1189 let chunk_size_max = file_upload_max_data_chunk_size(self.usable_frame_size(), name)
1190 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
1191 let mut data_buffer = vec![0u8; chunk_size_max].into_boxed_slice();
1192
1193 let mut offset = 0;
1194
1195 loop {
1196 let current_chunk_size = (size - offset).min(data_buffer.len() as u64) as usize;
1197
1198 let chunk_buffer = &mut data_buffer[..current_chunk_size];
1199 reader
1200 .read_exact(chunk_buffer)
1201 .map_err(MCUmgrClientError::ReaderError)?;
1202
1203 let upload_response = self.connection.execute_command(&commands::fs::FileUpload {
1204 off: offset,
1205 data: chunk_buffer,
1206 name,
1207 len: if offset == 0 { Some(size) } else { None },
1208 })?;
1209
1210 offset += chunk_buffer.len() as u64;
1211
1212 if offset != upload_response.off {
1213 return Err(MCUmgrClientError::UnexpectedOffset);
1214 }
1215
1216 if let Some(progress) = &mut progress {
1217 if !progress(offset, size) {
1218 return Err(MCUmgrClientError::ProgressCallbackError);
1219 };
1220 }
1221
1222 if offset >= size {
1223 break;
1224 }
1225 }
1226
1227 Ok(())
1228 }
1229
1230 pub fn fs_file_status(
1232 &self,
1233 name: impl AsRef<str>,
1234 ) -> Result<commands::fs::FileStatusResponse, MCUmgrClientError> {
1235 self.connection
1236 .execute_command(&commands::fs::FileStatus {
1237 name: name.as_ref(),
1238 })
1239 .map_err(Into::into)
1240 }
1241
1242 pub fn fs_file_checksum(
1254 &self,
1255 name: impl AsRef<str>,
1256 algorithm: Option<impl AsRef<str>>,
1257 offset: u64,
1258 length: Option<u64>,
1259 ) -> Result<commands::fs::FileChecksumResponse, MCUmgrClientError> {
1260 self.connection
1261 .execute_command(&commands::fs::FileChecksum {
1262 name: name.as_ref(),
1263 r#type: algorithm.as_ref().map(AsRef::as_ref),
1264 off: offset,
1265 len: length,
1266 })
1267 .map_err(Into::into)
1268 }
1269
1270 pub fn fs_supported_checksum_types(
1272 &self,
1273 ) -> Result<HashMap<String, commands::fs::FileChecksumProperties>, MCUmgrClientError> {
1274 self.connection
1275 .execute_command(&commands::fs::SupportedFileChecksumTypes)
1276 .map(|val| val.types)
1277 .map_err(Into::into)
1278 }
1279
1280 pub fn fs_file_close(&self) -> Result<(), MCUmgrClientError> {
1282 self.connection
1283 .execute_command(&commands::fs::FileClose)
1284 .map(Into::into)
1285 .map_err(Into::into)
1286 }
1287
1288 pub fn shell_execute(
1300 &self,
1301 argv: &[String],
1302 use_retries: bool,
1303 ) -> Result<(i32, String), MCUmgrClientError> {
1304 let command = commands::shell::ShellCommandLineExecute { argv };
1305
1306 if use_retries {
1307 self.connection.execute_command(&command)
1308 } else {
1309 self.connection.execute_command_without_retries(&command)
1310 }
1311 .map(|ret| (ret.ret, ret.o))
1312 .map_err(Into::into)
1313 }
1314
1315 pub fn enum_get_group_count(&self) -> Result<u16, MCUmgrClientError> {
1322 self.connection
1323 .execute_command(&commands::r#enum::GroupCount)
1324 .map(|ret| ret.count)
1325 .map_err(Into::into)
1326 }
1327
1328 pub fn enum_get_group_ids(&self) -> Result<Vec<u16>, MCUmgrClientError> {
1343 self.connection
1344 .execute_command(&commands::r#enum::ListGroups)
1345 .map(|ret| ret.groups)
1346 .map_err(Into::into)
1347 }
1348
1349 pub fn enum_get_group_id(&self, index: u16) -> Result<u16, MCUmgrClientError> {
1361 self.connection
1362 .execute_command(&commands::r#enum::GroupId { index: Some(index) })
1363 .map(|ret| ret.group)
1364 .map_err(Into::into)
1365 }
1366
1367 pub fn enum_iter_group_ids(&self) -> impl Iterator<Item = Result<u16, MCUmgrClientError>> {
1373 let mut i = 0;
1374 let mut num_elements = None;
1375
1376 std::iter::from_fn(move || -> Option<Result<u16, MCUmgrClientError>> {
1377 let mut num_elements_err = None;
1378 let num_elements =
1379 *num_elements.get_or_insert_with(|| match self.enum_get_group_count() {
1380 Ok(n) => n,
1381 Err(e) => {
1382 num_elements_err = Some(e);
1383 0
1384 }
1385 });
1386 if let Some(err) = num_elements_err {
1387 return Some(Err(err));
1388 }
1389
1390 if i >= num_elements {
1391 None
1392 } else {
1393 Some(match self.enum_get_group_id(i) {
1394 Ok(group_id) => {
1395 i += 1;
1396 Ok(group_id)
1397 }
1398 Err(e) => {
1399 i = num_elements;
1400 Err(e)
1401 }
1402 })
1403 }
1404 })
1405 }
1406
1407 pub fn enum_get_group_details(
1418 &self,
1419 groups: Option<&[u16]>,
1420 ) -> Result<Vec<commands::r#enum::GroupDetailsEntry>, MCUmgrClientError> {
1421 self.connection
1422 .execute_command(&commands::r#enum::GroupDetails { groups })
1423 .map(|ret| ret.groups)
1424 .map_err(Into::into)
1425 }
1426
1427 pub fn zephyr_erase_storage(&self) -> Result<(), MCUmgrClientError> {
1429 self.connection
1430 .execute_command(&commands::zephyr::EraseStorage)
1431 .map(Into::into)
1432 .map_err(Into::into)
1433 }
1434
1435 pub fn raw_command<T: commands::McuMgrCommand>(
1441 &self,
1442 command: &T,
1443 ) -> Result<T::Response, MCUmgrClientError> {
1444 self.connection.execute_command(command).map_err(Into::into)
1445 }
1446}