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 sync::atomic::AtomicUsize,
12 time::Duration,
13};
14
15use miette::Diagnostic;
16use rand::distr::SampleString;
17use serde::Serialize;
18use sha2::{Digest, Sha256};
19use thiserror::Error;
20
21use crate::{
22 bootloader::BootloaderInfo,
23 commands::{
24 self, fs::file_upload_max_data_chunk_size, image::image_upload_max_data_chunk_size,
25 },
26 connection::{Connection, ExecuteError},
27 transport::{
28 ReceiveError,
29 serial::{ConfigurableTimeout, SerialTransport},
30 },
31};
32
33const ZEPHYR_DEFAULT_SMP_FRAME_SIZE: usize = 384;
37
38pub struct MCUmgrClient {
42 connection: Connection,
43 smp_frame_size: AtomicUsize,
44}
45
46#[derive(Error, Debug, Diagnostic)]
48pub enum MCUmgrClientError {
49 #[error("Command execution failed")]
51 #[diagnostic(code(mcumgr_toolkit::client::execute))]
52 ExecuteError(#[from] ExecuteError),
53 #[error("Received an unexpected offset value")]
55 #[diagnostic(code(mcumgr_toolkit::client::unexpected_offset))]
56 UnexpectedOffset,
57 #[error("Writer returned an error")]
59 #[diagnostic(code(mcumgr_toolkit::client::writer))]
60 WriterError(#[source] io::Error),
61 #[error("Reader returned an error")]
63 #[diagnostic(code(mcumgr_toolkit::client::reader))]
64 ReaderError(#[source] io::Error),
65 #[error("Received data does not match reported size")]
67 #[diagnostic(code(mcumgr_toolkit::client::size_mismatch))]
68 SizeMismatch,
69 #[error("Received data is missing file size information")]
71 #[diagnostic(code(mcumgr_toolkit::client::missing_size))]
72 MissingSize,
73 #[error("Progress callback returned an error")]
75 #[diagnostic(code(mcumgr_toolkit::client::progress_cb_error))]
76 ProgressCallbackError,
77 #[error("SMP frame size too small for this command")]
79 #[diagnostic(code(mcumgr_toolkit::client::framesize_too_small))]
80 FrameSizeTooSmall(#[source] io::Error),
81 #[error("Device reported checksum mismatch")]
83 #[diagnostic(code(mcumgr_toolkit::client::checksum_mismatch_on_device))]
84 ChecksumMismatchOnDevice,
85 #[error("Firmware image does not match given checksum")]
87 #[diagnostic(code(mcumgr_toolkit::client::checksum_mismatch))]
88 ChecksumMismatch,
89 #[error("Failed to set the device timeout")]
91 #[diagnostic(code(mcumgr_toolkit::client::set_timeout))]
92 SetTimeoutFailed(#[source] Box<dyn std::error::Error + Send + Sync>),
93}
94
95impl MCUmgrClientError {
96 pub fn command_not_supported(&self) -> bool {
98 if let Self::ExecuteError(err) = self {
99 err.command_not_supported()
100 } else {
101 false
102 }
103 }
104}
105
106#[derive(Debug, Serialize, Clone, Eq, PartialEq)]
108pub struct UsbSerialPortInfo {
109 pub identifier: String,
111 pub port_name: String,
113 pub port_info: serialport::UsbPortInfo,
115}
116
117#[derive(Serialize, Clone, Eq, PartialEq)]
121#[serde(transparent)]
122pub struct UsbSerialPorts(pub Vec<UsbSerialPortInfo>);
123impl std::fmt::Display for UsbSerialPorts {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 if self.0.is_empty() {
126 writeln!(f)?;
127 write!(f, " - None -")?;
128 return Ok(());
129 }
130
131 for UsbSerialPortInfo {
132 identifier,
133 port_name,
134 port_info,
135 } in &self.0
136 {
137 writeln!(f)?;
138 write!(f, " - {identifier}")?;
139
140 let mut print_port_string = true;
141 let port_string = format!("({port_name})");
142
143 if port_info.manufacturer.is_some() || port_info.product.is_some() {
144 write!(f, " -")?;
145 if let Some(manufacturer) = &port_info.manufacturer {
146 let mut print_manufacturer = true;
147
148 if let Some(product) = &port_info.product {
149 if product.starts_with(manufacturer) {
150 print_manufacturer = false;
151 }
152 }
153
154 if print_manufacturer {
155 write!(f, " {manufacturer}")?;
156 }
157 }
158 if let Some(product) = &port_info.product {
159 write!(f, " {product}")?;
160
161 if product.ends_with(&port_string) {
162 print_port_string = false;
163 }
164 }
165 }
166
167 if print_port_string {
168 write!(f, " {port_string}")?;
169 }
170 }
171 Ok(())
172 }
173}
174impl std::fmt::Debug for UsbSerialPorts {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 std::fmt::Debug::fmt(&self.0, f)
177 }
178}
179
180#[derive(Error, Debug, Diagnostic)]
182pub enum UsbSerialError {
183 #[error("Serialport returned an error")]
185 #[diagnostic(code(mcumgr_toolkit::usb_serial::serialport_error))]
186 SerialPortError(#[from] serialport::Error),
187 #[error("No serial port matched the identifier '{identifier}'\nAvailable ports:\n{available}")]
189 #[diagnostic(code(mcumgr_toolkit::usb_serial::no_matches))]
190 NoMatchingPort {
191 identifier: String,
193 available: UsbSerialPorts,
195 },
196 #[error("Multiple serial ports matched the identifier '{identifier}'\n{ports}")]
198 #[diagnostic(code(mcumgr_toolkit::usb_serial::multiple_matches))]
199 MultipleMatchingPorts {
200 identifier: String,
202 ports: UsbSerialPorts,
204 },
205 #[error("An empty identifier was provided")]
208 #[diagnostic(code(mcumgr_toolkit::usb_serial::empty_identifier))]
209 IdentifierEmpty {
210 ports: UsbSerialPorts,
212 },
213 #[error("The given identifier was not a valid RegEx")]
215 #[diagnostic(code(mcumgr_toolkit::usb_serial::regex_error))]
216 RegexError(#[from] regex::Error),
217}
218
219impl MCUmgrClient {
220 pub fn new_from_serial<T: Send + Read + Write + ConfigurableTimeout + 'static>(
233 serial: T,
234 ) -> Self {
235 Self {
236 connection: Connection::new(SerialTransport::new(serial)),
237 smp_frame_size: ZEPHYR_DEFAULT_SMP_FRAME_SIZE.into(),
238 }
239 }
240
241 pub fn new_from_usb_serial(
259 identifier: impl AsRef<str>,
260 baud_rate: u32,
261 timeout: Duration,
262 ) -> Result<Self, UsbSerialError> {
263 let identifier = identifier.as_ref();
264
265 let ports = serialport::available_ports()?
266 .into_iter()
267 .filter_map(|port| {
268 if let serialport::SerialPortType::UsbPort(port_info) = port.port_type {
269 if let Some(interface) = port_info.interface {
270 Some(UsbSerialPortInfo {
271 identifier: format!(
272 "{:04x}:{:04x}:{}",
273 port_info.vid, port_info.pid, interface
274 ),
275 port_name: port.port_name,
276 port_info,
277 })
278 } else {
279 Some(UsbSerialPortInfo {
280 identifier: format!("{:04x}:{:04x}", port_info.vid, port_info.pid),
281 port_name: port.port_name,
282 port_info,
283 })
284 }
285 } else {
286 None
287 }
288 })
289 .collect::<Vec<_>>();
290
291 if identifier.is_empty() {
292 return Err(UsbSerialError::IdentifierEmpty {
293 ports: UsbSerialPorts(ports),
294 });
295 }
296
297 let port_regex = regex::RegexBuilder::new(identifier)
298 .case_insensitive(true)
299 .unicode(true)
300 .build()?;
301
302 let matches = ports
303 .iter()
304 .filter(|port| {
305 if let Some(m) = port_regex.find(&port.identifier) {
306 m.start() == 0
308 } else {
309 false
310 }
311 })
312 .cloned()
313 .collect::<Vec<_>>();
314
315 if matches.len() > 1 {
316 return Err(UsbSerialError::MultipleMatchingPorts {
317 identifier: identifier.to_string(),
318 ports: UsbSerialPorts(matches),
319 });
320 }
321
322 let port_name = match matches.into_iter().next() {
323 Some(port) => port.port_name,
324 None => {
325 return Err(UsbSerialError::NoMatchingPort {
326 identifier: identifier.to_string(),
327 available: UsbSerialPorts(ports),
328 });
329 }
330 };
331
332 let serial = serialport::new(port_name, baud_rate)
333 .timeout(timeout)
334 .open()?;
335
336 Ok(Self::new_from_serial(serial))
337 }
338
339 pub fn set_frame_size(&self, smp_frame_size: usize) {
344 self.smp_frame_size
345 .store(smp_frame_size, std::sync::atomic::Ordering::SeqCst);
346 }
347
348 pub fn use_auto_frame_size(&self) -> Result<(), MCUmgrClientError> {
352 let mcumgr_params = self
353 .connection
354 .execute_command(&commands::os::MCUmgrParameters)?;
355
356 log::debug!("Using frame size {}.", mcumgr_params.buf_size);
357
358 self.smp_frame_size.store(
359 mcumgr_params.buf_size as usize,
360 std::sync::atomic::Ordering::SeqCst,
361 );
362
363 Ok(())
364 }
365
366 pub fn set_timeout(&self, timeout: Duration) -> Result<(), MCUmgrClientError> {
371 self.connection
372 .set_timeout(timeout)
373 .map_err(MCUmgrClientError::SetTimeoutFailed)
374 }
375
376 pub fn set_retries(&self, retries: u8) {
381 self.connection.set_retries(retries)
382 }
383
384 pub fn check_connection(&self) -> Result<(), MCUmgrClientError> {
392 let random_message = rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 16);
393 let response = self.os_echo(&random_message)?;
394 if random_message == response {
395 Ok(())
396 } else {
397 Err(
398 ExecuteError::ReceiveFailed(crate::transport::ReceiveError::UnexpectedResponse)
399 .into(),
400 )
401 }
402 }
403
404 pub fn firmware_update(
414 &self,
415 firmware: impl AsRef<[u8]>,
416 checksum: Option<[u8; 32]>,
417 params: FirmwareUpdateParams,
418 progress: Option<&mut FirmwareUpdateProgressCallback>,
419 ) -> Result<(), FirmwareUpdateError> {
420 firmware_update::firmware_update(self, firmware, checksum, params, progress)
421 }
422
423 pub fn os_echo(&self, msg: impl AsRef<str>) -> Result<String, MCUmgrClientError> {
427 self.connection
428 .execute_command(&commands::os::Echo { d: msg.as_ref() })
429 .map(|resp| resp.r)
430 .map_err(Into::into)
431 }
432
433 pub fn os_task_statistics(
444 &self,
445 ) -> Result<HashMap<String, commands::os::TaskStatisticsEntry>, MCUmgrClientError> {
446 self.connection
447 .execute_command(&commands::os::TaskStatistics)
448 .map(|resp| {
449 let mut tasks = resp.tasks;
450 for (_, stats) in tasks.iter_mut() {
451 stats.stkuse = stats.stkuse.map(|val| val * 4);
452 stats.stksiz = stats.stksiz.map(|val| val * 4);
453 }
454 tasks
455 })
456 .map_err(Into::into)
457 }
458
459 pub fn os_set_datetime(
461 &self,
462 datetime: chrono::NaiveDateTime,
463 ) -> Result<(), MCUmgrClientError> {
464 self.connection
465 .execute_command(&commands::os::DateTimeSet { datetime })
466 .map(Into::into)
467 .map_err(Into::into)
468 }
469
470 pub fn os_get_datetime(&self) -> Result<chrono::NaiveDateTime, MCUmgrClientError> {
472 self.connection
473 .execute_command(&commands::os::DateTimeGet)
474 .map(|val| val.datetime)
475 .map_err(Into::into)
476 }
477
478 pub fn os_system_reset(
492 &self,
493 force: bool,
494 boot_mode: Option<u8>,
495 ) -> Result<(), MCUmgrClientError> {
496 self.connection
497 .execute_command(&commands::os::SystemReset { force, boot_mode })
498 .map(Into::into)
499 .map_err(Into::into)
500 }
501
502 pub fn os_mcumgr_parameters(
504 &self,
505 ) -> Result<commands::os::MCUmgrParametersResponse, MCUmgrClientError> {
506 self.connection
507 .execute_command(&commands::os::MCUmgrParameters)
508 .map_err(Into::into)
509 }
510
511 pub fn os_application_info(&self, format: Option<&str>) -> Result<String, MCUmgrClientError> {
523 self.connection
524 .execute_command(&commands::os::ApplicationInfo { format })
525 .map(|resp| resp.output)
526 .map_err(Into::into)
527 }
528
529 pub fn os_bootloader_info(&self) -> Result<BootloaderInfo, MCUmgrClientError> {
531 Ok(
532 match self
533 .connection
534 .execute_command(&commands::os::BootloaderInfo)?
535 .bootloader
536 .as_str()
537 {
538 "MCUboot" => {
539 let mode_data = self
540 .connection
541 .execute_command(&commands::os::BootloaderInfoMcubootMode {})?;
542 BootloaderInfo::MCUboot {
543 mode: mode_data.mode,
544 no_downgrade: mode_data.no_downgrade,
545 }
546 }
547 name => BootloaderInfo::Unknown {
548 name: name.to_string(),
549 },
550 },
551 )
552 }
553
554 pub fn image_get_state(&self) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
556 self.connection
557 .execute_command(&commands::image::GetImageState)
558 .map(|val| val.images)
559 .map_err(Into::into)
560 }
561
562 pub fn image_set_state(
578 &self,
579 hash: Option<&[u8]>,
580 confirm: bool,
581 ) -> Result<Vec<commands::image::ImageState>, MCUmgrClientError> {
582 self.connection
583 .execute_command(&commands::image::SetImageState { hash, confirm })
584 .map(|val| val.images)
585 .map_err(Into::into)
586 }
587
588 pub fn image_upload(
606 &self,
607 data: impl AsRef<[u8]>,
608 image: Option<u32>,
609 checksum: Option<[u8; 32]>,
610 upgrade_only: bool,
611 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
612 ) -> Result<(), MCUmgrClientError> {
613 let chunk_size_max = image_upload_max_data_chunk_size(
614 self.smp_frame_size
615 .load(std::sync::atomic::Ordering::SeqCst),
616 )
617 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
618
619 let data = data.as_ref();
620
621 let actual_checksum: [u8; 32] = Sha256::digest(data).into();
622 if let Some(checksum) = checksum {
623 if actual_checksum != checksum {
624 return Err(MCUmgrClientError::ChecksumMismatch);
625 }
626 }
627
628 let mut offset = 0;
629 let size = data.len();
630
631 let mut checksum_matched = None;
632
633 while offset < size {
634 let current_chunk_size = (size - offset).min(chunk_size_max);
635 let chunk_data = &data[offset..offset + current_chunk_size];
636
637 let upload_response = if offset == 0 {
638 let result = self
639 .connection
640 .execute_command(&commands::image::ImageUpload {
641 image,
642 len: Some(size as u64),
643 off: offset as u64,
644 sha: Some(&actual_checksum),
645 data: chunk_data,
646 upgrade: Some(upgrade_only),
647 });
648
649 if let Err(ExecuteError::ReceiveFailed(ReceiveError::TransportError(e))) = &result {
650 if let io::ErrorKind::TimedOut = e.kind() {
651 log::warn!(
652 "Timed out during transfer of first chunk. Consider enabling CONFIG_IMG_ERASE_PROGRESSIVELY."
653 )
654 }
655 }
656
657 result?
658 } else {
659 self.connection
660 .execute_command(&commands::image::ImageUpload {
661 image: None,
662 len: None,
663 off: offset as u64,
664 sha: None,
665 data: chunk_data,
666 upgrade: None,
667 })?
668 };
669
670 offset = upload_response
671 .off
672 .try_into()
673 .map_err(|_| MCUmgrClientError::UnexpectedOffset)?;
674
675 if offset > size {
676 return Err(MCUmgrClientError::UnexpectedOffset);
677 }
678
679 if let Some(progress) = &mut progress {
680 if !progress(offset as u64, size as u64) {
681 return Err(MCUmgrClientError::ProgressCallbackError);
682 };
683 }
684
685 if let Some(is_match) = upload_response.r#match {
686 checksum_matched = Some(is_match);
687 }
688 }
689
690 if let Some(checksum_matched) = checksum_matched {
691 if !checksum_matched {
692 return Err(MCUmgrClientError::ChecksumMismatchOnDevice);
693 }
694 } else {
695 log::warn!("Device did not perform image checksum verification");
696 }
697
698 Ok(())
699 }
700
701 pub fn image_erase(&self, slot: Option<u32>) -> Result<(), MCUmgrClientError> {
708 self.connection
709 .execute_command(&commands::image::ImageErase { slot })
710 .map(Into::into)
711 .map_err(Into::into)
712 }
713
714 pub fn image_slot_info(
716 &self,
717 ) -> Result<Vec<commands::image::SlotInfoImage>, MCUmgrClientError> {
718 self.connection
719 .execute_command(&commands::image::SlotInfo)
720 .map(|val| val.images)
721 .map_err(Into::into)
722 }
723
724 pub fn stats_get_group_data(
731 &self,
732 name: impl AsRef<str>,
733 ) -> Result<HashMap<String, u64>, MCUmgrClientError> {
734 self.connection
735 .execute_command(&commands::stats::GroupData {
736 name: name.as_ref(),
737 })
738 .map(|val| val.fields)
739 .map_err(Into::into)
740 }
741
742 pub fn stats_list_groups(&self) -> Result<Vec<String>, MCUmgrClientError> {
744 self.connection
745 .execute_command(&commands::stats::ListGroups)
746 .map(|val| val.stat_list)
747 .map_err(Into::into)
748 }
749
750 pub fn fs_file_download<T: Write>(
764 &self,
765 name: impl AsRef<str>,
766 mut writer: T,
767 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
768 ) -> Result<(), MCUmgrClientError> {
769 let name = name.as_ref();
770 let response = self
771 .connection
772 .execute_command(&commands::fs::FileDownload { name, off: 0 })?;
773
774 let file_len = response.len.ok_or(MCUmgrClientError::MissingSize)?;
775 if response.off != 0 {
776 return Err(MCUmgrClientError::UnexpectedOffset);
777 }
778
779 let mut offset = 0;
780
781 if let Some(progress) = &mut progress {
782 if !progress(offset, file_len) {
783 return Err(MCUmgrClientError::ProgressCallbackError);
784 };
785 }
786
787 writer
788 .write_all(&response.data)
789 .map_err(MCUmgrClientError::WriterError)?;
790 offset += response.data.len() as u64;
791
792 if let Some(progress) = &mut progress {
793 if !progress(offset, file_len) {
794 return Err(MCUmgrClientError::ProgressCallbackError);
795 };
796 }
797
798 while offset < file_len {
799 let response = self
800 .connection
801 .execute_command(&commands::fs::FileDownload { name, off: offset })?;
802
803 if response.off != offset {
804 return Err(MCUmgrClientError::UnexpectedOffset);
805 }
806
807 writer
808 .write_all(&response.data)
809 .map_err(MCUmgrClientError::WriterError)?;
810 offset += response.data.len() as u64;
811
812 if let Some(progress) = &mut progress {
813 if !progress(offset, file_len) {
814 return Err(MCUmgrClientError::ProgressCallbackError);
815 };
816 }
817 }
818
819 if offset != file_len {
820 return Err(MCUmgrClientError::SizeMismatch);
821 }
822
823 Ok(())
824 }
825
826 pub fn fs_file_upload<T: Read>(
842 &self,
843 name: impl AsRef<str>,
844 mut reader: T,
845 size: u64,
846 mut progress: Option<&mut dyn FnMut(u64, u64) -> bool>,
847 ) -> Result<(), MCUmgrClientError> {
848 let name = name.as_ref();
849
850 let chunk_size_max = file_upload_max_data_chunk_size(
851 self.smp_frame_size
852 .load(std::sync::atomic::Ordering::SeqCst),
853 name,
854 )
855 .map_err(MCUmgrClientError::FrameSizeTooSmall)?;
856 let mut data_buffer = vec![0u8; chunk_size_max].into_boxed_slice();
857
858 let mut offset = 0;
859
860 while offset < size {
861 let current_chunk_size = (size - offset).min(data_buffer.len() as u64) as usize;
862
863 let chunk_buffer = &mut data_buffer[..current_chunk_size];
864 reader
865 .read_exact(chunk_buffer)
866 .map_err(MCUmgrClientError::ReaderError)?;
867
868 self.connection.execute_command(&commands::fs::FileUpload {
869 off: offset,
870 data: chunk_buffer,
871 name,
872 len: if offset == 0 { Some(size) } else { None },
873 })?;
874
875 offset += chunk_buffer.len() as u64;
876
877 if let Some(progress) = &mut progress {
878 if !progress(offset, size) {
879 return Err(MCUmgrClientError::ProgressCallbackError);
880 };
881 }
882 }
883
884 Ok(())
885 }
886
887 pub fn fs_file_status(
889 &self,
890 name: impl AsRef<str>,
891 ) -> Result<commands::fs::FileStatusResponse, MCUmgrClientError> {
892 self.connection
893 .execute_command(&commands::fs::FileStatus {
894 name: name.as_ref(),
895 })
896 .map_err(Into::into)
897 }
898
899 pub fn fs_file_checksum(
911 &self,
912 name: impl AsRef<str>,
913 algorithm: Option<impl AsRef<str>>,
914 offset: u64,
915 length: Option<u64>,
916 ) -> Result<commands::fs::FileChecksumResponse, MCUmgrClientError> {
917 self.connection
918 .execute_command(&commands::fs::FileChecksum {
919 name: name.as_ref(),
920 r#type: algorithm.as_ref().map(AsRef::as_ref),
921 off: offset,
922 len: length,
923 })
924 .map_err(Into::into)
925 }
926
927 pub fn fs_supported_checksum_types(
929 &self,
930 ) -> Result<HashMap<String, commands::fs::FileChecksumProperties>, MCUmgrClientError> {
931 self.connection
932 .execute_command(&commands::fs::SupportedFileChecksumTypes)
933 .map(|val| val.types)
934 .map_err(Into::into)
935 }
936
937 pub fn fs_file_close(&self) -> Result<(), MCUmgrClientError> {
939 self.connection
940 .execute_command(&commands::fs::FileClose)
941 .map(Into::into)
942 .map_err(Into::into)
943 }
944
945 pub fn shell_execute(
957 &self,
958 argv: &[String],
959 use_retries: bool,
960 ) -> Result<(i32, String), MCUmgrClientError> {
961 let command = commands::shell::ShellCommandLineExecute { argv };
962
963 if use_retries {
964 self.connection.execute_command(&command)
965 } else {
966 self.connection.execute_command_without_retries(&command)
967 }
968 .map(|ret| (ret.ret, ret.o))
969 .map_err(Into::into)
970 }
971
972 pub fn enum_get_group_count(&self) -> Result<u16, MCUmgrClientError> {
979 self.connection
980 .execute_command(&commands::r#enum::GroupCount)
981 .map(|ret| ret.count)
982 .map_err(Into::into)
983 }
984
985 pub fn enum_get_group_ids(&self) -> Result<Vec<u16>, MCUmgrClientError> {
1000 self.connection
1001 .execute_command(&commands::r#enum::ListGroups)
1002 .map(|ret| ret.groups)
1003 .map_err(Into::into)
1004 }
1005
1006 pub fn enum_get_group_id(&self, index: u16) -> Result<u16, MCUmgrClientError> {
1018 self.connection
1019 .execute_command(&commands::r#enum::GroupId { index: Some(index) })
1020 .map(|ret| ret.group)
1021 .map_err(Into::into)
1022 }
1023
1024 pub fn enum_iter_group_ids(&self) -> impl Iterator<Item = Result<u16, MCUmgrClientError>> {
1030 let mut i = 0;
1031 let mut num_elements = None;
1032
1033 std::iter::from_fn(move || -> Option<Result<u16, MCUmgrClientError>> {
1034 let mut num_elements_err = None;
1035 let num_elements =
1036 *num_elements.get_or_insert_with(|| match self.enum_get_group_count() {
1037 Ok(n) => n,
1038 Err(e) => {
1039 num_elements_err = Some(e);
1040 0
1041 }
1042 });
1043 if let Some(err) = num_elements_err {
1044 return Some(Err(err));
1045 }
1046
1047 if i >= num_elements {
1048 None
1049 } else {
1050 Some(match self.enum_get_group_id(i) {
1051 Ok(group_id) => {
1052 i += 1;
1053 Ok(group_id)
1054 }
1055 Err(e) => {
1056 i = num_elements;
1057 Err(e)
1058 }
1059 })
1060 }
1061 })
1062 }
1063
1064 pub fn enum_get_group_details(
1075 &self,
1076 groups: Option<&[u16]>,
1077 ) -> Result<Vec<commands::r#enum::GroupDetailsEntry>, MCUmgrClientError> {
1078 self.connection
1079 .execute_command(&commands::r#enum::GroupDetails { groups })
1080 .map(|ret| ret.groups)
1081 .map_err(Into::into)
1082 }
1083
1084 pub fn zephyr_erase_storage(&self) -> Result<(), MCUmgrClientError> {
1086 self.connection
1087 .execute_command(&commands::zephyr::EraseStorage)
1088 .map(Into::into)
1089 .map_err(Into::into)
1090 }
1091
1092 pub fn raw_command<T: commands::McuMgrCommand>(
1098 &self,
1099 command: &T,
1100 ) -> Result<T::Response, MCUmgrClientError> {
1101 self.connection.execute_command(command).map_err(Into::into)
1102 }
1103}