1use core::fmt::{Debug, Display};
21
22use crate::dm::clusters::net_comm::{
23 self, NetCtlError, NetworkCommissioningStatusEnum, NetworkType, Networks, NetworksError,
24 ThreadCapabilitiesBitmap, WirelessCreds,
25};
26use crate::dm::clusters::{thread_diag, wifi_diag};
27use crate::error::{Error, ErrorCode};
28use crate::fmt::Bytes;
29use crate::persist::{KvBlobStore, NETWORKS_KEY};
30use crate::tlv::{FromTLV, TLVElement, TLVTag, TLVWrite, ToTLV};
31use crate::transport::network::btp::Btp;
32use crate::utils::cell::RefCell;
33use crate::utils::init::{init, Init};
34use crate::utils::storage::{Vec, WriteBuf};
35use crate::utils::sync::blocking;
36use crate::utils::sync::DynBase;
37
38use super::NetChangeNotif;
39
40pub use mgr::*;
41pub use thread::*;
42pub use wifi::*;
43
44mod mgr;
45mod thread;
46mod wifi;
47
48pub const MAX_WIRELESS_NETWORK_ID_LEN: usize = 32;
51
52pub type OwnedWirelessNetworkId = Vec<u8, MAX_WIRELESS_NETWORK_ID_LEN>;
58
59pub trait WirelessNetwork: Send + for<'a> FromTLV<'a> + ToTLV {
63 fn id(&self) -> &[u8];
68
69 fn init_from<'a>(creds: &'a WirelessCreds<'a>) -> impl Init<Self, Error> + 'a;
74
75 fn update(&mut self, creds: &WirelessCreds<'_>) -> Result<(), Error>;
80
81 fn creds(&self) -> WirelessCreds<'_>;
83
84 #[cfg(not(feature = "defmt"))]
86 fn display(&self) -> impl Display {
87 Self::display_id(self.id())
88 }
89
90 #[cfg(feature = "defmt")]
92 fn display(&self) -> impl Display + defmt::Format {
93 Self::display_id(self.id())
94 }
95
96 #[cfg(not(feature = "defmt"))]
98 fn display_id(id: &[u8]) -> impl Display;
99
100 #[cfg(feature = "defmt")]
102 fn display_id(id: &[u8]) -> impl Display + defmt::Format;
103}
104
105#[derive(Clone, Debug)]
107#[cfg_attr(feature = "defmt", derive(defmt::Format))]
108pub struct WirelessNetworks<const N: usize, T> {
109 networks: crate::utils::storage::Vec<T, N>,
110 commissioned: bool,
111}
112
113impl<const N: usize, T> Default for WirelessNetworks<N, T>
114where
115 T: WirelessNetwork,
116{
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl<const N: usize, T> WirelessNetworks<N, T>
123where
124 T: WirelessNetwork,
125{
126 pub const fn new() -> Self {
127 Self {
128 networks: crate::utils::storage::Vec::new(),
129 commissioned: false,
130 }
131 }
132
133 pub fn init() -> impl Init<Self> {
134 init!(Self {
135 networks <- crate::utils::storage::Vec::init(),
136 commissioned: false,
137 })
138 }
139
140 pub fn reset(&mut self) {
142 self.networks.clear();
143 self.commissioned = false;
144 }
145
146 pub async fn reset_persist<S: KvBlobStore>(
152 &mut self,
153 mut kv: S,
154 buf: &mut [u8],
155 ) -> Result<(), Error> {
156 self.reset();
157
158 kv.remove(NETWORKS_KEY, buf)?;
159
160 info!("Removed all wireless networks from storage");
161
162 Ok(())
163 }
164
165 pub async fn load_persist<S: KvBlobStore>(
171 &mut self,
172 mut kv: S,
173 buf: &mut [u8],
174 ) -> Result<(), Error> {
175 self.reset();
176
177 if let Some(data) = kv.load(NETWORKS_KEY, buf)? {
178 self.load(data)?;
179
180 info!(
181 "Loaded {} wireless networks from storage",
182 self.networks.len()
183 );
184 }
185
186 Ok(())
187 }
188
189 pub fn load(&mut self, data: &[u8]) -> Result<(), Error> {
194 let root = TLVElement::new(data);
195
196 self.networks.clear();
197
198 if let Ok(structure) = root.structure() {
201 for network in structure.ctx(0)?.array()?.iter() {
202 let network = network?;
203
204 self.networks.push_init(T::init_from_tlv(network), || {
205 ErrorCode::ResourceExhausted.into()
206 })?;
207 }
208
209 self.commissioned = structure.ctx(1)?.bool()?;
210 } else {
211 for network in root.array()?.iter() {
212 let network = network?;
213
214 self.networks.push_init(T::init_from_tlv(network), || {
215 ErrorCode::ResourceExhausted.into()
216 })?;
217 }
218
219 self.commissioned = false;
220 }
221
222 Ok(())
223 }
224
225 pub fn store(&self, buf: &mut [u8]) -> Result<usize, Error> {
232 let mut wb = WriteBuf::new(buf);
233
234 wb.start_struct(&TLVTag::Anonymous)?;
235
236 self.networks.to_tlv(&TLVTag::Context(0), &mut wb)?;
237 self.commissioned.to_tlv(&TLVTag::Context(1), &mut wb)?;
238
239 wb.end_container()?;
240
241 let tail = wb.get_tail();
242
243 Ok(tail)
244 }
245
246 pub fn networks<F>(&self, mut f: F) -> Result<(), Error>
251 where
252 F: FnMut(&T) -> Result<(), Error>,
253 {
254 for network in self.networks.iter() {
255 f(network)?;
256 }
257
258 Ok(())
259 }
260
261 pub fn network<F>(&self, network_id: &[u8], f: F) -> Result<u8, NetworksError>
269 where
270 F: FnOnce(&T) -> Result<(), Error>,
271 {
272 let networks = self
273 .networks
274 .iter()
275 .enumerate()
276 .find(|(_, network)| network.id() == network_id);
277
278 if let Some((index, network)) = networks {
279 f(network)?;
280
281 Ok(index as _)
282 } else {
283 Err(NetworksError::NetworkIdNotFound)
284 }
285 }
286
287 pub fn next_network<F>(&self, last_network_id: Option<&[u8]>, f: F) -> Result<bool, Error>
293 where
294 F: FnOnce(&T) -> Result<(), Error>,
295 {
296 if let Some(last_network_id) = last_network_id {
297 info!(
298 "Looking for network after the one with ID: {}",
299 T::display_id(last_network_id)
300 );
301
302 let mut networks = self.networks.iter();
305
306 for network in &mut networks {
307 if network.id() == last_network_id {
308 break;
309 }
310 }
311
312 let network = networks.next();
313 if let Some(network) = network {
314 info!("Trying with next network - ID: {}", network.display());
315
316 f(network)?;
317 return Ok(true);
318 }
319 }
320
321 info!("Wrapping over");
323
324 if let Some(network) = self.networks.first() {
325 info!("Trying with first network - ID: {}", network.display());
326
327 f(network)?;
328 Ok(true)
329 } else {
330 info!("No networks available");
331 Ok(false)
332 }
333 }
334
335 pub fn add_or_update<A, U>(
344 &mut self,
345 network_id: &[u8],
346 add: A,
347 update: U,
348 ) -> Result<u8, NetworksError>
349 where
350 A: Init<T, Error>,
351 U: FnOnce(&mut T) -> Result<(), Error>,
352 {
353 let unetwork = self
354 .networks
355 .iter_mut()
356 .enumerate()
357 .find(|(_, unetwork)| unetwork.id() == network_id);
358
359 if let Some((index, unetwork)) = unetwork {
360 update(unetwork)?;
362
363 info!("Updated network with ID {}", unetwork.display());
364
365 Ok(index as _)
366 } else if self.networks.len() >= N {
367 warn!(
368 "Adding network with ID {} failed: too many",
369 T::display_id(network_id)
370 );
371
372 Err(NetworksError::BoundsExceeded)
373 } else {
374 self.networks
376 .push_init(add, || ErrorCode::ResourceExhausted.into())?;
377
378 info!("Added network with ID {}", T::display_id(network_id));
379
380 Ok((self.networks.len() - 1) as _)
381 }
382 }
383
384 pub fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
393 let cur_index = self
394 .networks
395 .iter()
396 .position(|conf| conf.id() == network_id);
397
398 if let Some(cur_index) = cur_index {
399 if index < self.networks.len() as u8 {
402 let conf = self.networks.remove(cur_index);
403 unwrap!(self.networks.insert(index as usize, conf).map_err(|_| ()));
404
405 info!(
406 "Network with ID {} reordered to index {}",
407 T::display_id(network_id),
408 index
409 );
410 } else {
411 warn!(
412 "Reordering network with ID {} to index {} failed: out of range",
413 T::display_id(network_id),
414 index
415 );
416
417 Err(NetworksError::OutOfRange)?;
418 }
419 } else {
420 warn!("Network with ID {} not found", T::display_id(network_id));
421 Err(NetworksError::NetworkIdNotFound)?;
422 }
423
424 Ok(index)
425 }
426
427 pub fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
434 let index = self
435 .networks
436 .iter()
437 .position(|conf| conf.id() == network_id);
438
439 if let Some(index) = index {
440 self.networks.remove(index);
442
443 info!("Removed network with ID {}", T::display_id(network_id));
444
445 Ok(index as _)
446 } else {
447 warn!("Network with ID {} not found", T::display_id(network_id));
448
449 Err(NetworksError::NetworkIdNotFound)
450 }
451 }
452
453 pub fn commissioned(&self) -> bool {
454 self.commissioned
455 }
456
457 pub fn set_commissioned(&mut self, commissioned: bool) {
458 self.commissioned = commissioned;
459 }
460}
461
462impl<const N: usize, T> Networks for WirelessNetworks<N, T>
463where
464 T: WirelessNetwork,
465{
466 fn max_networks(&self) -> Result<u8, Error> {
467 Ok(N as _)
468 }
469
470 fn networks(
471 &self,
472 f: &mut dyn FnMut(&net_comm::NetworkInfo) -> Result<(), Error>,
473 ) -> Result<(), Error> {
474 WirelessNetworks::networks(self, |network| {
475 let network_id = network.id();
476
477 let network_info = net_comm::NetworkInfo {
478 network_id,
479 connected: false, };
481
482 f(&network_info)
483 })
484 }
485
486 fn creds(
487 &self,
488 network_id: &[u8],
489 f: &mut dyn FnMut(&net_comm::WirelessCreds) -> Result<(), Error>,
490 ) -> Result<u8, NetworksError> {
491 WirelessNetworks::network(self, network_id, |network| f(&network.creds()))
492 }
493
494 fn next_creds(
495 &self,
496 last_network_id: Option<&[u8]>,
497 f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
498 ) -> Result<bool, Error> {
499 WirelessNetworks::next_network(self, last_network_id, |network| f(&network.creds()))
500 }
501
502 fn enabled(&self) -> Result<bool, Error> {
503 Ok(true)
504 }
505
506 fn set_enabled(&mut self, _enabled: bool) -> Result<(), Error> {
507 Ok(())
508 }
509
510 fn add_or_update(
511 &mut self,
512 creds: &net_comm::WirelessCreds<'_>,
513 ) -> Result<u8, net_comm::NetworksError> {
514 WirelessNetworks::add_or_update(self, creds.id()?, T::init_from(creds), |network| {
515 network.update(creds)
516 })
517 }
518
519 fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
520 WirelessNetworks::reorder(self, index, network_id)
521 }
522
523 fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
524 WirelessNetworks::remove(self, network_id)
525 }
526
527 fn commissioned(&self) -> Result<bool, Error> {
528 Ok(self.commissioned())
529 }
530
531 fn set_commissioned(&mut self, commissioned: bool) -> Result<(), Error> {
532 WirelessNetworks::set_commissioned(self, commissioned);
533
534 Ok(())
535 }
536
537 fn reset(&mut self) -> Result<(), Error> {
538 WirelessNetworks::reset(self);
539
540 Ok(())
541 }
542
543 fn load(&mut self, data: &[u8]) -> Result<(), Error> {
544 WirelessNetworks::load(self, data)
545 }
546
547 fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
548 WirelessNetworks::store(self, buf).map(Some)
549 }
550}
551
552#[derive(Debug)]
554enum DisplayId<'a> {
555 Wifi(&'a [u8]),
556 Thread(&'a [u8]),
557}
558
559impl Display for DisplayId<'_> {
560 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
561 match self {
562 DisplayId::Wifi(id) => {
563 if let Ok(str) = core::str::from_utf8(id) {
564 write!(f, "Wifi SSID({})", str)
565 } else {
566 write!(f, "Wifi SSID({:?})", Bytes(id))
567 }
568 }
569 DisplayId::Thread(id) => write!(f, "Thread ExtPanID({:?})", Bytes(id)),
570 }
571 }
572}
573
574#[cfg(feature = "defmt")]
575impl defmt::Format for DisplayId<'_> {
576 fn format(&self, fmt: defmt::Formatter) {
577 match self {
578 DisplayId::Wifi(id) => {
579 if let Ok(str) = core::str::from_utf8(id) {
580 defmt::write!(fmt, "Wifi SSID({})", str)
581 } else {
582 defmt::write!(fmt, "Wifi SSID({:?})", Bytes(id))
583 }
584 }
585 DisplayId::Thread(id) => defmt::write!(fmt, "Thread ExtPanID({:?})", Bytes(id)),
586 }
587 }
588}
589
590pub struct NoopWirelessNetCtl(NetworkType);
595
596impl NoopWirelessNetCtl {
597 pub const fn new(net_type: NetworkType) -> Self {
604 Self(net_type)
605 }
606}
607
608impl net_comm::NetCtl for NoopWirelessNetCtl {
609 fn net_type(&self) -> NetworkType {
610 self.0
611 }
612
613 async fn scan<F>(&self, _network: Option<&[u8]>, _f: F) -> Result<(), NetCtlError>
614 where
615 F: FnOnce(&net_comm::NetworkScanInfo) -> Result<(), Error>,
616 {
617 Err(NetCtlError::Other(ErrorCode::InvalidAction.into()))
618 }
619
620 async fn connect(&self, creds: &WirelessCreds<'_>) -> Result<(), NetCtlError> {
621 Ok(creds.check_match(self.0)?)
622 }
623}
624
625impl NetChangeNotif for NoopWirelessNetCtl {
626 async fn wait_changed(&self) {
627 core::future::pending().await
628 }
629}
630
631impl DynBase for NoopWirelessNetCtl {}
632
633impl wifi_diag::WirelessDiag for NoopWirelessNetCtl {}
634
635impl wifi_diag::WifiDiag for NoopWirelessNetCtl {}
636
637impl thread_diag::ThreadDiag for NoopWirelessNetCtl {}
638
639pub struct NetCtlState {
641 pub network_id: OwnedWirelessNetworkId,
643 pub networking_status: Option<NetworkCommissioningStatusEnum>,
645 pub connect_error_value: Option<i32>,
648}
649
650impl NetCtlState {
651 pub const fn new() -> Self {
653 Self {
654 network_id: OwnedWirelessNetworkId::new(),
655 networking_status: None,
656 connect_error_value: None,
657 }
658 }
659
660 pub fn init() -> impl Init<Self> {
662 init!(Self {
663 network_id <- OwnedWirelessNetworkId::init(),
664 networking_status: None,
665 connect_error_value: None,
666 })
667 }
668
669 pub const fn new_with_mutex() -> NetCtlStateMutex {
671 blocking::Mutex::new(RefCell::new(Self::new()))
672 }
673
674 pub fn init_with_mutex() -> impl Init<NetCtlStateMutex> {
676 blocking::Mutex::init(RefCell::init(init!(Self {
677 network_id <- OwnedWirelessNetworkId::init(),
678 networking_status: None,
679 connect_error_value: None,
680 })))
681 }
682
683 pub fn is_prov_ready(&self) -> bool {
685 !self.network_id.is_empty()
686 && matches!(
687 self.networking_status,
688 Some(NetworkCommissioningStatusEnum::Success)
689 )
690 && self.connect_error_value.is_none()
691 }
692
693 pub fn update<R>(
697 &mut self,
698 network_id: Option<&[u8]>,
699 result: Result<R, NetCtlError>,
700 ) -> Result<R, NetCtlError> {
701 self.network_id.clear();
702
703 if let Some(network_id) = network_id {
704 unwrap!(self.network_id.extend_from_slice(network_id));
705 }
706
707 if let Some((status, err_code)) = NetworkCommissioningStatusEnum::map_ctl_status(&result) {
708 self.networking_status = Some(status);
709 self.connect_error_value = err_code;
710 } else {
711 self.networking_status = None;
712 self.connect_error_value = None;
713 }
714
715 result
716 }
717
718 pub fn update_with_mutex<R>(
722 state: &NetCtlStateMutex,
723 network_id: Option<&[u8]>,
724 result: Result<R, NetCtlError>,
725 ) -> Result<R, NetCtlError> {
726 state.lock(|state| state.borrow_mut().update(network_id, result))
727 }
728
729 pub async fn wait_prov_ready(state: &NetCtlStateMutex, _btp: &Btp) {
736 while !state.lock(|state| state.borrow().is_prov_ready()) {
737 embassy_time::Timer::after_secs(1).await;
741 }
742 }
743}
744
745impl Default for NetCtlState {
746 fn default() -> Self {
747 Self::new()
748 }
749}
750
751pub type NetCtlStateMutex = blocking::Mutex<RefCell<NetCtlState>>;
753
754pub struct NetCtlWithStatusImpl<'a, T> {
756 state: &'a NetCtlStateMutex,
757 net_ctl: T,
758}
759
760impl<'a, T> NetCtlWithStatusImpl<'a, T> {
761 pub const fn new(state: &'a NetCtlStateMutex, net_ctl: T) -> Self {
767 Self { state, net_ctl }
768 }
769}
770
771impl<T> net_comm::NetCtl for NetCtlWithStatusImpl<'_, T>
772where
773 T: net_comm::NetCtl,
774{
775 fn net_type(&self) -> NetworkType {
776 self.net_ctl.net_type()
777 }
778
779 fn connect_max_time_seconds(&self) -> u8 {
780 self.net_ctl.connect_max_time_seconds()
781 }
782
783 fn scan_max_time_seconds(&self) -> u8 {
784 self.net_ctl.scan_max_time_seconds()
785 }
786
787 fn supported_wifi_bands<F>(&self, f: F) -> Result<(), Error>
788 where
789 F: FnMut(net_comm::WiFiBandEnum) -> Result<(), Error>,
790 {
791 self.net_ctl.supported_wifi_bands(f)
792 }
793
794 fn supported_thread_features(&self) -> ThreadCapabilitiesBitmap {
795 self.net_ctl.supported_thread_features()
796 }
797
798 fn thread_version(&self) -> u16 {
799 self.net_ctl.thread_version()
800 }
801
802 async fn scan<F>(&self, network: Option<&[u8]>, f: F) -> Result<(), NetCtlError>
803 where
804 F: FnMut(&net_comm::NetworkScanInfo) -> Result<(), Error>,
805 {
806 self.net_ctl.scan(network, f).await
807 }
808
809 async fn connect(&self, creds: &WirelessCreds<'_>) -> Result<(), NetCtlError> {
810 NetCtlState::update_with_mutex(
811 self.state,
812 Some(creds.id()?),
813 self.net_ctl.connect(creds).await,
814 )
815 }
816}
817
818impl<T> net_comm::NetCtlStatus for NetCtlWithStatusImpl<'_, T>
819where
820 T: net_comm::NetCtl,
821{
822 fn last_networking_status(&self) -> Result<Option<NetworkCommissioningStatusEnum>, Error> {
823 Ok(self.state.lock(|state| state.borrow().networking_status))
824 }
825
826 fn last_network_id<F, R>(&self, f: F) -> Result<R, Error>
827 where
828 F: FnOnce(Option<&[u8]>) -> Result<R, Error>,
829 {
830 self.state.lock(|state| {
831 let state = state.borrow();
832
833 if state.network_id.is_empty() {
834 f(None)
835 } else {
836 f(Some(&state.network_id))
837 }
838 })
839 }
840
841 fn last_connect_error_value(&self) -> Result<Option<i32>, Error> {
842 Ok(self.state.lock(|state| state.borrow().connect_error_value))
843 }
844}
845
846impl<T> NetChangeNotif for NetCtlWithStatusImpl<'_, T>
847where
848 T: NetChangeNotif,
849{
850 async fn wait_changed(&self) {
851 self.net_ctl.wait_changed().await
852 }
853}
854
855#[cfg(feature = "sync-mutex")]
856impl<T> DynBase for NetCtlWithStatusImpl<'_, T> where T: Send + Sync {}
857
858#[cfg(not(feature = "sync-mutex"))]
859impl<T> DynBase for NetCtlWithStatusImpl<'_, T> {}
860
861impl<T> wifi_diag::WirelessDiag for NetCtlWithStatusImpl<'_, T>
862where
863 T: wifi_diag::WirelessDiag,
864{
865 fn connected(&self) -> Result<bool, Error> {
866 self.net_ctl.connected()
867 }
868}
869
870impl<T> wifi_diag::WifiDiag for NetCtlWithStatusImpl<'_, T>
871where
872 T: wifi_diag::WifiDiag,
873{
874 fn bssid(&self, f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>) -> Result<(), Error> {
875 self.net_ctl.bssid(f)
876 }
877
878 fn security_type(&self) -> Result<crate::tlv::Nullable<wifi_diag::SecurityTypeEnum>, Error> {
879 self.net_ctl.security_type()
880 }
881
882 fn wi_fi_version(&self) -> Result<crate::tlv::Nullable<wifi_diag::WiFiVersionEnum>, Error> {
883 self.net_ctl.wi_fi_version()
884 }
885
886 fn channel_number(&self) -> Result<crate::tlv::Nullable<u16>, Error> {
887 self.net_ctl.channel_number()
888 }
889
890 fn rssi(&self) -> Result<crate::tlv::Nullable<i8>, Error> {
891 self.net_ctl.rssi()
892 }
893}
894
895impl<T> thread_diag::ThreadDiag for NetCtlWithStatusImpl<'_, T>
896where
897 T: thread_diag::ThreadDiag,
898{
899 fn channel(&self) -> Result<Option<u16>, Error> {
900 self.net_ctl.channel()
901 }
902
903 fn routing_role(&self) -> Result<Option<thread_diag::RoutingRoleEnum>, Error> {
904 self.net_ctl.routing_role()
905 }
906
907 fn network_name(
908 &self,
909 f: &mut dyn FnMut(Option<&str>) -> Result<(), Error>,
910 ) -> Result<(), Error> {
911 self.net_ctl.network_name(f)
912 }
913
914 fn pan_id(&self) -> Result<Option<u16>, Error> {
915 self.net_ctl.pan_id()
916 }
917
918 fn extended_pan_id(&self) -> Result<Option<u64>, Error> {
919 self.net_ctl.extended_pan_id()
920 }
921
922 fn mesh_local_prefix(
923 &self,
924 f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>,
925 ) -> Result<(), Error> {
926 self.net_ctl.mesh_local_prefix(f)
927 }
928
929 fn neighbor_table(
930 &self,
931 f: &mut dyn FnMut(&thread_diag::NeighborTable) -> Result<(), Error>,
932 ) -> Result<(), Error> {
933 self.net_ctl.neighbor_table(f)
934 }
935
936 fn route_table(
937 &self,
938 f: &mut dyn FnMut(&thread_diag::RouteTable) -> Result<(), Error>,
939 ) -> Result<(), Error> {
940 self.net_ctl.route_table(f)
941 }
942
943 fn partition_id(&self) -> Result<Option<u32>, Error> {
944 self.net_ctl.partition_id()
945 }
946
947 fn weighting(&self) -> Result<Option<u16>, Error> {
948 self.net_ctl.weighting()
949 }
950
951 fn data_version(&self) -> Result<Option<u16>, Error> {
952 self.net_ctl.data_version()
953 }
954
955 fn stable_data_version(&self) -> Result<Option<u16>, Error> {
956 self.net_ctl.stable_data_version()
957 }
958
959 fn leader_router_id(&self) -> Result<Option<u8>, Error> {
960 self.net_ctl.leader_router_id()
961 }
962
963 fn security_policy(&self) -> Result<Option<thread_diag::SecurityPolicy>, Error> {
964 self.net_ctl.security_policy()
965 }
966
967 fn channel_page0_mask(
968 &self,
969 f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>,
970 ) -> Result<(), Error> {
971 self.net_ctl.channel_page0_mask(f)
972 }
973
974 fn operational_dataset_components(
975 &self,
976 f: &mut dyn FnMut(Option<&thread_diag::OperationalDatasetComponents>) -> Result<(), Error>,
977 ) -> Result<(), Error> {
978 self.net_ctl.operational_dataset_components(f)
979 }
980
981 fn active_network_faults_list(
982 &self,
983 f: &mut dyn FnMut(thread_diag::NetworkFaultEnum) -> Result<(), Error>,
984 ) -> Result<(), Error> {
985 self.net_ctl.active_network_faults_list(f)
986 }
987}
988
989#[cfg(test)]
990mod tests {
991 use crate::dm::clusters::net_comm::{
992 NetworksAccess, NetworksError, SharedNetworks, WirelessCreds,
993 };
994
995 use super::wifi::{Wifi, WifiNetworks};
996 use super::WirelessNetwork;
997
998 fn wifi_creds<'a>(ssid: &'a [u8], pass: &'a [u8]) -> WirelessCreds<'a> {
1001 WirelessCreds::Wifi { ssid, pass }
1002 }
1003
1004 fn collect_ssids(nets: &WifiNetworks<4>) -> Vec<Vec<u8>> {
1005 let mut ids = Vec::new();
1006 nets.networks(|n| {
1007 ids.push(n.id().to_vec());
1008 Ok(())
1009 })
1010 .unwrap();
1011 ids
1012 }
1013
1014 #[test]
1017 fn add_networks() {
1018 let mut nets = WifiNetworks::<4>::new();
1019
1020 let idx = nets
1021 .add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"PassA")), |_| {
1022 Ok(())
1023 })
1024 .unwrap();
1025 assert_eq!(idx, 0);
1026
1027 let idx = nets
1028 .add_or_update(b"B", Wifi::init_from(&wifi_creds(b"B", b"PassB")), |_| {
1029 Ok(())
1030 })
1031 .unwrap();
1032 assert_eq!(idx, 1);
1033
1034 assert_eq!(collect_ssids(&nets), vec![b"A".to_vec(), b"B".to_vec()]);
1035 }
1036
1037 #[test]
1038 fn update_existing_network() {
1039 let mut nets = WifiNetworks::<4>::new();
1040 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"Old")), |_| Ok(()))
1041 .unwrap();
1042
1043 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"New")), |wifi| {
1045 wifi.update(&wifi_creds(b"A", b"New"))
1046 })
1047 .unwrap();
1048
1049 assert_eq!(collect_ssids(&nets).len(), 1);
1051
1052 let mut pass = Vec::new();
1054 nets.network(b"A", |w| {
1055 if let WirelessCreds::Wifi { pass: p, .. } = w.creds() {
1056 pass.extend_from_slice(p);
1057 }
1058 Ok(())
1059 })
1060 .unwrap();
1061 assert_eq!(pass, b"New");
1062 }
1063
1064 #[test]
1065 fn add_exceeds_capacity() {
1066 let mut nets = WifiNetworks::<2>::new();
1067 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"p")), |_| Ok(()))
1068 .unwrap();
1069 nets.add_or_update(b"B", Wifi::init_from(&wifi_creds(b"B", b"p")), |_| Ok(()))
1070 .unwrap();
1071
1072 let err = nets.add_or_update(b"C", Wifi::init_from(&wifi_creds(b"C", b"p")), |_| Ok(()));
1073 assert!(matches!(err, Err(NetworksError::BoundsExceeded)));
1074 }
1075
1076 #[test]
1077 fn remove_network() {
1078 let mut nets = WifiNetworks::<4>::new();
1079 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"p")), |_| Ok(()))
1080 .unwrap();
1081 nets.add_or_update(b"B", Wifi::init_from(&wifi_creds(b"B", b"p")), |_| Ok(()))
1082 .unwrap();
1083
1084 let idx = nets.remove(b"A").unwrap();
1085 assert_eq!(idx, 0);
1086 assert_eq!(collect_ssids(&nets), vec![b"B".to_vec()]);
1087 }
1088
1089 #[test]
1090 fn remove_nonexistent() {
1091 let mut nets = WifiNetworks::<4>::new();
1092 assert!(matches!(
1093 nets.remove(b"X"),
1094 Err(NetworksError::NetworkIdNotFound)
1095 ));
1096 }
1097
1098 #[test]
1101 fn reorder_moves_to_front() {
1102 let mut nets = WifiNetworks::<4>::new();
1103 for id in [b"A", b"B", b"C"] {
1104 nets.add_or_update(
1105 id.as_slice(),
1106 Wifi::init_from(&wifi_creds(id, b"p")),
1107 |_| Ok(()),
1108 )
1109 .unwrap();
1110 }
1111
1112 nets.reorder(0, b"C").unwrap();
1114 assert_eq!(
1115 collect_ssids(&nets),
1116 vec![b"C".to_vec(), b"A".to_vec(), b"B".to_vec()]
1117 );
1118 }
1119
1120 #[test]
1121 fn reorder_out_of_range() {
1122 let mut nets = WifiNetworks::<4>::new();
1123 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"p")), |_| Ok(()))
1124 .unwrap();
1125
1126 assert!(matches!(
1127 nets.reorder(5, b"A"),
1128 Err(NetworksError::OutOfRange)
1129 ));
1130 }
1131
1132 #[test]
1133 fn reorder_nonexistent() {
1134 let mut nets = WifiNetworks::<4>::new();
1135 assert!(matches!(
1136 nets.reorder(0, b"X"),
1137 Err(NetworksError::NetworkIdNotFound)
1138 ));
1139 }
1140
1141 #[test]
1144 fn next_network_iterates_and_wraps() {
1145 let mut nets = WifiNetworks::<4>::new();
1146 for id in [b"A", b"B", b"C"] {
1147 nets.add_or_update(
1148 id.as_slice(),
1149 Wifi::init_from(&wifi_creds(id, b"p")),
1150 |_| Ok(()),
1151 )
1152 .unwrap();
1153 }
1154
1155 let get_next = |last: Option<&[u8]>| -> Option<Vec<u8>> {
1156 let mut result = None;
1157 let found = nets
1158 .next_network(last, |w| {
1159 result = Some(w.id().to_vec());
1160 Ok(())
1161 })
1162 .unwrap();
1163 if found {
1164 result
1165 } else {
1166 None
1167 }
1168 };
1169
1170 assert_eq!(get_next(None), Some(b"A".to_vec()));
1171 assert_eq!(get_next(Some(b"A")), Some(b"B".to_vec()));
1172 assert_eq!(get_next(Some(b"B")), Some(b"C".to_vec()));
1173 assert_eq!(get_next(Some(b"C")), Some(b"A".to_vec()));
1175 assert_eq!(get_next(Some(b"Z")), Some(b"A".to_vec()));
1177 }
1178
1179 #[test]
1180 fn next_network_empty_returns_false() {
1181 let nets = WifiNetworks::<4>::new();
1182 let found = nets.next_network(None, |_| Ok(())).unwrap();
1183 assert!(!found);
1184 }
1185
1186 #[test]
1189 fn store_load_round_trip() {
1190 let mut nets = WifiNetworks::<4>::new();
1191 nets.add_or_update(
1192 b"Net1",
1193 Wifi::init_from(&wifi_creds(b"Net1", b"P1")),
1194 |_| Ok(()),
1195 )
1196 .unwrap();
1197 nets.add_or_update(
1198 b"Net2",
1199 Wifi::init_from(&wifi_creds(b"Net2", b"P2")),
1200 |_| Ok(()),
1201 )
1202 .unwrap();
1203 nets.set_commissioned(true);
1204
1205 let mut buf = [0u8; 512];
1206 let len = nets.store(&mut buf).unwrap();
1207
1208 let mut loaded = WifiNetworks::<4>::new();
1209 loaded.load(&buf[..len]).unwrap();
1210
1211 assert_eq!(collect_ssids(&loaded), collect_ssids(&nets));
1212 assert!(loaded.commissioned());
1213 }
1214
1215 #[test]
1218 fn commissioned_default_false() {
1219 let nets = WifiNetworks::<4>::new();
1220 assert!(!nets.commissioned());
1221 }
1222
1223 #[test]
1224 fn set_commissioned() {
1225 let mut nets = WifiNetworks::<4>::new();
1226 nets.set_commissioned(true);
1227 assert!(nets.commissioned());
1228 nets.set_commissioned(false);
1229 assert!(!nets.commissioned());
1230 }
1231
1232 #[test]
1235 fn reset_clears_all() {
1236 let mut nets = WifiNetworks::<4>::new();
1237 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"p")), |_| Ok(()))
1238 .unwrap();
1239 nets.set_commissioned(true);
1240
1241 nets.reset();
1242 assert!(collect_ssids(&nets).is_empty());
1243 assert!(!nets.commissioned());
1244 }
1245
1246 #[test]
1249 fn shared_networks_access_add_and_read() {
1250 let shared = SharedNetworks::new(WifiNetworks::<4>::new());
1251
1252 shared.access(|networks| {
1253 networks
1254 .add_or_update(&wifi_creds(b"SSID1", b"pass1"))
1255 .unwrap();
1256 networks
1257 .add_or_update(&wifi_creds(b"SSID2", b"pass2"))
1258 .unwrap();
1259 });
1260
1261 let count = shared.access(|networks| {
1263 let mut count = 0u8;
1264 networks
1265 .networks(&mut |_info| {
1266 count += 1;
1267 Ok(())
1268 })
1269 .unwrap();
1270 count
1271 });
1272
1273 assert_eq!(count, 2);
1274 }
1275
1276 #[test]
1277 fn shared_networks_commissioned_via_access() {
1278 let shared = SharedNetworks::new(WifiNetworks::<4>::new());
1279
1280 let commissioned = shared.access(|networks| networks.commissioned().unwrap());
1281 assert!(!commissioned);
1282
1283 shared.access(|networks| networks.set_commissioned(true).unwrap());
1284
1285 let commissioned = shared.access(|networks| networks.commissioned().unwrap());
1286 assert!(commissioned);
1287 }
1288
1289 #[test]
1290 fn shared_networks_next_creds_round_robin() {
1291 let shared = SharedNetworks::new(WifiNetworks::<4>::new());
1292
1293 shared.access(|networks| {
1294 for (ssid, pass) in [(b"A", b"pA"), (b"B", b"pB"), (b"C", b"pC")] {
1295 networks
1296 .add_or_update(&wifi_creds(ssid.as_slice(), pass.as_slice()))
1297 .unwrap();
1298 }
1299 });
1300
1301 let get_next_ssid = |last: Option<&[u8]>| -> Option<Vec<u8>> {
1302 shared.access(|networks| {
1303 let mut result = None;
1304 let found = networks
1305 .next_creds(last, &mut |creds| {
1306 if let WirelessCreds::Wifi { ssid, .. } = creds {
1307 result = Some(ssid.to_vec());
1308 }
1309 Ok(())
1310 })
1311 .unwrap();
1312 if found {
1313 result
1314 } else {
1315 None
1316 }
1317 })
1318 };
1319
1320 assert_eq!(get_next_ssid(None), Some(b"A".to_vec()));
1321 assert_eq!(get_next_ssid(Some(b"A")), Some(b"B".to_vec()));
1322 assert_eq!(get_next_ssid(Some(b"C")), Some(b"A".to_vec()));
1323 }
1324
1325 #[test]
1328 fn load_old_format_bare_array() {
1329 use crate::tlv::{TLVTag, TLVWrite};
1330 use crate::utils::storage::WriteBuf;
1331
1332 let mut buf = [0u8; 512];
1336 let mut wb = WriteBuf::new(&mut buf);
1337
1338 wb.start_array(&TLVTag::Anonymous).unwrap();
1339
1340 wb.start_struct(&TLVTag::Anonymous).unwrap();
1342 wb.str(&TLVTag::Context(0), b"A").unwrap();
1343 wb.str(&TLVTag::Context(1), b"pA").unwrap();
1344 wb.end_container().unwrap();
1345
1346 wb.start_struct(&TLVTag::Anonymous).unwrap();
1348 wb.str(&TLVTag::Context(0), b"B").unwrap();
1349 wb.str(&TLVTag::Context(1), b"pB").unwrap();
1350 wb.end_container().unwrap();
1351
1352 wb.end_container().unwrap();
1353 let len = wb.get_tail();
1354
1355 let mut nets = WifiNetworks::<4>::new();
1357 nets.load(&buf[..len]).unwrap();
1358
1359 assert_eq!(collect_ssids(&nets), vec![b"A".to_vec(), b"B".to_vec()]);
1360 assert!(
1361 !nets.commissioned(),
1362 "Old format should default commissioned to false"
1363 );
1364 }
1365
1366 #[test]
1369 fn shared_networks_save_does_not_trigger_change() {
1370 use core::pin::pin;
1371 use embassy_futures::select::{select, Either};
1372
1373 let shared = SharedNetworks::new(WifiNetworks::<4>::new());
1374
1375 shared.access(|n| n.add_or_update(&wifi_creds(b"A", b"p")).unwrap());
1377
1378 embassy_futures::block_on(shared.wait_state_changed());
1380
1381 shared.access(|n| {
1383 let mut buf = [0u8; 512];
1384 n.save(&mut buf).unwrap();
1385 });
1386
1387 let notified = embassy_futures::block_on(async {
1392 match select(
1393 pin!(shared.wait_state_changed()),
1394 pin!(core::future::ready(())),
1395 )
1396 .await
1397 {
1398 Either::First(_) => true,
1399 Either::Second(_) => false,
1400 }
1401 });
1402
1403 assert!(!notified, "save() must not trigger change notification");
1404 }
1405}