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
35#[cfg(feature = "ble")]
36use crate::transport::ble::{BleIdentifier, BleRuntimeError};
37
38const ZEPHYR_DEFAULT_SMP_FRAME_SIZE: usize = 384;
42
43pub struct MCUmgrClient {
47 connection: Connection,
48 smp_frame_size: AtomicUsize,
49}
50
51#[derive(Error, Debug, Diagnostic)]
53pub enum MCUmgrClientError {
54 #[error("Command execution failed")]
56 #[diagnostic(code(mcumgr_toolkit::client::execute))]
57 ExecuteError(#[from] ExecuteError),
58 #[error("Received an unexpected offset value")]
60 #[diagnostic(code(mcumgr_toolkit::client::unexpected_offset))]
61 UnexpectedOffset,
62 #[error("Writer returned an error")]
64 #[diagnostic(code(mcumgr_toolkit::client::writer))]
65 WriterError(#[source] io::Error),
66 #[error("Reader returned an error")]
68 #[diagnostic(code(mcumgr_toolkit::client::reader))]
69 ReaderError(#[source] io::Error),
70 #[error("Received data does not match reported size")]
72 #[diagnostic(code(mcumgr_toolkit::client::size_mismatch))]
73 SizeMismatch,
74 #[error("Received data is missing file size information")]
76 #[diagnostic(code(mcumgr_toolkit::client::missing_size))]
77 MissingSize,
78 #[error("Progress callback returned an error")]
80 #[diagnostic(code(mcumgr_toolkit::client::progress_cb_error))]
81 ProgressCallbackError,
82 #[error("SMP frame size too small for this command")]
84 #[diagnostic(code(mcumgr_toolkit::client::framesize_too_small))]
85 FrameSizeTooSmall(#[source] io::Error),
86 #[error("Device reported checksum mismatch")]
88 #[diagnostic(code(mcumgr_toolkit::client::checksum_mismatch_on_device))]
89 ChecksumMismatchOnDevice,
90 #[error("Firmware image does not match given checksum")]
92 #[diagnostic(code(mcumgr_toolkit::client::checksum_mismatch))]
93 ChecksumMismatch,
94 #[error("Failed to set the device timeout")]
96 #[diagnostic(code(mcumgr_toolkit::client::set_timeout))]
97 SetTimeoutFailed(#[source] Box<dyn std::error::Error + Send + Sync>),
98}
99
100impl MCUmgrClientError {
101 pub fn command_not_supported(&self) -> bool {
103 if let Self::ExecuteError(err) = self {
104 err.command_not_supported()
105 } else {
106 false
107 }
108 }
109}
110
111#[derive(Debug, Serialize, Clone, Eq, PartialEq)]
113pub struct UsbSerialPortInfo {
114 pub identifier: String,
116 pub port_name: String,
118 pub port_info: serialport::UsbPortInfo,
120}
121
122#[derive(Serialize, Clone, Eq, PartialEq)]
126#[serde(transparent)]
127pub struct UsbSerialPorts(pub Vec<UsbSerialPortInfo>);
128impl std::fmt::Display for UsbSerialPorts {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 if self.0.is_empty() {
131 writeln!(f)?;
132 write!(f, " - None -")?;
133 return Ok(());
134 }
135
136 for UsbSerialPortInfo {
137 identifier,
138 port_name,
139 port_info,
140 } in &self.0
141 {
142 writeln!(f)?;
143 write!(f, " - {identifier}")?;
144
145 let mut print_port_string = true;
146 let port_string = format!("({port_name})");
147
148 if port_info.manufacturer.is_some() || port_info.product.is_some() {
149 write!(f, " -")?;
150 if let Some(manufacturer) = &port_info.manufacturer {
151 let mut print_manufacturer = true;
152
153 if let Some(product) = &port_info.product {
154 if product.starts_with(manufacturer) {
155 print_manufacturer = false;
156 }
157 }
158
159 if print_manufacturer {
160 write!(f, " {manufacturer}")?;
161 }
162 }
163 if let Some(product) = &port_info.product {
164 write!(f, " {product}")?;
165
166 if product.ends_with(&port_string) {
167 print_port_string = false;
168 }
169 }
170 }
171
172 if print_port_string {
173 write!(f, " {port_string}")?;
174 }
175 }
176 Ok(())
177 }
178}
179impl std::fmt::Debug for UsbSerialPorts {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 std::fmt::Debug::fmt(&self.0, f)
182 }
183}
184
185#[cfg(feature = "ble")]
186fn ble_identifier_to_str<S>(id: &BleIdentifier, ser: S) -> Result<S::Ok, S::Error>
187where
188 S: serde::Serializer,
189{
190 ser.collect_str(id)
191}
192
193#[cfg(feature = "ble")]
195#[derive(Debug, Serialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
196pub struct BleDeviceInfo {
197 #[serde(serialize_with = "ble_identifier_to_str")]
199 pub id: BleIdentifier,
200 pub name: Option<String>,
202 pub rssi: Option<i16>,
204}
205
206#[cfg(feature = "ble")]
210#[derive(Serialize, Clone, Eq, PartialEq)]
211#[serde(transparent)]
212pub struct BleDevices(pub Vec<BleDeviceInfo>);
213
214#[cfg(feature = "ble")]
215impl std::fmt::Display for BleDevices {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 if self.0.is_empty() {
218 writeln!(f)?;
219 write!(f, " - None -")?;
220 return Ok(());
221 }
222
223 for BleDeviceInfo { id, name, rssi } in &self.0 {
224 writeln!(f)?;
225
226 if let Some(name) = name {
227 write!(f, " - {id} - {name:?}")?;
228 } else {
229 write!(f, " - {id} - <unknown>")?;
230 }
231
232 if let Some(rssi) = rssi {
233 write!(f, " ({rssi} dBm)")?;
234 }
235 }
236 Ok(())
237 }
238}
239
240#[cfg(feature = "ble")]
241impl std::fmt::Debug for BleDevices {
242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 std::fmt::Debug::fmt(&self.0, f)
244 }
245}
246
247#[derive(Error, Debug, Diagnostic)]
249pub enum UdpError {
250 #[error("Failed to open UDP socket")]
252 #[diagnostic(code(mcumgr_toolkit::udp::io_error))]
253 Io(#[from] io::Error),
254}
255
256#[derive(Error, Debug, Diagnostic)]
258pub enum UsbSerialError {
259 #[error("Serialport returned an error")]
261 #[diagnostic(code(mcumgr_toolkit::usb_serial::serialport_error))]
262 SerialPortError(#[from] serialport::Error),
263 #[error("No serial port matched the identifier '{identifier}'\nAvailable ports:\n{available}")]
265 #[diagnostic(code(mcumgr_toolkit::usb_serial::no_matches))]
266 NoMatchingPort {
267 identifier: String,
269 available: UsbSerialPorts,
271 },
272 #[error("Multiple serial ports matched the identifier '{identifier}'\n{ports}")]
274 #[diagnostic(code(mcumgr_toolkit::usb_serial::multiple_matches))]
275 MultipleMatchingPorts {
276 identifier: String,
278 ports: UsbSerialPorts,
280 },
281 #[error("An empty identifier was provided")]
284 #[diagnostic(code(mcumgr_toolkit::usb_serial::empty_identifier))]
285 IdentifierEmpty {
286 ports: UsbSerialPorts,
288 },
289 #[error("The given identifier was not a valid RegEx")]
291 #[diagnostic(code(mcumgr_toolkit::usb_serial::regex_error))]
292 RegexError(#[from] regex::Error),
293}
294
295#[cfg(feature = "ble")]
297#[derive(Error, Debug, Diagnostic)]
298pub enum BleError {
299 #[error("BLE runtime layer returned an error")]
301 #[diagnostic(code(mcumgr_toolkit::ble::runtime))]
302 BleRuntime(#[from] BleRuntimeError),
303 #[error("BLE scanning unexpectedly stopped")]
305 #[diagnostic(code(mcumgr_toolkit::ble::scan_stopped))]
306 ScanStopped,
307 #[error("Device not found\nAvailable devices:\n{available}")]
309 #[diagnostic(code(mcumgr_toolkit::ble::device_not_found))]
310 DeviceNotFound {
311 available: BleDevices,
313 },
314 #[error("An empty identifier was provided")]
317 #[diagnostic(code(mcumgr_toolkit::ble::empty_identifier))]
318 IdentifierEmpty {
319 devices: BleDevices,
321 },
322}
323
324impl MCUmgrClient {
325 pub fn new_from_serial<T: Send + Read + Write + ConfigurableTimeout + 'static>(
338 serial: T,
339 ) -> Self {
340 Self {
341 connection: Connection::new(SerialTransport::new(serial)),
342 smp_frame_size: ZEPHYR_DEFAULT_SMP_FRAME_SIZE.into(),
343 }
344 }
345
346 pub fn new_from_usb_serial(
364 identifier: impl AsRef<str>,
365 baud_rate: u32,
366 timeout: Duration,
367 ) -> Result<Self, UsbSerialError> {
368 let identifier = identifier.as_ref();
369
370 let ports = serialport::available_ports()?
371 .into_iter()
372 .filter_map(|port| {
373 if let serialport::SerialPortType::UsbPort(port_info) = port.port_type {
374 if let Some(interface) = port_info.interface {
375 Some(UsbSerialPortInfo {
376 identifier: format!(
377 "{:04x}:{:04x}:{}",
378 port_info.vid, port_info.pid, interface
379 ),
380 port_name: port.port_name,
381 port_info,
382 })
383 } else {
384 Some(UsbSerialPortInfo {
385 identifier: format!("{:04x}:{:04x}", port_info.vid, port_info.pid),
386 port_name: port.port_name,
387 port_info,
388 })
389 }
390 } else {
391 None
392 }
393 })
394 .collect::<Vec<_>>();
395
396 if identifier.is_empty() {
397 return Err(UsbSerialError::IdentifierEmpty {
398 ports: UsbSerialPorts(ports),
399 });
400 }
401
402 let port_regex = regex::RegexBuilder::new(identifier)
403 .case_insensitive(true)
404 .unicode(true)
405 .build()?;
406
407 let matches = ports
408 .iter()
409 .filter(|port| {
410 if let Some(m) = port_regex.find(&port.identifier) {
411 m.start() == 0
413 } else {
414 false
415 }
416 })
417 .cloned()
418 .collect::<Vec<_>>();
419
420 if matches.len() > 1 {
421 return Err(UsbSerialError::MultipleMatchingPorts {
422 identifier: identifier.to_string(),
423 ports: UsbSerialPorts(matches),
424 });
425 }
426
427 let port_name = match matches.into_iter().next() {
428 Some(port) => port.port_name,
429 None => {
430 return Err(UsbSerialError::NoMatchingPort {
431 identifier: identifier.to_string(),
432 available: UsbSerialPorts(ports),
433 });
434 }
435 };
436
437 let serial = serialport::new(port_name, baud_rate)
438 .timeout(timeout)
439 .open()?;
440
441 Ok(Self::new_from_serial(serial))
442 }
443
444 #[cfg(feature = "ble")]
452 pub fn new_from_ble(
453 identifier: Option<BleIdentifier>,
454 timeout: Duration,
455 ) -> Result<Self, BleError> {
456 use btleplug::api::{Central, Peripheral};
457 use futures::StreamExt;
458 use tokio::time::error::Elapsed;
459
460 let mut runtime = crate::transport::ble::BleRuntime::new()?;
461
462 let scan_timeout = Duration::from_secs(3);
463
464 let mut devices = HashMap::new();
465
466 let device = runtime.scan(
467 async |mut events, central| -> Result<btleplug::platform::Peripheral, BleError> {
468 tokio::time::timeout(scan_timeout, async {
469 loop {
470 match events.next().await.ok_or(BleError::ScanStopped)? {
471 btleplug::api::CentralEvent::DeviceDiscovered(id)
472 | btleplug::api::CentralEvent::DeviceConnected(id)
473 | btleplug::api::CentralEvent::DeviceUpdated(id)
474 | btleplug::api::CentralEvent::DeviceServicesModified(id)
475 | btleplug::api::CentralEvent::ServiceDataAdvertisement {
476 id,
477 service_data: _,
478 }
479 | btleplug::api::CentralEvent::ServicesAdvertisement {
480 id,
481 services: _,
482 }
483 | btleplug::api::CentralEvent::ManufacturerDataAdvertisement {
484 id,
485 manufacturer_data: _,
486 } => {
487 if let Ok(device) = central.peripheral(&id).await {
488 #[allow(irrefutable_let_patterns)]
491 #[allow(clippy::unnecessary_fallible_conversions)]
492 if let Ok(current_identifier) = BleIdentifier::try_from(&device)
493 {
494 if let Some(identifier) = &identifier
495 && identifier == ¤t_identifier
496 {
497 break Ok(device);
498 }
499
500 if let Ok(Some(properties)) = device.properties().await
501 && properties
502 .services
503 .contains(&crate::transport::ble::SMP_UUID)
504 {
505 devices.entry(id).insert_entry(BleDeviceInfo {
506 id: current_identifier,
507 name: properties.local_name,
508 rssi: properties.rssi,
509 });
510 }
511 }
512 }
513 }
514 btleplug::api::CentralEvent::RssiUpdate { id, rssi } => {
515 if let Some(device) = devices.get_mut(&id) {
516 device.rssi = Some(rssi);
517 }
518 }
519 _ => (),
520 }
521 }
522 })
523 .await
524 .map_err(|_: Elapsed| {
525 let devices = BleDevices({
526 let mut device_list = devices.into_values().collect::<Vec<_>>();
527 device_list.sort();
528 device_list
529 });
530 if identifier.is_none() {
531 BleError::IdentifierEmpty { devices }
532 } else {
533 BleError::DeviceNotFound { available: devices }
534 }
535 })?
536 },
537 )??;
538
539 let transport = runtime.into_transport(device, timeout)?;
540 Ok(Self {
541 connection: Connection::new(transport),
542 smp_frame_size: ZEPHYR_DEFAULT_SMP_FRAME_SIZE.into(),
543 })
544 }
545
546 pub fn new_from_udp(addr: impl Into<SocketAddr>, timeout: Duration) -> Result<Self, UdpError> {
578 let addr = addr.into();
579 log::debug!("Connecting to {addr} ...");
580 Ok(Self {
581 connection: Connection::new(UdpTransport::new(addr, timeout)?),
582 smp_frame_size: ZEPHYR_DEFAULT_SMP_FRAME_SIZE.into(),
583 })
584 }
585
586 pub fn set_frame_size(&self, smp_frame_size: usize) {
591 self.smp_frame_size
592 .store(smp_frame_size, std::sync::atomic::Ordering::SeqCst);
593 }
594
595 pub fn use_auto_frame_size(&self) -> Result<(), MCUmgrClientError> {
599 let mcumgr_params = self
600 .connection
601 .execute_command(&commands::os::MCUmgrParameters)?;
602
603 let frame_size =
604 (mcumgr_params.buf_size as usize).min(self.connection.max_transport_frame_size());
605
606 log::debug!("Using frame size {}.", frame_size);
607
608 self.smp_frame_size
609 .store(frame_size, std::sync::atomic::Ordering::SeqCst);
610
611 Ok(())
612 }
613
614 pub fn set_timeout(&self, timeout: Duration) -> Result<(), MCUmgrClientError> {
619 self.connection
620 .set_timeout(timeout)
621 .map_err(MCUmgrClientError::SetTimeoutFailed)
622 }
623
624 pub fn set_retries(&self, retries: u8) {
629 self.connection.set_retries(retries)
630 }
631
632 pub fn check_connection(&self) -> Result<(), MCUmgrClientError> {
640 let random_message = rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 16);
641 let response = self.os_echo(&random_message)?;
642 if random_message == response {
643 Ok(())
644 } else {
645 Err(
646 ExecuteError::ReceiveFailed(crate::transport::ReceiveError::UnexpectedResponse)
647 .into(),
648 )
649 }
650 }
651
652 pub fn firmware_update(
662 &self,
663 firmware: impl AsRef<[u8]>,
664 checksum: Option<[u8; 32]>,
665 params: FirmwareUpdateParams,
666 progress: Option<&mut FirmwareUpdateProgressCallback>,
667 ) -> Result<(), FirmwareUpdateError> {
668 firmware_update::firmware_update(self, firmware, checksum, params, progress)
669 }
670
671 pub fn os_echo(&self, msg: impl AsRef<str>) -> Result<String, MCUmgrClientError> {
675 self.connection
676 .execute_command(&commands::os::Echo { d: msg.as_ref() })
677 .map(|resp| resp.r)
678 .map_err(Into::into)
679 }
680
681 pub fn os_task_statistics(
692 &self,
693 ) -> Result<HashMap<String, commands::os::TaskStatisticsEntry>, MCUmgrClientError> {
694 self.connection
695 .execute_command(&commands::os::TaskStatistics)
696 .map(|resp| {
697 let mut tasks = resp.tasks;
698 for stats in tasks.values_mut() {
699 stats.stkuse = stats.stkuse.map(|val| val * 4);
700 stats.stksiz = stats.stksiz.map(|val| val * 4);
701 }
702 tasks
703 })
704 .map_err(Into::into)
705 }
706
707 pub fn os_memory_pool_statistics(
713 &self,
714 ) -> Result<HashMap<String, commands::os::MemoryPoolStatisticsEntry>, MCUmgrClientError> {
715 self.connection
716 .execute_command(&commands::os::MemoryPoolStatistics)
717 .map(|resp| resp.pools)
718 .map_err(Into::into)
719 }
720
721 pub fn os_set_datetime(
723 &self,
724 datetime: chrono::NaiveDateTime,
725 ) -> Result<(), MCUmgrClientError> {
726 self.connection
727 .execute_command(&commands::os::DateTimeSet { datetime })
728 .map(Into::into)
729 .map_err(Into::into)
730 }
731
732 pub fn os_get_datetime(&self) -> Result<chrono::NaiveDateTime, MCUmgrClientError> {
734 self.connection
735 .execute_command(&commands::os::DateTimeGet)
736 .map(|val| val.datetime)
737 .map_err(Into::into)
738 }
739
740 pub fn os_system_reset(
754 &self,
755 force: bool,
756 boot_mode: Option<u8>,
757 ) -> Result<(), MCUmgrClientError> {
758 self.connection
759 .execute_command(&commands::os::SystemReset { force, boot_mode })
760 .map(Into::into)
761 .map_err(Into::into)
762 }
763
764 pub fn os_mcumgr_parameters(
766 &self,
767 ) -> Result<commands::os::MCUmgrParametersResponse, MCUmgrClientError> {
768 self.connection
769 .execute_command(&commands::os::MCUmgrParameters)
770 .map_err(Into::into)
771 }
772
773 pub fn os_application_info(&self, format: Option<&str>) -> Result<String, MCUmgrClientError> {
785 self.connection
786 .execute_command(&commands::os::ApplicationInfo { format })
787 .map(|resp| resp.output)
788 .map_err(Into::into)
789 }
790
791 pub fn os_bootloader_info(&self) -> Result<BootloaderInfo, MCUmgrClientError> {
793 Ok(
794 match self
795 .connection
796 .execute_command(&commands::os::BootloaderInfo)?
797 .bootloader
798 .as_str()
799 {
800 "MCUboot" => {
801 let mode_data = self
802 .connection
803 .execute_command(&commands::os::BootloaderInfoMcubootMode {})?;
804 BootloaderInfo::MCUboot {
805 mode: mode_data.mode,
806 no_downgrade: mode_data.no_downgrade,
807 }
808 }
809 name => BootloaderInfo::Unknown {
810 name: name.to_string(),
811 },
812 },
813 )
814 }
815
816 pub fn image_get_state(&self) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
818 self.connection
819 .execute_command(&commands::image::GetImageState)
820 .map(|val| val.images)
821 .map_err(Into::into)
822 }
823
824 pub fn image_set_state(
840 &self,
841 hash: Option<&[u8]>,
842 confirm: bool,
843 ) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
844 self.connection
845 .execute_command(&commands::image::SetImageState { hash, confirm })
846 .map(|val| val.images)
847 .map_err(Into::into)
848 }
849
850 pub fn image_upload(
868 &self,
869 data: impl AsRef<[u8]>,
870 image: Option<u32>,
871 checksum: Option<[u8; 32]>,
872 upgrade_only: bool,
873 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
874 ) -> Result<(), MCUmgrClientError> {
875 let first_chunk_size_max = image_upload_max_data_chunk_size(
876 self.smp_frame_size
877 .load(std::sync::atomic::Ordering::SeqCst),
878 true,
879 )
880 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
881 let other_chunk_size_max = image_upload_max_data_chunk_size(
882 self.smp_frame_size
883 .load(std::sync::atomic::Ordering::SeqCst),
884 false,
885 )
886 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
887 log::debug!("Max chunk size: {first_chunk_size_max}, {other_chunk_size_max}");
888
889 let data = data.as_ref();
890
891 let actual_checksum: [u8; 32] = Sha256::digest(data).into();
892 if let Some(checksum) = checksum {
893 if actual_checksum != checksum {
894 return Err(MCUmgrClientError::ChecksumMismatch);
895 }
896 }
897
898 let mut offset = 0;
899 let size = data.len();
900
901 let mut checksum_matched = None;
902
903 while offset < size {
904 let upload_response = if offset == 0 {
905 let current_chunk_size = (size - offset).min(first_chunk_size_max);
906 let chunk_data = &data[offset..offset + current_chunk_size];
907
908 let result = self
909 .connection
910 .execute_command(&commands::image::ImageUpload {
911 image,
912 len: Some(size as u64),
913 off: offset as u64,
914 sha: Some(&actual_checksum),
915 data: chunk_data,
916 upgrade: Some(upgrade_only),
917 });
918
919 if let Err(ExecuteError::ReceiveFailed(ReceiveError::Timeout)) = &result {
920 log::warn!(
921 "Timed out during transfer of first chunk. Consider enabling CONFIG_IMG_ERASE_PROGRESSIVELY."
922 )
923 }
924
925 result?
926 } else {
927 let current_chunk_size = (size - offset).min(other_chunk_size_max);
928 let chunk_data = &data[offset..offset + current_chunk_size];
929
930 self.connection
931 .execute_command(&commands::image::ImageUpload {
932 image: None,
933 len: None,
934 off: offset as u64,
935 sha: None,
936 data: chunk_data,
937 upgrade: None,
938 })?
939 };
940
941 offset = upload_response
942 .off
943 .try_into()
944 .map_err(|_| MCUmgrClientError::UnexpectedOffset)?;
945
946 if offset > size {
947 return Err(MCUmgrClientError::UnexpectedOffset);
948 }
949
950 if let Some(progress) = &mut progress {
951 if !progress(offset as u64, size as u64) {
952 return Err(MCUmgrClientError::ProgressCallbackError);
953 };
954 }
955
956 if let Some(is_match) = upload_response.r#match {
957 checksum_matched = Some(is_match);
958 }
959 }
960
961 if let Some(checksum_matched) = checksum_matched {
962 if !checksum_matched {
963 return Err(MCUmgrClientError::ChecksumMismatchOnDevice);
964 }
965 } else {
966 log::warn!("Device did not perform image checksum verification");
967 }
968
969 Ok(())
970 }
971
972 pub fn image_erase(&self, slot: Option<u32>) -> Result<(), MCUmgrClientError> {
979 self.connection
980 .execute_command(&commands::image::ImageErase { slot })
981 .map(Into::into)
982 .map_err(Into::into)
983 }
984
985 pub fn image_slot_info(
987 &self,
988 ) -> Result<Vec<commands::image::SlotInfoImage>, MCUmgrClientError> {
989 self.connection
990 .execute_command(&commands::image::SlotInfo)
991 .map(|val| val.images)
992 .map_err(Into::into)
993 }
994
995 pub fn stats_get_group_data(
1002 &self,
1003 name: impl AsRef<str>,
1004 ) -> Result<HashMap<String, u64>, MCUmgrClientError> {
1005 self.connection
1006 .execute_command(&commands::stats::GroupData {
1007 name: name.as_ref(),
1008 })
1009 .map(|val| val.fields)
1010 .map_err(Into::into)
1011 }
1012
1013 pub fn stats_list_groups(&self) -> Result<Vec<String>, MCUmgrClientError> {
1015 self.connection
1016 .execute_command(&commands::stats::ListGroups)
1017 .map(|val| val.stat_list)
1018 .map_err(Into::into)
1019 }
1020
1021 pub fn settings_read(&self, name: impl AsRef<str>) -> Result<Vec<u8>, MCUmgrClientError> {
1034 let name = name.as_ref();
1035
1036 self.settings_read_ext(name, None).map(|val| val.val)
1037 }
1038
1039 pub fn settings_read_ext(
1049 &self,
1050 name: impl AsRef<str>,
1051 max_size: Option<u32>,
1052 ) -> Result<commands::settings::ReadSettingResponse, MCUmgrClientError> {
1053 let name = name.as_ref();
1054
1055 self.connection
1056 .execute_command(&commands::settings::ReadSetting { name, max_size })
1057 .map_err(Into::into)
1058 }
1059
1060 pub fn settings_write(
1068 &self,
1069 name: impl AsRef<str>,
1070 value: &[u8],
1071 ) -> Result<(), MCUmgrClientError> {
1072 let name = name.as_ref();
1073
1074 self.connection
1075 .execute_command(&commands::settings::WriteSetting { name, val: value })
1076 .map(Into::into)
1077 .map_err(Into::into)
1078 }
1079
1080 pub fn settings_delete(&self, name: impl AsRef<str>) -> Result<(), MCUmgrClientError> {
1087 let name = name.as_ref();
1088
1089 self.connection
1090 .execute_command(&commands::settings::DeleteSetting { name })
1091 .map(Into::into)
1092 .map_err(Into::into)
1093 }
1094
1095 pub fn settings_commit(&self) -> Result<(), MCUmgrClientError> {
1098 self.connection
1099 .execute_command(&commands::settings::CommitSettings)
1100 .map(Into::into)
1101 .map_err(Into::into)
1102 }
1103
1104 pub fn settings_load(&self) -> Result<(), MCUmgrClientError> {
1107 self.connection
1108 .execute_command(&commands::settings::LoadSettings)
1109 .map(Into::into)
1110 .map_err(Into::into)
1111 }
1112
1113 pub fn settings_save(&self, name: Option<impl AsRef<str>>) -> Result<(), MCUmgrClientError> {
1120 let name = name.as_ref().map(|val| val.as_ref());
1121
1122 self.connection
1123 .execute_command(&commands::settings::SaveSettings { name })
1124 .map(Into::into)
1125 .map_err(Into::into)
1126 }
1127
1128 pub fn fs_file_download<T: Write>(
1142 &self,
1143 name: impl AsRef<str>,
1144 mut writer: T,
1145 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1146 ) -> Result<(), MCUmgrClientError> {
1147 let name = name.as_ref();
1148 let response = self
1149 .connection
1150 .execute_command(&commands::fs::FileDownload { name, off: 0 })?;
1151
1152 let file_len = response.len.ok_or(MCUmgrClientError::MissingSize)?;
1153 if response.off != 0 {
1154 return Err(MCUmgrClientError::UnexpectedOffset);
1155 }
1156
1157 let mut offset = 0;
1158
1159 if let Some(progress) = &mut progress {
1160 if !progress(offset, file_len) {
1161 return Err(MCUmgrClientError::ProgressCallbackError);
1162 };
1163 }
1164
1165 writer
1166 .write_all(&response.data)
1167 .map_err(MCUmgrClientError::WriterError)?;
1168 offset += response.data.len() as u64;
1169
1170 if let Some(progress) = &mut progress {
1171 if !progress(offset, file_len) {
1172 return Err(MCUmgrClientError::ProgressCallbackError);
1173 };
1174 }
1175
1176 while offset < file_len {
1177 let response = self
1178 .connection
1179 .execute_command(&commands::fs::FileDownload { name, off: offset })?;
1180
1181 if response.off != offset {
1182 return Err(MCUmgrClientError::UnexpectedOffset);
1183 }
1184
1185 writer
1186 .write_all(&response.data)
1187 .map_err(MCUmgrClientError::WriterError)?;
1188 offset += response.data.len() as u64;
1189
1190 if let Some(progress) = &mut progress {
1191 if !progress(offset, file_len) {
1192 return Err(MCUmgrClientError::ProgressCallbackError);
1193 };
1194 }
1195 }
1196
1197 if offset != file_len {
1198 return Err(MCUmgrClientError::SizeMismatch);
1199 }
1200
1201 Ok(())
1202 }
1203
1204 pub fn fs_file_upload<T: Read>(
1220 &self,
1221 name: impl AsRef<str>,
1222 mut reader: T,
1223 size: u64,
1224 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
1225 ) -> Result<(), MCUmgrClientError> {
1226 let name = name.as_ref();
1227
1228 let chunk_size_max = file_upload_max_data_chunk_size(
1229 self.smp_frame_size
1230 .load(std::sync::atomic::Ordering::SeqCst),
1231 name,
1232 )
1233 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
1234 let mut data_buffer = vec![0u8; chunk_size_max].into_boxed_slice();
1235
1236 let mut offset = 0;
1237
1238 while offset < size {
1239 let current_chunk_size = (size - offset).min(data_buffer.len() as u64) as usize;
1240
1241 let chunk_buffer = &mut data_buffer[..current_chunk_size];
1242 reader
1243 .read_exact(chunk_buffer)
1244 .map_err(MCUmgrClientError::ReaderError)?;
1245
1246 self.connection.execute_command(&commands::fs::FileUpload {
1247 off: offset,
1248 data: chunk_buffer,
1249 name,
1250 len: if offset == 0 { Some(size) } else { None },
1251 })?;
1252
1253 offset += chunk_buffer.len() as u64;
1254
1255 if let Some(progress) = &mut progress {
1256 if !progress(offset, size) {
1257 return Err(MCUmgrClientError::ProgressCallbackError);
1258 };
1259 }
1260 }
1261
1262 Ok(())
1263 }
1264
1265 pub fn fs_file_status(
1267 &self,
1268 name: impl AsRef<str>,
1269 ) -> Result<commands::fs::FileStatusResponse, MCUmgrClientError> {
1270 self.connection
1271 .execute_command(&commands::fs::FileStatus {
1272 name: name.as_ref(),
1273 })
1274 .map_err(Into::into)
1275 }
1276
1277 pub fn fs_file_checksum(
1289 &self,
1290 name: impl AsRef<str>,
1291 algorithm: Option<impl AsRef<str>>,
1292 offset: u64,
1293 length: Option<u64>,
1294 ) -> Result<commands::fs::FileChecksumResponse, MCUmgrClientError> {
1295 self.connection
1296 .execute_command(&commands::fs::FileChecksum {
1297 name: name.as_ref(),
1298 r#type: algorithm.as_ref().map(AsRef::as_ref),
1299 off: offset,
1300 len: length,
1301 })
1302 .map_err(Into::into)
1303 }
1304
1305 pub fn fs_supported_checksum_types(
1307 &self,
1308 ) -> Result<HashMap<String, commands::fs::FileChecksumProperties>, MCUmgrClientError> {
1309 self.connection
1310 .execute_command(&commands::fs::SupportedFileChecksumTypes)
1311 .map(|val| val.types)
1312 .map_err(Into::into)
1313 }
1314
1315 pub fn fs_file_close(&self) -> Result<(), MCUmgrClientError> {
1317 self.connection
1318 .execute_command(&commands::fs::FileClose)
1319 .map(Into::into)
1320 .map_err(Into::into)
1321 }
1322
1323 pub fn shell_execute(
1335 &self,
1336 argv: &[String],
1337 use_retries: bool,
1338 ) -> Result<(i32, String), MCUmgrClientError> {
1339 let command = commands::shell::ShellCommandLineExecute { argv };
1340
1341 if use_retries {
1342 self.connection.execute_command(&command)
1343 } else {
1344 self.connection.execute_command_without_retries(&command)
1345 }
1346 .map(|ret| (ret.ret, ret.o))
1347 .map_err(Into::into)
1348 }
1349
1350 pub fn enum_get_group_count(&self) -> Result<u16, MCUmgrClientError> {
1357 self.connection
1358 .execute_command(&commands::r#enum::GroupCount)
1359 .map(|ret| ret.count)
1360 .map_err(Into::into)
1361 }
1362
1363 pub fn enum_get_group_ids(&self) -> Result<Vec<u16>, MCUmgrClientError> {
1378 self.connection
1379 .execute_command(&commands::r#enum::ListGroups)
1380 .map(|ret| ret.groups)
1381 .map_err(Into::into)
1382 }
1383
1384 pub fn enum_get_group_id(&self, index: u16) -> Result<u16, MCUmgrClientError> {
1396 self.connection
1397 .execute_command(&commands::r#enum::GroupId { index: Some(index) })
1398 .map(|ret| ret.group)
1399 .map_err(Into::into)
1400 }
1401
1402 pub fn enum_iter_group_ids(&self) -> impl Iterator<Item = Result<u16, MCUmgrClientError>> {
1408 let mut i = 0;
1409 let mut num_elements = None;
1410
1411 std::iter::from_fn(move || -> Option<Result<u16, MCUmgrClientError>> {
1412 let mut num_elements_err = None;
1413 let num_elements =
1414 *num_elements.get_or_insert_with(|| match self.enum_get_group_count() {
1415 Ok(n) => n,
1416 Err(e) => {
1417 num_elements_err = Some(e);
1418 0
1419 }
1420 });
1421 if let Some(err) = num_elements_err {
1422 return Some(Err(err));
1423 }
1424
1425 if i >= num_elements {
1426 None
1427 } else {
1428 Some(match self.enum_get_group_id(i) {
1429 Ok(group_id) => {
1430 i += 1;
1431 Ok(group_id)
1432 }
1433 Err(e) => {
1434 i = num_elements;
1435 Err(e)
1436 }
1437 })
1438 }
1439 })
1440 }
1441
1442 pub fn enum_get_group_details(
1453 &self,
1454 groups: Option<&[u16]>,
1455 ) -> Result<Vec<commands::r#enum::GroupDetailsEntry>, MCUmgrClientError> {
1456 self.connection
1457 .execute_command(&commands::r#enum::GroupDetails { groups })
1458 .map(|ret| ret.groups)
1459 .map_err(Into::into)
1460 }
1461
1462 pub fn zephyr_erase_storage(&self) -> Result<(), MCUmgrClientError> {
1464 self.connection
1465 .execute_command(&commands::zephyr::EraseStorage)
1466 .map(Into::into)
1467 .map_err(Into::into)
1468 }
1469
1470 pub fn raw_command<T: commands::McuMgrCommand>(
1476 &self,
1477 command: &T,
1478 ) -> Result<T::Response, MCUmgrClientError> {
1479 self.connection.execute_command(command).map_err(Into::into)
1480 }
1481}