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 managed: 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 managed: false,
130 }
131 }
132
133 pub fn init() -> impl Init<Self> {
134 init!(Self {
135 networks <- crate::utils::storage::Vec::init(),
136 managed: false,
137 })
138 }
139
140 pub fn reset(&mut self) {
142 self.networks.clear();
143 self.managed = 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.managed = 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.managed = 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.managed.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 self.managed = false;
369
370 info!("Updated network with ID {}", unetwork.display());
371
372 Ok(index as _)
373 } else if self.networks.len() >= N {
374 warn!(
375 "Adding network with ID {} failed: too many",
376 T::display_id(network_id)
377 );
378
379 Err(NetworksError::BoundsExceeded)
380 } else {
381 self.networks
383 .push_init(add, || ErrorCode::ResourceExhausted.into())?;
384
385 self.managed = false;
387
388 info!("Added network with ID {}", T::display_id(network_id));
389
390 Ok((self.networks.len() - 1) as _)
391 }
392 }
393
394 pub fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
403 let cur_index = self
404 .networks
405 .iter()
406 .position(|conf| conf.id() == network_id);
407
408 if let Some(cur_index) = cur_index {
409 if index < self.networks.len() as u8 {
412 let conf = self.networks.remove(cur_index);
413 unwrap!(self.networks.insert(index as usize, conf).map_err(|_| ()));
414
415 self.managed = false;
417
418 info!(
419 "Network with ID {} reordered to index {}",
420 T::display_id(network_id),
421 index
422 );
423 } else {
424 warn!(
425 "Reordering network with ID {} to index {} failed: out of range",
426 T::display_id(network_id),
427 index
428 );
429
430 Err(NetworksError::OutOfRange)?;
431 }
432 } else {
433 warn!("Network with ID {} not found", T::display_id(network_id));
434 Err(NetworksError::NetworkIdNotFound)?;
435 }
436
437 Ok(index)
438 }
439
440 pub fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
447 let index = self
448 .networks
449 .iter()
450 .position(|conf| conf.id() == network_id);
451
452 if let Some(index) = index {
453 self.networks.remove(index);
455
456 self.managed = false;
458
459 info!("Removed network with ID {}", T::display_id(network_id));
460
461 Ok(index as _)
462 } else {
463 warn!("Network with ID {} not found", T::display_id(network_id));
464
465 Err(NetworksError::NetworkIdNotFound)
466 }
467 }
468
469 pub fn managed(&self) -> bool {
470 self.managed
471 }
472
473 pub fn set_managed(&mut self, managed: bool) {
474 self.managed = managed;
475 }
476}
477
478impl<const N: usize, T> Networks for WirelessNetworks<N, T>
479where
480 T: WirelessNetwork,
481{
482 fn max_networks(&self) -> Result<u8, Error> {
483 Ok(N as _)
484 }
485
486 fn networks(&self, f: &mut dyn FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error> {
487 WirelessNetworks::networks(self, |network| f(network.id()))
488 }
489
490 fn creds(
491 &self,
492 network_id: &[u8],
493 f: &mut dyn FnMut(&net_comm::WirelessCreds) -> Result<(), Error>,
494 ) -> Result<u8, NetworksError> {
495 WirelessNetworks::network(self, network_id, |network| f(&network.creds()))
496 }
497
498 fn next_creds(
499 &self,
500 last_network_id: Option<&[u8]>,
501 f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
502 ) -> Result<bool, Error> {
503 WirelessNetworks::next_network(self, last_network_id, |network| f(&network.creds()))
504 }
505
506 fn enabled(&self) -> Result<bool, Error> {
507 Ok(true)
508 }
509
510 fn set_enabled(&mut self, _enabled: bool) -> Result<(), Error> {
511 Ok(())
512 }
513
514 fn add_or_update(
515 &mut self,
516 creds: &net_comm::WirelessCreds<'_>,
517 ) -> Result<u8, net_comm::NetworksError> {
518 WirelessNetworks::add_or_update(self, creds.id()?, T::init_from(creds), |network| {
519 network.update(creds)
520 })
521 }
522
523 fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
524 WirelessNetworks::reorder(self, index, network_id)
525 }
526
527 fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
528 WirelessNetworks::remove(self, network_id)
529 }
530
531 fn managed(&self) -> Result<bool, Error> {
532 Ok(self.managed())
533 }
534
535 fn set_managed(&mut self, managed: bool) -> Result<(), Error> {
536 WirelessNetworks::set_managed(self, managed);
537
538 Ok(())
539 }
540
541 fn reset(&mut self) -> Result<(), Error> {
542 WirelessNetworks::reset(self);
543
544 Ok(())
545 }
546
547 fn load(&mut self, data: &[u8]) -> Result<(), Error> {
548 WirelessNetworks::load(self, data)
549 }
550
551 fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
552 WirelessNetworks::store(self, buf).map(Some)
553 }
554}
555
556#[derive(Debug)]
558enum DisplayId<'a> {
559 Wifi(&'a [u8]),
560 Thread(&'a [u8]),
561}
562
563impl Display for DisplayId<'_> {
564 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
565 match self {
566 DisplayId::Wifi(id) => {
567 if let Ok(str) = core::str::from_utf8(id) {
568 write!(f, "Wifi SSID({})", str)
569 } else {
570 write!(f, "Wifi SSID({:?})", Bytes(id))
571 }
572 }
573 DisplayId::Thread(id) => write!(f, "Thread ExtPanID({:?})", Bytes(id)),
574 }
575 }
576}
577
578#[cfg(feature = "defmt")]
579impl defmt::Format for DisplayId<'_> {
580 fn format(&self, fmt: defmt::Formatter) {
581 match self {
582 DisplayId::Wifi(id) => {
583 if let Ok(str) = core::str::from_utf8(id) {
584 defmt::write!(fmt, "Wifi SSID({})", str)
585 } else {
586 defmt::write!(fmt, "Wifi SSID({:?})", Bytes(id))
587 }
588 }
589 DisplayId::Thread(id) => defmt::write!(fmt, "Thread ExtPanID({:?})", Bytes(id)),
590 }
591 }
592}
593
594pub struct NoopWirelessNetCtl(NetworkType);
599
600impl NoopWirelessNetCtl {
601 pub const fn new(net_type: NetworkType) -> Self {
608 Self(net_type)
609 }
610}
611
612impl net_comm::NetCtl for NoopWirelessNetCtl {
613 fn net_type(&self) -> NetworkType {
614 self.0
615 }
616
617 async fn scan<F>(&self, _network: Option<&[u8]>, _f: F) -> Result<(), NetCtlError>
618 where
619 F: FnOnce(&net_comm::NetworkScanInfo) -> Result<(), Error>,
620 {
621 Err(NetCtlError::Other(ErrorCode::InvalidAction.into()))
622 }
623
624 async fn connect(&self, creds: &WirelessCreds<'_>) -> Result<(), NetCtlError> {
625 Ok(creds.check_match(self.0)?)
626 }
627}
628
629impl NetChangeNotif for NoopWirelessNetCtl {
630 async fn wait_changed(&self) {
631 core::future::pending().await
632 }
633}
634
635impl net_comm::NetCtlStatus for NoopWirelessNetCtl {
636 fn last_networking_status(
637 &self,
638 ) -> Result<Option<net_comm::NetworkCommissioningStatusEnum>, Error> {
639 Ok(None)
640 }
641
642 fn last_network_id<F, R>(&self, f: F) -> Result<R, Error>
643 where
644 F: FnOnce(Option<&[u8]>) -> Result<R, Error>,
645 {
646 f(None)
647 }
648
649 fn last_connect_error_value(&self) -> Result<Option<i32>, Error> {
650 Ok(None)
651 }
652}
653
654impl DynBase for NoopWirelessNetCtl {}
655
656impl wifi_diag::WirelessDiag for NoopWirelessNetCtl {}
657
658impl wifi_diag::WifiDiag for NoopWirelessNetCtl {}
659
660impl thread_diag::ThreadDiag for NoopWirelessNetCtl {}
661
662pub struct NetCtlState {
664 pub network_id: OwnedWirelessNetworkId,
666 pub networking_status: Option<NetworkCommissioningStatusEnum>,
668 pub connect_error_value: Option<i32>,
671}
672
673impl NetCtlState {
674 pub const fn new() -> Self {
676 Self {
677 network_id: OwnedWirelessNetworkId::new(),
678 networking_status: None,
679 connect_error_value: None,
680 }
681 }
682
683 pub fn init() -> impl Init<Self> {
685 init!(Self {
686 network_id <- OwnedWirelessNetworkId::init(),
687 networking_status: None,
688 connect_error_value: None,
689 })
690 }
691
692 pub const fn new_with_mutex() -> NetCtlStateMutex {
694 blocking::Mutex::new(RefCell::new(Self::new()))
695 }
696
697 pub fn init_with_mutex() -> impl Init<NetCtlStateMutex> {
699 blocking::Mutex::init(RefCell::init(init!(Self {
700 network_id <- OwnedWirelessNetworkId::init(),
701 networking_status: None,
702 connect_error_value: None,
703 })))
704 }
705
706 pub fn is_prov_ready(&self) -> bool {
708 !self.network_id.is_empty()
709 && matches!(
710 self.networking_status,
711 Some(NetworkCommissioningStatusEnum::Success)
712 )
713 && self.connect_error_value.is_none()
714 }
715
716 pub fn update<R>(
720 &mut self,
721 network_id: Option<&[u8]>,
722 result: Result<R, NetCtlError>,
723 ) -> Result<R, NetCtlError> {
724 self.network_id.clear();
725
726 if let Some(network_id) = network_id {
727 unwrap!(self.network_id.extend_from_slice(network_id));
728 }
729
730 if let Some((status, err_code)) = NetworkCommissioningStatusEnum::map_ctl_status(&result) {
731 self.networking_status = Some(status);
732 self.connect_error_value = err_code;
733 } else {
734 self.networking_status = None;
735 self.connect_error_value = None;
736 }
737
738 result
739 }
740
741 pub fn update_with_mutex<R>(
745 state: &NetCtlStateMutex,
746 network_id: Option<&[u8]>,
747 result: Result<R, NetCtlError>,
748 ) -> Result<R, NetCtlError> {
749 state.lock(|state| state.borrow_mut().update(network_id, result))
750 }
751
752 pub async fn wait_prov_ready(state: &NetCtlStateMutex, _btp: &Btp) {
759 while !state.lock(|state| state.borrow().is_prov_ready()) {
760 embassy_time::Timer::after_secs(1).await;
764 }
765 }
766}
767
768impl Default for NetCtlState {
769 fn default() -> Self {
770 Self::new()
771 }
772}
773
774pub type NetCtlStateMutex = blocking::Mutex<RefCell<NetCtlState>>;
776
777pub struct NetCtlWithStatusImpl<'a, T> {
779 state: &'a NetCtlStateMutex,
780 net_ctl: T,
781}
782
783impl<'a, T> NetCtlWithStatusImpl<'a, T> {
784 pub const fn new(state: &'a NetCtlStateMutex, net_ctl: T) -> Self {
790 Self { state, net_ctl }
791 }
792}
793
794impl<T> net_comm::NetCtl for NetCtlWithStatusImpl<'_, T>
795where
796 T: net_comm::NetCtl,
797{
798 fn net_type(&self) -> NetworkType {
799 self.net_ctl.net_type()
800 }
801
802 fn connect_max_time_seconds(&self) -> u8 {
803 self.net_ctl.connect_max_time_seconds()
804 }
805
806 fn scan_max_time_seconds(&self) -> u8 {
807 self.net_ctl.scan_max_time_seconds()
808 }
809
810 fn supported_wifi_bands<F>(&self, f: F) -> Result<(), Error>
811 where
812 F: FnMut(net_comm::WiFiBandEnum) -> Result<(), Error>,
813 {
814 self.net_ctl.supported_wifi_bands(f)
815 }
816
817 fn supported_thread_features(&self) -> ThreadCapabilitiesBitmap {
818 self.net_ctl.supported_thread_features()
819 }
820
821 fn thread_version(&self) -> u16 {
822 self.net_ctl.thread_version()
823 }
824
825 async fn scan<F>(&self, network: Option<&[u8]>, f: F) -> Result<(), NetCtlError>
826 where
827 F: FnMut(&net_comm::NetworkScanInfo) -> Result<(), Error>,
828 {
829 self.net_ctl.scan(network, f).await
830 }
831
832 async fn connect(&self, creds: &WirelessCreds<'_>) -> Result<(), NetCtlError> {
833 NetCtlState::update_with_mutex(
834 self.state,
835 Some(creds.id()?),
836 self.net_ctl.connect(creds).await,
837 )
838 }
839}
840
841impl<T> net_comm::NetCtlStatus for NetCtlWithStatusImpl<'_, T>
842where
843 T: net_comm::NetCtl,
844{
845 fn last_networking_status(&self) -> Result<Option<NetworkCommissioningStatusEnum>, Error> {
846 Ok(self.state.lock(|state| state.borrow().networking_status))
847 }
848
849 fn last_network_id<F, R>(&self, f: F) -> Result<R, Error>
850 where
851 F: FnOnce(Option<&[u8]>) -> Result<R, Error>,
852 {
853 self.state.lock(|state| {
854 let state = state.borrow();
855
856 if state.network_id.is_empty() {
857 f(None)
858 } else {
859 f(Some(&state.network_id))
860 }
861 })
862 }
863
864 fn last_connect_error_value(&self) -> Result<Option<i32>, Error> {
865 Ok(self.state.lock(|state| state.borrow().connect_error_value))
866 }
867}
868
869impl<T> NetChangeNotif for NetCtlWithStatusImpl<'_, T>
870where
871 T: NetChangeNotif,
872{
873 async fn wait_changed(&self) {
874 self.net_ctl.wait_changed().await
875 }
876}
877
878#[cfg(feature = "sync-mutex")]
879impl<T> DynBase for NetCtlWithStatusImpl<'_, T> where T: Send + Sync {}
880
881#[cfg(not(feature = "sync-mutex"))]
882impl<T> DynBase for NetCtlWithStatusImpl<'_, T> {}
883
884impl<T> wifi_diag::WirelessDiag for NetCtlWithStatusImpl<'_, T>
885where
886 T: wifi_diag::WirelessDiag,
887{
888 fn connected(&self) -> Result<bool, Error> {
889 self.net_ctl.connected()
890 }
891}
892
893impl<T> wifi_diag::WifiDiag for NetCtlWithStatusImpl<'_, T>
894where
895 T: wifi_diag::WifiDiag,
896{
897 fn bssid(&self, f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>) -> Result<(), Error> {
898 self.net_ctl.bssid(f)
899 }
900
901 fn security_type(&self) -> Result<crate::tlv::Nullable<wifi_diag::SecurityTypeEnum>, Error> {
902 self.net_ctl.security_type()
903 }
904
905 fn wi_fi_version(&self) -> Result<crate::tlv::Nullable<wifi_diag::WiFiVersionEnum>, Error> {
906 self.net_ctl.wi_fi_version()
907 }
908
909 fn channel_number(&self) -> Result<crate::tlv::Nullable<u16>, Error> {
910 self.net_ctl.channel_number()
911 }
912
913 fn rssi(&self) -> Result<crate::tlv::Nullable<i8>, Error> {
914 self.net_ctl.rssi()
915 }
916}
917
918impl<T> thread_diag::ThreadDiag for NetCtlWithStatusImpl<'_, T>
919where
920 T: thread_diag::ThreadDiag,
921{
922 fn channel(&self) -> Result<Option<u16>, Error> {
923 self.net_ctl.channel()
924 }
925
926 fn routing_role(&self) -> Result<Option<thread_diag::RoutingRoleEnum>, Error> {
927 self.net_ctl.routing_role()
928 }
929
930 fn network_name(
931 &self,
932 f: &mut dyn FnMut(Option<&str>) -> Result<(), Error>,
933 ) -> Result<(), Error> {
934 self.net_ctl.network_name(f)
935 }
936
937 fn pan_id(&self) -> Result<Option<u16>, Error> {
938 self.net_ctl.pan_id()
939 }
940
941 fn extended_pan_id(&self) -> Result<Option<u64>, Error> {
942 self.net_ctl.extended_pan_id()
943 }
944
945 fn mesh_local_prefix(
946 &self,
947 f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>,
948 ) -> Result<(), Error> {
949 self.net_ctl.mesh_local_prefix(f)
950 }
951
952 fn neighbor_table(
953 &self,
954 f: &mut dyn FnMut(&thread_diag::NeighborTable) -> Result<(), Error>,
955 ) -> Result<(), Error> {
956 self.net_ctl.neighbor_table(f)
957 }
958
959 fn route_table(
960 &self,
961 f: &mut dyn FnMut(&thread_diag::RouteTable) -> Result<(), Error>,
962 ) -> Result<(), Error> {
963 self.net_ctl.route_table(f)
964 }
965
966 fn partition_id(&self) -> Result<Option<u32>, Error> {
967 self.net_ctl.partition_id()
968 }
969
970 fn weighting(&self) -> Result<Option<u16>, Error> {
971 self.net_ctl.weighting()
972 }
973
974 fn data_version(&self) -> Result<Option<u16>, Error> {
975 self.net_ctl.data_version()
976 }
977
978 fn stable_data_version(&self) -> Result<Option<u16>, Error> {
979 self.net_ctl.stable_data_version()
980 }
981
982 fn leader_router_id(&self) -> Result<Option<u8>, Error> {
983 self.net_ctl.leader_router_id()
984 }
985
986 fn security_policy(&self) -> Result<Option<thread_diag::SecurityPolicy>, Error> {
987 self.net_ctl.security_policy()
988 }
989
990 fn channel_page0_mask(
991 &self,
992 f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>,
993 ) -> Result<(), Error> {
994 self.net_ctl.channel_page0_mask(f)
995 }
996
997 fn operational_dataset_components(
998 &self,
999 f: &mut dyn FnMut(Option<&thread_diag::OperationalDatasetComponents>) -> Result<(), Error>,
1000 ) -> Result<(), Error> {
1001 self.net_ctl.operational_dataset_components(f)
1002 }
1003
1004 fn active_network_faults_list(
1005 &self,
1006 f: &mut dyn FnMut(thread_diag::NetworkFaultEnum) -> Result<(), Error>,
1007 ) -> Result<(), Error> {
1008 self.net_ctl.active_network_faults_list(f)
1009 }
1010
1011 fn mac_counters(
1012 &self,
1013 f: &mut dyn FnMut(Option<&thread_diag::MacCounters>) -> Result<(), Error>,
1014 ) -> Result<(), Error> {
1015 self.net_ctl.mac_counters(f)
1016 }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021 use crate::dm::clusters::net_comm::{
1022 NetworksAccess, NetworksError, SharedNetworks, WirelessCreds,
1023 };
1024
1025 use super::wifi::{Wifi, WifiNetworks};
1026 use super::WirelessNetwork;
1027
1028 fn wifi_creds<'a>(ssid: &'a [u8], pass: &'a [u8]) -> WirelessCreds<'a> {
1031 WirelessCreds::Wifi { ssid, pass }
1032 }
1033
1034 fn collect_ssids(nets: &WifiNetworks<4>) -> Vec<Vec<u8>> {
1035 let mut ids = Vec::new();
1036 nets.networks(|n| {
1037 ids.push(n.id().to_vec());
1038 Ok(())
1039 })
1040 .unwrap();
1041 ids
1042 }
1043
1044 #[test]
1047 fn add_networks() {
1048 let mut nets = WifiNetworks::<4>::new();
1049
1050 let idx = nets
1051 .add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"PassA")), |_| {
1052 Ok(())
1053 })
1054 .unwrap();
1055 assert_eq!(idx, 0);
1056
1057 let idx = nets
1058 .add_or_update(b"B", Wifi::init_from(&wifi_creds(b"B", b"PassB")), |_| {
1059 Ok(())
1060 })
1061 .unwrap();
1062 assert_eq!(idx, 1);
1063
1064 assert_eq!(collect_ssids(&nets), vec![b"A".to_vec(), b"B".to_vec()]);
1065 }
1066
1067 #[test]
1068 fn update_existing_network() {
1069 let mut nets = WifiNetworks::<4>::new();
1070 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"Old")), |_| Ok(()))
1071 .unwrap();
1072
1073 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"New")), |wifi| {
1075 wifi.update(&wifi_creds(b"A", b"New"))
1076 })
1077 .unwrap();
1078
1079 assert_eq!(collect_ssids(&nets).len(), 1);
1081
1082 let mut pass = Vec::new();
1084 nets.network(b"A", |w| {
1085 if let WirelessCreds::Wifi { pass: p, .. } = w.creds() {
1086 pass.extend_from_slice(p);
1087 }
1088 Ok(())
1089 })
1090 .unwrap();
1091 assert_eq!(pass, b"New");
1092 }
1093
1094 #[test]
1095 fn add_exceeds_capacity() {
1096 let mut nets = WifiNetworks::<2>::new();
1097 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"p")), |_| Ok(()))
1098 .unwrap();
1099 nets.add_or_update(b"B", Wifi::init_from(&wifi_creds(b"B", b"p")), |_| Ok(()))
1100 .unwrap();
1101
1102 let err = nets.add_or_update(b"C", Wifi::init_from(&wifi_creds(b"C", b"p")), |_| Ok(()));
1103 assert!(matches!(err, Err(NetworksError::BoundsExceeded)));
1104 }
1105
1106 #[test]
1107 fn remove_network() {
1108 let mut nets = WifiNetworks::<4>::new();
1109 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"p")), |_| Ok(()))
1110 .unwrap();
1111 nets.add_or_update(b"B", Wifi::init_from(&wifi_creds(b"B", b"p")), |_| Ok(()))
1112 .unwrap();
1113
1114 let idx = nets.remove(b"A").unwrap();
1115 assert_eq!(idx, 0);
1116 assert_eq!(collect_ssids(&nets), vec![b"B".to_vec()]);
1117 }
1118
1119 #[test]
1120 fn remove_nonexistent() {
1121 let mut nets = WifiNetworks::<4>::new();
1122 assert!(matches!(
1123 nets.remove(b"X"),
1124 Err(NetworksError::NetworkIdNotFound)
1125 ));
1126 }
1127
1128 #[test]
1131 fn reorder_moves_to_front() {
1132 let mut nets = WifiNetworks::<4>::new();
1133 for id in [b"A", b"B", b"C"] {
1134 nets.add_or_update(
1135 id.as_slice(),
1136 Wifi::init_from(&wifi_creds(id, b"p")),
1137 |_| Ok(()),
1138 )
1139 .unwrap();
1140 }
1141
1142 nets.reorder(0, b"C").unwrap();
1144 assert_eq!(
1145 collect_ssids(&nets),
1146 vec![b"C".to_vec(), b"A".to_vec(), b"B".to_vec()]
1147 );
1148 }
1149
1150 #[test]
1151 fn reorder_out_of_range() {
1152 let mut nets = WifiNetworks::<4>::new();
1153 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"p")), |_| Ok(()))
1154 .unwrap();
1155
1156 assert!(matches!(
1157 nets.reorder(5, b"A"),
1158 Err(NetworksError::OutOfRange)
1159 ));
1160 }
1161
1162 #[test]
1163 fn reorder_nonexistent() {
1164 let mut nets = WifiNetworks::<4>::new();
1165 assert!(matches!(
1166 nets.reorder(0, b"X"),
1167 Err(NetworksError::NetworkIdNotFound)
1168 ));
1169 }
1170
1171 #[test]
1174 fn next_network_iterates_and_wraps() {
1175 let mut nets = WifiNetworks::<4>::new();
1176 for id in [b"A", b"B", b"C"] {
1177 nets.add_or_update(
1178 id.as_slice(),
1179 Wifi::init_from(&wifi_creds(id, b"p")),
1180 |_| Ok(()),
1181 )
1182 .unwrap();
1183 }
1184
1185 let get_next = |last: Option<&[u8]>| -> Option<Vec<u8>> {
1186 let mut result = None;
1187 let found = nets
1188 .next_network(last, |w| {
1189 result = Some(w.id().to_vec());
1190 Ok(())
1191 })
1192 .unwrap();
1193 if found {
1194 result
1195 } else {
1196 None
1197 }
1198 };
1199
1200 assert_eq!(get_next(None), Some(b"A".to_vec()));
1201 assert_eq!(get_next(Some(b"A")), Some(b"B".to_vec()));
1202 assert_eq!(get_next(Some(b"B")), Some(b"C".to_vec()));
1203 assert_eq!(get_next(Some(b"C")), Some(b"A".to_vec()));
1205 assert_eq!(get_next(Some(b"Z")), Some(b"A".to_vec()));
1207 }
1208
1209 #[test]
1210 fn next_network_empty_returns_false() {
1211 let nets = WifiNetworks::<4>::new();
1212 let found = nets.next_network(None, |_| Ok(())).unwrap();
1213 assert!(!found);
1214 }
1215
1216 #[test]
1219 fn store_load_round_trip() {
1220 let mut nets = WifiNetworks::<4>::new();
1221 nets.add_or_update(
1222 b"Net1",
1223 Wifi::init_from(&wifi_creds(b"Net1", b"P1")),
1224 |_| Ok(()),
1225 )
1226 .unwrap();
1227 nets.add_or_update(
1228 b"Net2",
1229 Wifi::init_from(&wifi_creds(b"Net2", b"P2")),
1230 |_| Ok(()),
1231 )
1232 .unwrap();
1233 nets.set_managed(true);
1234
1235 let mut buf = [0u8; 512];
1236 let len = nets.store(&mut buf).unwrap();
1237
1238 let mut loaded = WifiNetworks::<4>::new();
1239 loaded.load(&buf[..len]).unwrap();
1240
1241 assert_eq!(collect_ssids(&loaded), collect_ssids(&nets));
1242 assert!(loaded.managed());
1243 }
1244
1245 #[test]
1248 fn managed_default_false() {
1249 let nets = WifiNetworks::<4>::new();
1250 assert!(!nets.managed());
1251 }
1252
1253 #[test]
1254 fn set_managed() {
1255 let mut nets = WifiNetworks::<4>::new();
1256 nets.set_managed(true);
1257 assert!(nets.managed());
1258 nets.set_managed(false);
1259 assert!(!nets.managed());
1260 }
1261
1262 #[test]
1263 fn mutations_clear_managed() {
1264 let mut nets = WifiNetworks::<4>::new();
1265
1266 nets.set_managed(true);
1268 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"p")), |_| Ok(()))
1269 .unwrap();
1270 assert!(!nets.managed());
1271
1272 nets.set_managed(true);
1274 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"q")), |network| {
1275 network.update(&wifi_creds(b"A", b"q"))
1276 })
1277 .unwrap();
1278 assert!(!nets.managed());
1279
1280 nets.add_or_update(b"B", Wifi::init_from(&wifi_creds(b"B", b"p")), |_| Ok(()))
1282 .unwrap();
1283 nets.set_managed(true);
1284 nets.reorder(0, b"B").unwrap();
1285 assert!(!nets.managed());
1286
1287 nets.set_managed(true);
1289 nets.remove(b"B").unwrap();
1290 assert!(!nets.managed());
1291
1292 nets.set_managed(true);
1294 assert!(nets.remove(b"NOSUCH").is_err());
1295 assert!(nets.reorder(42, b"A").is_err());
1296 assert!(nets.managed());
1297 }
1298
1299 #[test]
1302 fn reset_clears_all() {
1303 let mut nets = WifiNetworks::<4>::new();
1304 nets.add_or_update(b"A", Wifi::init_from(&wifi_creds(b"A", b"p")), |_| Ok(()))
1305 .unwrap();
1306 nets.set_managed(true);
1307
1308 nets.reset();
1309 assert!(collect_ssids(&nets).is_empty());
1310 assert!(!nets.managed());
1311 }
1312
1313 #[test]
1316 fn shared_networks_access_add_and_read() {
1317 let shared = SharedNetworks::new(WifiNetworks::<4>::new());
1318
1319 shared.access(|networks| {
1320 networks
1321 .add_or_update(&wifi_creds(b"SSID1", b"pass1"))
1322 .unwrap();
1323 networks
1324 .add_or_update(&wifi_creds(b"SSID2", b"pass2"))
1325 .unwrap();
1326 });
1327
1328 let count = shared.access(|networks| {
1330 let mut count = 0u8;
1331 networks
1332 .networks(&mut |_info| {
1333 count += 1;
1334 Ok(())
1335 })
1336 .unwrap();
1337 count
1338 });
1339
1340 assert_eq!(count, 2);
1341 }
1342
1343 #[test]
1344 fn shared_networks_commissioned_via_access() {
1345 let shared = SharedNetworks::new(WifiNetworks::<4>::new());
1346
1347 let managed = shared.access(|networks| networks.managed().unwrap());
1348 assert!(!managed);
1349
1350 shared.access(|networks| networks.set_managed(true).unwrap());
1351
1352 let managed = shared.access(|networks| networks.managed().unwrap());
1353 assert!(managed);
1354 }
1355
1356 #[test]
1357 fn shared_networks_next_creds_round_robin() {
1358 let shared = SharedNetworks::new(WifiNetworks::<4>::new());
1359
1360 shared.access(|networks| {
1361 for (ssid, pass) in [(b"A", b"pA"), (b"B", b"pB"), (b"C", b"pC")] {
1362 networks
1363 .add_or_update(&wifi_creds(ssid.as_slice(), pass.as_slice()))
1364 .unwrap();
1365 }
1366 });
1367
1368 let get_next_ssid = |last: Option<&[u8]>| -> Option<Vec<u8>> {
1369 shared.access(|networks| {
1370 let mut result = None;
1371 let found = networks
1372 .next_creds(last, &mut |creds| {
1373 if let WirelessCreds::Wifi { ssid, .. } = creds {
1374 result = Some(ssid.to_vec());
1375 }
1376 Ok(())
1377 })
1378 .unwrap();
1379 if found {
1380 result
1381 } else {
1382 None
1383 }
1384 })
1385 };
1386
1387 assert_eq!(get_next_ssid(None), Some(b"A".to_vec()));
1388 assert_eq!(get_next_ssid(Some(b"A")), Some(b"B".to_vec()));
1389 assert_eq!(get_next_ssid(Some(b"C")), Some(b"A".to_vec()));
1390 }
1391
1392 #[test]
1395 fn load_old_format_bare_array() {
1396 use crate::tlv::{TLVTag, TLVWrite};
1397 use crate::utils::storage::WriteBuf;
1398
1399 let mut buf = [0u8; 512];
1403 let mut wb = WriteBuf::new(&mut buf);
1404
1405 wb.start_array(&TLVTag::Anonymous).unwrap();
1406
1407 wb.start_struct(&TLVTag::Anonymous).unwrap();
1409 wb.str(&TLVTag::Context(0), b"A").unwrap();
1410 wb.str(&TLVTag::Context(1), b"pA").unwrap();
1411 wb.end_container().unwrap();
1412
1413 wb.start_struct(&TLVTag::Anonymous).unwrap();
1415 wb.str(&TLVTag::Context(0), b"B").unwrap();
1416 wb.str(&TLVTag::Context(1), b"pB").unwrap();
1417 wb.end_container().unwrap();
1418
1419 wb.end_container().unwrap();
1420 let len = wb.get_tail();
1421
1422 let mut nets = WifiNetworks::<4>::new();
1424 nets.load(&buf[..len]).unwrap();
1425
1426 assert_eq!(collect_ssids(&nets), vec![b"A".to_vec(), b"B".to_vec()]);
1427 assert!(
1428 !nets.managed(),
1429 "Old format should default managed to false"
1430 );
1431 }
1432
1433 #[test]
1436 fn shared_networks_save_does_not_trigger_change() {
1437 use core::pin::pin;
1438 use embassy_futures::select::{select, Either};
1439
1440 let shared = SharedNetworks::new(WifiNetworks::<4>::new());
1441
1442 shared.access(|n| n.add_or_update(&wifi_creds(b"A", b"p")).unwrap());
1444
1445 embassy_futures::block_on(shared.wait_state_changed());
1447
1448 shared.access(|n| {
1450 let mut buf = [0u8; 512];
1451 n.save(&mut buf).unwrap();
1452 });
1453
1454 let notified = embassy_futures::block_on(async {
1459 match select(
1460 pin!(shared.wait_state_changed()),
1461 pin!(core::future::ready(())),
1462 )
1463 .await
1464 {
1465 Either::First(_) => true,
1466 Either::Second(_) => false,
1467 }
1468 });
1469
1470 assert!(!notified, "save() must not trigger change notification");
1471 }
1472}