Skip to main content

rs_matter/dm/clusters/
net_comm.rs

1/*
2 *
3 *    Copyright (c) 2025-2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18//! This module contains the implementation of the Network Commissioning cluster and its handler.
19
20use core::fmt::{self, Debug};
21use core::future::{ready, Future};
22
23use crate::dm::clusters::gen_comm::GenCommHandler;
24use crate::dm::clusters::wifi_diag::WirelessDiag;
25use crate::dm::networks::wireless::{Thread, ThreadTLV, MAX_WIRELESS_NETWORK_ID_LEN};
26use crate::dm::networks::NetChangeNotif;
27use crate::dm::{ArrayAttributeRead, Cluster, Dataver, InvokeContext, ReadContext, WriteContext};
28use crate::error::{Error, ErrorCode};
29use crate::persist::{Persist, NETWORKS_KEY};
30use crate::tlv::{
31    Nullable, NullableBuilder, Octets, OctetsBuilder, TLVBuilder, TLVBuilderParent, TLVWrite,
32    ToTLVArrayBuilder, ToTLVBuilder,
33};
34use crate::utils::cell::RefCell;
35use crate::utils::init::{init, Init};
36use crate::utils::sync::blocking::Mutex;
37use crate::utils::sync::{DynBase, Notification};
38use crate::with;
39
40pub use crate::dm::clusters::decl::network_commissioning::*;
41
42/// Network type supported by the `NetCtl` implementations
43#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
44#[cfg_attr(feature = "defmt", derive(defmt::Format))]
45pub enum NetworkType {
46    Ethernet,
47    Wifi,
48    Thread,
49}
50
51impl NetworkType {
52    /// Return an instance of the Network Commissioning cluster meta-data for the given network type.
53    pub const fn cluster(&self) -> Cluster<'static> {
54        match self {
55            Self::Ethernet => FULL_CLUSTER
56                .with_features(Feature::ETHERNET_NETWORK_INTERFACE.bits())
57                .with_attrs(with!(required))
58                .with_cmds(with!()),
59            Self::Wifi => FULL_CLUSTER
60                .with_features(Feature::WI_FI_NETWORK_INTERFACE.bits())
61                .with_attrs(with!(required; AttributeId::ScanMaxTimeSeconds | AttributeId::ConnectMaxTimeSeconds | AttributeId::SupportedWiFiBands))
62                .with_cmds(with!(CommandId::AddOrUpdateWiFiNetwork | CommandId::ScanNetworks | CommandId::RemoveNetwork | CommandId::ConnectNetwork | CommandId::ReorderNetwork)),
63            Self::Thread => FULL_CLUSTER
64                .with_features(Feature::THREAD_NETWORK_INTERFACE.bits())
65                .with_attrs(with!(required; AttributeId::ScanMaxTimeSeconds | AttributeId::ConnectMaxTimeSeconds | AttributeId::ThreadVersion | AttributeId::SupportedThreadFeatures))
66                .with_cmds(with!(CommandId::AddOrUpdateThreadNetwork | CommandId::ScanNetworks | CommandId::RemoveNetwork | CommandId::ConnectNetwork | CommandId::ReorderNetwork)),
67        }
68    }
69}
70
71/// Read one entry of the `Networks` attribute into the given builder.
72///
73/// `connected` is not something the `Networks` store can answer - it is the
74/// network *controller* that knows which network is currently up - so it is
75/// supplied by the caller (see `NetCommHandler::networks`) rather than carried
76/// alongside the network ID.
77fn network_read_into<P: TLVBuilderParent>(
78    network_id: &[u8],
79    connected: bool,
80    builder: NetworkInfoStructBuilder<P>,
81) -> Result<P, Error> {
82    // `NetworkIdentifier` / `ClientIdentifier` are not part of
83    // `NetworkInfoStruct` in the Matter 1.6.0 data model: the Wi-Fi
84    // per-device-credentials surface moved to the separate
85    // `NetworkIdentityManagement` cluster. They exist again in the 1.6.1
86    // IDL, so the two lines below are kept, commented, to be restored
87    // together with `CSA_STANDARD_CLUSTERS_IDL_V1_6_1_0`.
88    builder
89        .network_id(Octets::new(network_id))?
90        .connected(connected)?
91        // .network_identifier(None)?
92        // .client_identifier(None)?
93        .end()
94}
95
96/// Network scan information as returned by the `NetCtl::scan` method
97#[derive(Debug, Clone, Eq, PartialEq, Hash)]
98#[cfg_attr(feature = "defmt", derive(defmt::Format))]
99pub enum NetworkScanInfo<'a> {
100    /// WiFi network scan information when the network type is `NetworkType::Wifi`
101    Wifi {
102        security: WiFiSecurityBitmap,
103        ssid: &'a [u8],
104        bssid: &'a [u8],
105        channel: u16,
106        band: WiFiBandEnum,
107        rssi: i8,
108    },
109    /// Thread network scan information when the network type is `NetworkType::Thread`
110    Thread {
111        pan_id: u16,
112        ext_pan_id: u64,
113        network_name: &'a str,
114        channel: u16,
115        version: u8,
116        ext_addr: &'a [u8],
117        rssi: i8,
118        lqi: u8,
119    },
120}
121
122impl NetworkScanInfo<'_> {
123    /// Read the network scan information into the given `NetworkScanInfoStructBuilder`.
124    /// If the network type is not `NetworkType::Wifi`, this method will panic.
125    pub fn wifi_read_into<P: TLVBuilderParent>(
126        &self,
127        builder: WiFiInterfaceScanResultStructBuilder<P>,
128    ) -> Result<P, Error> {
129        let NetworkScanInfo::Wifi {
130            security,
131            ssid,
132            bssid,
133            channel,
134            band,
135            rssi,
136        } = self
137        else {
138            panic!("Wifi scan info expected");
139        };
140
141        builder
142            .security(*security)?
143            .ssid(Octets::new(ssid))?
144            .bssid(Octets::new(bssid))?
145            .channel(*channel)?
146            .wi_fi_band(*band)?
147            .rssi(*rssi)?
148            .end()
149    }
150
151    /// Read the network scan information into the given `ThreadInterfaceScanResultStructBuilder`.
152    /// If the network type is not `NetworkType::Thread`, this method will panic.
153    pub fn thread_read_into<P: TLVBuilderParent>(
154        &self,
155        builder: ThreadInterfaceScanResultStructBuilder<P>,
156    ) -> Result<P, Error> {
157        let NetworkScanInfo::Thread {
158            pan_id,
159            ext_pan_id: extended_pan_id,
160            network_name,
161            channel,
162            version,
163            ext_addr,
164            rssi,
165            lqi,
166        } = self
167        else {
168            panic!("Thread scan info expected");
169        };
170
171        builder
172            .pan_id(*pan_id)?
173            .extended_pan_id(*extended_pan_id)?
174            .network_name(network_name)?
175            .channel(*channel)?
176            .version(*version)?
177            .extended_address(Octets::new(ext_addr))?
178            .rssi(*rssi)?
179            .lqi(*lqi)?
180            .end()
181    }
182}
183
184/// Wireless credentials used for connecting to a network
185#[derive(Debug, Clone, Eq, PartialEq, Hash)]
186pub enum WirelessCreds<'a> {
187    /// WiFi credentials
188    Wifi { ssid: &'a [u8], pass: &'a [u8] },
189    /// Thread credentials
190    Thread { dataset_tlv: &'a [u8] },
191}
192
193impl WirelessCreds<'_> {
194    /// Return the network ID of the credentials
195    /// For Wifi networks, this is the SSID
196    /// For Thread networks, this is the extended PAN ID
197    pub fn id(&self) -> Result<&[u8], Error> {
198        match self {
199            WirelessCreds::Wifi { ssid, .. } => Ok(ssid),
200            WirelessCreds::Thread { dataset_tlv } => Thread::dataset_ext_pan_id(dataset_tlv),
201        }
202    }
203
204    /// Check if the credentials match the given network type
205    pub fn check_match(&self, net_type: NetworkType) -> Result<(), Error> {
206        match self {
207            WirelessCreds::Wifi { .. } if matches!(net_type, NetworkType::Wifi) => Ok(()),
208            WirelessCreds::Thread { .. } if matches!(net_type, NetworkType::Thread) => Ok(()),
209            _ => Err(ErrorCode::InvalidAction.into()),
210        }
211    }
212}
213
214impl fmt::Display for WirelessCreds<'_> {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        match self {
217            WirelessCreds::Wifi { ssid, .. } => write!(
218                f,
219                "SSID({})",
220                core::str::from_utf8(ssid).ok().unwrap_or("???")
221            ),
222            WirelessCreds::Thread { dataset_tlv } => write!(
223                f,
224                "ExtEpanId({:?})",
225                ThreadTLV::new(dataset_tlv).ext_pan_id().ok().unwrap_or(&[])
226            ),
227        }
228    }
229}
230
231#[cfg(feature = "defmt")]
232impl defmt::Format for WirelessCreds<'_> {
233    fn format(&self, fmt: defmt::Formatter) {
234        match self {
235            WirelessCreds::Wifi { ssid, .. } => defmt::write!(
236                fmt,
237                "SSID({})",
238                core::str::from_utf8(ssid).ok().unwrap_or("???")
239            ),
240            WirelessCreds::Thread { dataset_tlv } => defmt::write!(
241                fmt,
242                "ExtEpanId({:?})",
243                ThreadTLV::new(dataset_tlv).ext_pan_id().ok().unwrap_or(&[])
244            ),
245        }
246    }
247}
248
249/// Network error type for the `Networks` trait
250#[derive(Debug)]
251#[cfg_attr(feature = "defmt", derive(defmt::Format))]
252pub enum NetworksError {
253    NetworkIdNotFound,
254    DuplicateNetworkId,
255    OutOfRange,
256    BoundsExceeded,
257    Other(Error),
258}
259
260impl From<Error> for NetworksError {
261    fn from(err: Error) -> Self {
262        NetworksError::Other(err)
263    }
264}
265
266/// Network error type for the `NetCtl` trait
267#[derive(Debug)]
268#[cfg_attr(feature = "defmt", derive(defmt::Format))]
269pub enum NetCtlError {
270    NetworkNotFound,
271    UnsupportedSecurity,
272    AuthFailure,
273    OtherConnectionFailure,
274    IpBindFailed,
275    IpV6Failed,
276    Other(Error),
277}
278
279impl From<Error> for NetCtlError {
280    fn from(err: Error) -> Self {
281        NetCtlError::Other(err)
282    }
283}
284
285impl NetworkCommissioningStatusEnum {
286    /// Map the result of a network storage operation to a `NetworkCommissioningStatusEnum` if the operation
287    /// failed, or return the index of the network if it succeeded.
288    pub fn map<T>(
289        result: Result<T, NetworksError>,
290    ) -> Result<(NetworkCommissioningStatusEnum, Option<i32>, Option<T>), Error> {
291        if let Some((status, err_code)) = NetworkCommissioningStatusEnum::map_status(&result) {
292            Ok((status, err_code, result.ok()))
293        } else {
294            match result {
295                Err(NetworksError::Other(e)) => Err(e),
296                _ => unreachable!(),
297            }
298        }
299    }
300
301    /// Map the result of a network storage operation to a `NetworkCommissioningStatusEnum` and error code  if the operation
302    /// failed, or return the index of the network if it succeeded.
303    pub fn map_status<T>(
304        result: &Result<T, NetworksError>,
305    ) -> Option<(NetworkCommissioningStatusEnum, Option<i32>)> {
306        match result {
307            Ok(_) => Some((NetworkCommissioningStatusEnum::Success, None)),
308            Err(NetworksError::NetworkIdNotFound) => {
309                Some((NetworkCommissioningStatusEnum::NetworkIDNotFound, None))
310            }
311            Err(NetworksError::DuplicateNetworkId) => {
312                Some((NetworkCommissioningStatusEnum::DuplicateNetworkID, None))
313            }
314            Err(NetworksError::OutOfRange) => {
315                Some((NetworkCommissioningStatusEnum::OutOfRange, None))
316            }
317            Err(NetworksError::BoundsExceeded) => {
318                Some((NetworkCommissioningStatusEnum::BoundsExceeded, None))
319            }
320            Err(NetworksError::Other(_)) => None,
321        }
322    }
323
324    /// Map the result of a network IO operation to a `NetworkCommissioningStatusEnum` if the operation
325    /// failed, or return the index of the network if it succeeded.
326    pub fn map_ctl<T>(
327        result: Result<T, NetCtlError>,
328    ) -> Result<(NetworkCommissioningStatusEnum, Option<i32>, Option<T>), Error> {
329        if let Some((status, err_code)) = NetworkCommissioningStatusEnum::map_ctl_status(&result) {
330            Ok((status, err_code, result.ok()))
331        } else {
332            match result {
333                Err(NetCtlError::Other(e)) => Err(e),
334                _ => unreachable!(),
335            }
336        }
337    }
338
339    /// Map the result of a network IO operation to a `NetworkCommissioningStatusEnum` and error code  if the operation
340    /// failed, or return the index of the network if it succeeded.
341    pub fn map_ctl_status<T>(
342        result: &Result<T, NetCtlError>,
343    ) -> Option<(NetworkCommissioningStatusEnum, Option<i32>)> {
344        match result {
345            Ok(_) => Some((NetworkCommissioningStatusEnum::Success, None)),
346            Err(NetCtlError::UnsupportedSecurity) => {
347                Some((NetworkCommissioningStatusEnum::UnsupportedSecurity, None))
348            }
349            Err(NetCtlError::AuthFailure) => {
350                Some((NetworkCommissioningStatusEnum::AuthFailure, None))
351            }
352            Err(NetCtlError::IpBindFailed) => {
353                Some((NetworkCommissioningStatusEnum::IPBindFailed, None))
354            }
355            Err(NetCtlError::IpV6Failed) => {
356                Some((NetworkCommissioningStatusEnum::IPV6Failed, None))
357            }
358            Err(NetCtlError::OtherConnectionFailure) => {
359                Some((NetworkCommissioningStatusEnum::OtherConnectionFailure, None))
360            }
361            Err(NetCtlError::NetworkNotFound) => {
362                Some((NetworkCommissioningStatusEnum::NetworkNotFound, None))
363            }
364            Err(NetCtlError::Other(_)) => None,
365        }
366    }
367
368    /// Read the networking status and the provided optional index into the given `NetworkConfigResponseBuilder`.
369    pub fn read_into<P: TLVBuilderParent>(
370        &self,
371        index: Option<u8>,
372        builder: NetworkConfigResponseBuilder<P>,
373    ) -> Result<P, Error> {
374        // As with `NetworkInfoStruct`, `ClientIdentity` / `PossessionSignature`
375        // left `NetworkConfigResponse` in the 1.6.0 data model. Kept commented
376        // for the eventual move back to 1.6.1.
377        builder
378            .networking_status(*self)?
379            .debug_text(None)?
380            .network_index(index)?
381            // .client_identity(None)?
382            // .possession_signature(None)?
383            .end()
384    }
385}
386
387/// Trait for managing networks' credentials storage
388pub trait Networks {
389    /// Return the maximum number of networks supported by the implementation
390    ///
391    /// For `NetworkType::Ethernet` this method should always return 1
392    fn max_networks(&self) -> Result<u8, Error>;
393
394    /// Iterate over the networks recorded in the implementation and call the provided function for each network
395    fn networks(&self, f: &mut dyn FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error>;
396
397    /// Get the credentials for the given network ID by calling the provided function
398    ///
399    /// For `NetworkType::Ethernet` this method should always fail with an error.
400    ///
401    /// The function will be called with the credentials for the network ID, or an error if the network ID is not found.
402    ///
403    /// Return the index of the network ID if found, or an error if not found.
404    fn creds(
405        &self,
406        network_id: &[u8],
407        f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
408    ) -> Result<u8, NetworksError>;
409
410    /// Return the next credentials after the ones corresponding to the given network ID by calling the provided function
411    ///
412    /// For `NetworkType::Ethernet` this method should always fail with an error.
413    ///
414    /// If the network ID is `None` or credentials with the provided network ID cannot be found,
415    /// the first credentials will be returned.
416    ///
417    /// If the credentials corresponding to the network ID are the last ones recorded in the `Netwrks` trait implementation,
418    /// the method will wrap-over and will return the first credentials or even the same credentials again if there is only one
419    /// recorded network.
420    ///
421    /// Return `true` if the credentials were found, `false` otherwise.
422    fn next_creds(
423        &self,
424        last_network_id: Option<&[u8]>,
425        f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
426    ) -> Result<bool, Error>;
427
428    /// Return whether the network interface is enabled
429    fn enabled(&self) -> Result<bool, Error>;
430
431    /// Set the network interface enabled or disabled
432    fn set_enabled(&mut self, enabled: bool) -> Result<(), Error>;
433
434    /// Add or update the credentials for the given network ID
435    ///
436    /// For `NetworkType::Ethernet` this method should always fail with an error.
437    ///
438    /// The network ID is derived from the credentials.
439    ///
440    /// Return the index of the network ID if it was added or updated, or an error if the operation failed.
441    fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError>;
442
443    /// Reorder the network with the given index
444    ///
445    /// For `NetworkType::Ethernet` this method should always fail with an error.
446    ///
447    /// The index is the new index of the network ID.
448    ///
449    /// Return the index of the network ID if it was reordered, or an error if the operation failed.
450    fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError>;
451
452    /// Remove the network with the given network ID
453    ///
454    /// For `NetworkType::Ethernet` this method should always fail with an error.
455    ///
456    /// Return the index of the network ID if it was removed, or an error if the operation failed.
457    fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError>;
458
459    /// Return whether the networks store is in "managed" state.
460    ///
461    /// Managed means: the store holds *committed* operational network
462    /// configuration, and the operational connectivity manager
463    /// (`WirelessMgr`) may act on it. The store starts unmanaged (never
464    /// commissioned, empty), and temporarily *becomes* unmanaged again while
465    /// network changes are staged under an armed fail-safe (any successful
466    /// `AddOrUpdate*Network` / `RemoveNetwork` / `ReorderNetwork` /
467    /// `ConnectNetwork` flips it to `false`) - during such a window the
468    /// commissioner drives connectivity explicitly, and the manager must
469    /// stand down.
470    ///
471    /// The state comes back to managed on either exit from the window:
472    /// - commit: `CommissioningComplete` calls `set_managed(true)` and
473    ///   persists the store;
474    /// - revert: the fail-safe expiry reloads the persisted store, which
475    ///   always carries the committed - hence managed - state.
476    ///
477    /// Note that a window which stages no network changes (e.g. opened only
478    /// to commission an additional fabric) leaves the store managed, so
479    /// connectivity maintenance of the operational network continues
480    /// throughout - matching CHIP's behavior.
481    fn managed(&self) -> Result<bool, Error>;
482
483    /// Set the managed state of the networks store (see `managed`).
484    fn set_managed(&mut self, managed: bool) -> Result<(), Error>;
485
486    /// Reset the networks to the initial state, removing all recorded network credentials
487    fn reset(&mut self) -> Result<(), Error>;
488
489    /// Load the networks' credentials from the given data
490    fn load(&mut self, data: &[u8]) -> Result<(), Error>;
491
492    /// Save the networks' credentials into the given buffer and return the number of bytes written
493    /// or `None` if the networks do not need persistence.
494    fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error>;
495}
496
497impl<T> Networks for &mut T
498where
499    T: Networks,
500{
501    fn max_networks(&self) -> Result<u8, Error> {
502        (**self).max_networks()
503    }
504
505    fn networks(&self, f: &mut dyn FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error> {
506        (**self).networks(f)
507    }
508
509    fn creds(
510        &self,
511        network_id: &[u8],
512        f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
513    ) -> Result<u8, NetworksError> {
514        (**self).creds(network_id, f)
515    }
516
517    fn next_creds(
518        &self,
519        last_network_id: Option<&[u8]>,
520        f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
521    ) -> Result<bool, Error> {
522        (**self).next_creds(last_network_id, f)
523    }
524
525    fn enabled(&self) -> Result<bool, Error> {
526        (**self).enabled()
527    }
528
529    fn set_enabled(&mut self, enabled: bool) -> Result<(), Error> {
530        (*self).set_enabled(enabled)
531    }
532
533    fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
534        (*self).add_or_update(creds)
535    }
536
537    fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
538        (*self).reorder(index, network_id)
539    }
540
541    fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
542        (*self).remove(network_id)
543    }
544
545    fn managed(&self) -> Result<bool, Error> {
546        (**self).managed()
547    }
548
549    fn set_managed(&mut self, managed: bool) -> Result<(), Error> {
550        (**self).set_managed(managed)
551    }
552
553    fn reset(&mut self) -> Result<(), Error> {
554        (**self).reset()
555    }
556
557    fn load(&mut self, data: &[u8]) -> Result<(), Error> {
558        (**self).load(data)
559    }
560
561    fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
562        (**self).save(buf)
563    }
564}
565
566impl Networks for &mut dyn Networks {
567    fn max_networks(&self) -> Result<u8, Error> {
568        (**self).max_networks()
569    }
570
571    fn networks(&self, f: &mut dyn FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error> {
572        (**self).networks(f)
573    }
574
575    fn creds(
576        &self,
577        network_id: &[u8],
578        f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
579    ) -> Result<u8, NetworksError> {
580        (**self).creds(network_id, f)
581    }
582
583    fn next_creds(
584        &self,
585        last_network_id: Option<&[u8]>,
586        f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
587    ) -> Result<bool, Error> {
588        (**self).next_creds(last_network_id, f)
589    }
590
591    fn enabled(&self) -> Result<bool, Error> {
592        (**self).enabled()
593    }
594
595    fn set_enabled(&mut self, enabled: bool) -> Result<(), Error> {
596        (**self).set_enabled(enabled)
597    }
598
599    fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
600        (**self).add_or_update(creds)
601    }
602
603    fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
604        (**self).reorder(index, network_id)
605    }
606
607    fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
608        (**self).remove(network_id)
609    }
610
611    fn managed(&self) -> Result<bool, Error> {
612        (**self).managed()
613    }
614
615    fn set_managed(&mut self, managed: bool) -> Result<(), Error> {
616        (**self).set_managed(managed)
617    }
618
619    fn reset(&mut self) -> Result<(), Error> {
620        (**self).reset()
621    }
622
623    fn load(&mut self, data: &[u8]) -> Result<(), Error> {
624        (**self).load(data)
625    }
626
627    fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
628        (**self).save(buf)
629    }
630}
631
632pub trait NetworksAccess {
633    fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R;
634}
635
636impl<T> NetworksAccess for &T
637where
638    T: NetworksAccess,
639{
640    fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R {
641        (*self).access(f)
642    }
643}
644
645pub struct DummyNetworkAccess;
646
647impl NetworksAccess for DummyNetworkAccess {
648    fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R {
649        f(&mut DummyNetworks)
650    }
651}
652
653pub struct DummyNetworks;
654
655impl Networks for DummyNetworks {
656    fn max_networks(&self) -> Result<u8, Error> {
657        Ok(0)
658    }
659
660    fn networks(&self, _f: &mut dyn FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error> {
661        Ok(())
662    }
663
664    fn creds(
665        &self,
666        _network_id: &[u8],
667        _f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
668    ) -> Result<u8, NetworksError> {
669        Err(NetworksError::NetworkIdNotFound)
670    }
671
672    fn next_creds(
673        &self,
674        _last_network_id: Option<&[u8]>,
675        _f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
676    ) -> Result<bool, Error> {
677        Ok(false)
678    }
679
680    fn enabled(&self) -> Result<bool, Error> {
681        Ok(false)
682    }
683
684    fn set_enabled(&mut self, _enabled: bool) -> Result<(), Error> {
685        Ok(())
686    }
687
688    fn add_or_update(&mut self, _creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
689        Err(NetworksError::Other(ErrorCode::InvalidAction.into()))
690    }
691
692    fn reorder(&mut self, _index: u8, _network_id: &[u8]) -> Result<u8, NetworksError> {
693        Err(NetworksError::Other(ErrorCode::InvalidAction.into()))
694    }
695
696    fn remove(&mut self, _network_id: &[u8]) -> Result<u8, NetworksError> {
697        Err(NetworksError::Other(ErrorCode::InvalidAction.into()))
698    }
699
700    fn managed(&self) -> Result<bool, Error> {
701        Ok(false)
702    }
703
704    fn set_managed(&mut self, _managed: bool) -> Result<(), Error> {
705        Ok(())
706    }
707
708    fn reset(&mut self) -> Result<(), Error> {
709        Ok(())
710    }
711
712    fn load(&mut self, _data: &[u8]) -> Result<(), Error> {
713        Ok(())
714    }
715
716    fn save(&self, _buf: &mut [u8]) -> Result<Option<usize>, Error> {
717        Ok(None)
718    }
719}
720
721/// Trait for managing network connectivity
722pub trait NetCtl {
723    /// Return the network type of the implementation
724    fn net_type(&self) -> NetworkType;
725
726    /// Return the maximum time in seconds for connecting to a network
727    ///
728    /// Default implementation returns 30 seconds
729    fn connect_max_time_seconds(&self) -> u8 {
730        30
731    }
732
733    /// Return the maximum time in seconds for scanning for networks
734    ///
735    /// Default implementation returns 30 seconds
736    fn scan_max_time_seconds(&self) -> u8 {
737        30
738    }
739
740    /// Return the supported WiFi bands for the implementation in the provided closure
741    ///
742    /// Default implementation returns 2.4GHz band only
743    ///
744    /// NOTE: This method is only relevant when `net_type` is `NetworkType::Wifi`
745    fn supported_wifi_bands<F>(&self, mut f: F) -> Result<(), Error>
746    where
747        F: FnMut(WiFiBandEnum) -> Result<(), Error>,
748    {
749        f(WiFiBandEnum::V2G4)
750    }
751
752    /// Return the supported Thread features for the implementation
753    ///
754    /// Default implementation returns an empty bitmap
755    ///
756    /// NOTE: This method is only relevant when `net_type` is `NetworkType::Thread`
757    fn supported_thread_features(&self) -> ThreadCapabilitiesBitmap {
758        ThreadCapabilitiesBitmap::empty()
759    }
760
761    /// Return the Thread version for the implementation
762    ///
763    /// Default implementation returns 4 (Thread 1.3.0)
764    ///
765    /// NOTE: This method is only relevant when `net_type` is `NetworkType::Thread`
766    fn thread_version(&self) -> u16 {
767        4
768    }
769
770    /// Scan for networks and call the provided function for each network found
771    ///
772    /// For `NetworkType::Ethernet` this method should always fail with an error.
773    async fn scan<F>(&self, network: Option<&[u8]>, f: F) -> Result<(), NetCtlError>
774    where
775        F: FnMut(&NetworkScanInfo) -> Result<(), Error>;
776
777    /// Connect to the network with the given credentials
778    ///
779    /// For `NetworkType::Ethernet` this method should always fail with an error.
780    async fn connect(&self, creds: &WirelessCreds) -> Result<(), NetCtlError>;
781}
782
783impl<T> NetCtl for &T
784where
785    T: NetCtl,
786{
787    fn net_type(&self) -> NetworkType {
788        (*self).net_type()
789    }
790
791    fn connect_max_time_seconds(&self) -> u8 {
792        (*self).connect_max_time_seconds()
793    }
794
795    fn scan_max_time_seconds(&self) -> u8 {
796        (*self).scan_max_time_seconds()
797    }
798
799    fn supported_wifi_bands<F>(&self, f: F) -> Result<(), Error>
800    where
801        F: FnMut(WiFiBandEnum) -> Result<(), Error>,
802    {
803        (*self).supported_wifi_bands(f)
804    }
805
806    fn supported_thread_features(&self) -> ThreadCapabilitiesBitmap {
807        (*self).supported_thread_features()
808    }
809
810    fn thread_version(&self) -> u16 {
811        (*self).thread_version()
812    }
813
814    fn scan<F>(&self, network: Option<&[u8]>, f: F) -> impl Future<Output = Result<(), NetCtlError>>
815    where
816        F: FnMut(&NetworkScanInfo) -> Result<(), Error>,
817    {
818        (*self).scan(network, f)
819    }
820
821    fn connect(&self, creds: &WirelessCreds<'_>) -> impl Future<Output = Result<(), NetCtlError>> {
822        (*self).connect(creds)
823    }
824}
825
826/// Trait for providing the status of the last `scan` / `connect` operation
827pub trait NetCtlStatus {
828    /// Return the networking status of the last scan or connect operation
829    ///
830    /// For `NetworkType::Ethernet` this method should always return `Ok(None)`
831    fn last_networking_status(&self) -> Result<Option<NetworkCommissioningStatusEnum>, Error>;
832
833    /// Return the network ID of the last connect operation
834    ///
835    /// For `NetworkType::Ethernet` this method should always return `Ok(None)`
836    fn last_network_id<F, R>(&self, f: F) -> Result<R, Error>
837    where
838        F: FnOnce(Option<&[u8]>) -> Result<R, Error>;
839
840    /// Return the error value of the last connect operation
841    ///
842    /// For `NetworkType::Ethernet` this method should always return `Ok(None)`
843    fn last_connect_error_value(&self) -> Result<Option<i32>, Error>;
844}
845
846impl<T> NetCtlStatus for &T
847where
848    T: NetCtlStatus,
849{
850    fn last_networking_status(&self) -> Result<Option<NetworkCommissioningStatusEnum>, Error> {
851        (*self).last_networking_status()
852    }
853
854    fn last_network_id<F, R>(&self, f: F) -> Result<R, Error>
855    where
856        F: FnOnce(Option<&[u8]>) -> Result<R, Error>,
857    {
858        (*self).last_network_id(f)
859    }
860
861    fn last_connect_error_value(&self) -> Result<Option<i32>, Error> {
862        (*self).last_connect_error_value()
863    }
864}
865
866/// A type providing shared access to a `Networks` implementation with change notification capabilities.
867pub struct SharedNetworks<N> {
868    state: Mutex<RefCell<N>>,
869    state_changed: Notification,
870}
871
872impl<N> SharedNetworks<N> {
873    /// Create a new instance.
874    pub const fn new(networks: N) -> Self {
875        Self {
876            state: Mutex::new(RefCell::new(networks)),
877            state_changed: Notification::new(),
878        }
879    }
880
881    /// Return an in-place initializer for the struct.
882    pub fn init(networks: impl Init<N>) -> impl Init<Self> {
883        init!(Self {
884            state <- Mutex::init(RefCell::init(networks)),
885            state_changed <- Notification::init(),
886        })
887    }
888
889    /// Get a mutable reference to the inner `Networks` implementation.
890    pub fn get_mut(&mut self) -> &mut RefCell<N> {
891        self.state.get_mut()
892    }
893
894    /// Run a closure with mutable access to the raw inner `Networks`
895    /// implementation, without firing a change notification.
896    ///
897    /// Used by the startup / factory-reset persistence paths, where nothing is
898    /// subscribed to changes yet (or the notification is meaningless).
899    pub(crate) fn with_raw<F, R>(&self, f: F) -> R
900    where
901        F: FnOnce(&mut N) -> R,
902    {
903        self.state.lock(|state| f(&mut state.borrow_mut()))
904    }
905
906    /// Wait for the state to change.
907    pub fn wait_state_changed(&self) -> impl Future<Output = ()> + '_ {
908        self.state_changed.wait()
909    }
910}
911
912impl<N> DynBase for SharedNetworks<N> where N: Send {}
913
914impl<N> NetworksAccess for SharedNetworks<N>
915where
916    N: Networks,
917{
918    fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R {
919        self.state.lock(|state| {
920            let mut networks = state.borrow_mut();
921
922            let mut instance = SharedNetworksInstance {
923                networks: &mut *networks,
924                changed: &self.state_changed,
925            };
926
927            f(&mut instance)
928        })
929    }
930}
931
932impl<N> NetChangeNotif for SharedNetworks<N> {
933    fn wait_changed(&self) -> impl Future<Output = ()> {
934        self.state_changed.wait()
935    }
936}
937
938/// A wrapper around a `Networks` implementation that notifies on changes to the networks state.
939pub struct SharedNetworksInstance<'a> {
940    networks: &'a mut dyn Networks,
941    changed: &'a Notification,
942}
943
944impl Networks for SharedNetworksInstance<'_> {
945    fn max_networks(&self) -> Result<u8, Error> {
946        self.networks.max_networks()
947    }
948
949    fn networks(&self, f: &mut dyn FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error> {
950        self.networks.networks(f)
951    }
952
953    fn creds(
954        &self,
955        network_id: &[u8],
956        f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
957    ) -> Result<u8, NetworksError> {
958        self.networks.creds(network_id, f)
959    }
960
961    fn next_creds(
962        &self,
963        last_network_id: Option<&[u8]>,
964        f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
965    ) -> Result<bool, Error> {
966        self.networks.next_creds(last_network_id, f)
967    }
968
969    fn enabled(&self) -> Result<bool, Error> {
970        self.networks.enabled()
971    }
972
973    fn set_enabled(&mut self, enabled: bool) -> Result<(), Error> {
974        self.networks.set_enabled(enabled)?;
975
976        self.changed.notify();
977
978        Ok(())
979    }
980
981    fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
982        let index = self.networks.add_or_update(creds)?;
983
984        self.changed.notify();
985
986        Ok(index)
987    }
988
989    fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
990        let index = self.networks.reorder(index, network_id)?;
991
992        self.changed.notify();
993
994        Ok(index)
995    }
996
997    fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
998        let index = self.networks.remove(network_id)?;
999
1000        self.changed.notify();
1001
1002        Ok(index)
1003    }
1004
1005    fn managed(&self) -> Result<bool, Error> {
1006        self.networks.managed()
1007    }
1008
1009    fn set_managed(&mut self, managed: bool) -> Result<(), Error> {
1010        self.networks.set_managed(managed)?;
1011
1012        self.changed.notify();
1013
1014        Ok(())
1015    }
1016
1017    fn reset(&mut self) -> Result<(), Error> {
1018        self.networks.reset()?;
1019
1020        self.changed.notify();
1021
1022        Ok(())
1023    }
1024
1025    fn load(&mut self, data: &[u8]) -> Result<(), Error> {
1026        self.networks.load(data)?;
1027
1028        // As for every other mutator: `load` replaces the whole store state.
1029        // It is notably how the fail-safe expiry *reverts* staged network
1030        // changes (reloading the committed blob from the KV store) - without
1031        // the notification, a `WirelessMgr` parked on the store would sleep
1032        // through the revert and never reconcile connectivity with the
1033        // restored networks.
1034        self.changed.notify();
1035
1036        Ok(())
1037    }
1038
1039    fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
1040        let len = self.networks.save(buf)?;
1041
1042        Ok(len)
1043    }
1044}
1045
1046/// The system implementation of a handler for the Network Commissioning Matter cluster.
1047#[derive(Clone)]
1048pub struct NetCommHandler<'a, T> {
1049    dataver: Dataver,
1050    net_ctl: T,
1051    /// Tells whether the node currently has a network up. Combined with the
1052    /// last-connected network ID from `NetCtlStatus`, this is what the
1053    /// `Connected` field of each `Networks` entry is derived from - the
1054    /// `Networks` store itself has no way of knowing.
1055    wireless_diag: &'a dyn WirelessDiag,
1056}
1057
1058impl<'a, T> NetCommHandler<'a, T> {
1059    /// Create a new instance of `NetCommHandler` with the given `Dataver`,
1060    /// `NetCtl` and `WirelessDiag`.
1061    pub const fn new(dataver: Dataver, net_ctl: T, wireless_diag: &'a dyn WirelessDiag) -> Self {
1062        Self {
1063            dataver,
1064            net_ctl,
1065            wireless_diag,
1066        }
1067    }
1068
1069    /// Apply the `Breadcrumb` field of a Network Commissioning command.
1070    ///
1071    /// The field is optional, and only a command that *succeeded* may touch the
1072    /// `GeneralCommissioning::Breadcrumb` attribute - a failed command has to
1073    /// leave it exactly as it was.
1074    fn apply_breadcrumb(
1075        ctx: &impl InvokeContext,
1076        status: NetworkCommissioningStatusEnum,
1077        breadcrumb: Option<u64>,
1078    ) -> Result<(), Error> {
1079        if !matches!(status, NetworkCommissioningStatusEnum::Success) {
1080            return Ok(());
1081        }
1082
1083        let Some(breadcrumb) = breadcrumb else {
1084            return Ok(());
1085        };
1086
1087        ctx.exchange().with_state(|state| {
1088            state.failsafe.set_breadcrumb(breadcrumb);
1089
1090            Ok(())
1091        })
1092    }
1093
1094    /// Whether the `Networks` attribute currently has any entry.
1095    ///
1096    /// `LastNetworkingStatus`, `LastNetworkID` and `LastConnectErrorValue` all
1097    /// read as null while no network configurations exist, regardless of what
1098    /// happened before the last one was removed.
1099    fn has_networks(ctx: &impl ReadContext) -> Result<bool, Error> {
1100        ctx.networks().access(|networks| {
1101            let mut any = false;
1102
1103            networks.networks(&mut |_| {
1104                any = true;
1105
1106                Ok(())
1107            })?;
1108
1109            Ok(any)
1110        })
1111    }
1112
1113    /// Adapt the handler instance to the generic `rs-matter` `AsyncHandler` trait
1114    pub const fn adapt(self) -> HandlerAsyncAdaptor<Self> {
1115        HandlerAsyncAdaptor(self)
1116    }
1117}
1118
1119impl<T> ClusterAsyncHandler for NetCommHandler<'_, T>
1120where
1121    T: NetCtl + NetCtlStatus,
1122{
1123    const CLUSTER: Cluster<'static> = NetworkType::Ethernet.cluster(); // TODO
1124
1125    fn dataver(&self) -> u32 {
1126        self.dataver.get()
1127    }
1128
1129    fn dataver_changed(&self) {
1130        self.dataver.changed();
1131    }
1132
1133    fn max_networks(&self, ctx: impl ReadContext) -> impl Future<Output = Result<u8, Error>> {
1134        ready(ctx.networks().access(|networks| networks.max_networks()))
1135    }
1136
1137    fn connect_max_time_seconds(
1138        &self,
1139        _ctx: impl ReadContext,
1140    ) -> impl Future<Output = Result<u8, Error>> {
1141        ready(Ok(self.net_ctl.connect_max_time_seconds()))
1142    }
1143
1144    fn scan_max_time_seconds(
1145        &self,
1146        _ctx: impl ReadContext,
1147    ) -> impl Future<Output = Result<u8, Error>> {
1148        ready(Ok(self.net_ctl.scan_max_time_seconds()))
1149    }
1150
1151    fn supported_wi_fi_bands<P: TLVBuilderParent>(
1152        &self,
1153        _ctx: impl ReadContext,
1154        builder: ArrayAttributeRead<
1155            ToTLVArrayBuilder<P, WiFiBandEnum>,
1156            ToTLVBuilder<P, WiFiBandEnum>,
1157        >,
1158    ) -> impl Future<Output = Result<P, Error>> {
1159        ready(match builder {
1160            ArrayAttributeRead::ReadAll(builder) => builder.with(|builder| {
1161                let mut builder = Some(builder);
1162
1163                self.net_ctl.supported_wifi_bands(|band| {
1164                    builder = Some(unwrap!(builder.take()).push(&band)?);
1165
1166                    Ok(())
1167                })?;
1168
1169                unwrap!(builder.take()).end()
1170            }),
1171            ArrayAttributeRead::ReadOne(index, builder) => {
1172                let mut current = 0;
1173                let mut builder = Some(builder);
1174                let mut parent = None;
1175
1176                match self.net_ctl.supported_wifi_bands(|band| {
1177                    if current == index {
1178                        parent = Some(unwrap!(builder.take()).set(&band)?);
1179                    }
1180
1181                    current += 1;
1182
1183                    Ok(())
1184                }) {
1185                    Err(e) => Err(e),
1186                    Ok(()) => {
1187                        if let Some(parent) = parent {
1188                            Ok(parent)
1189                        } else {
1190                            Err(ErrorCode::ConstraintError.into())
1191                        }
1192                    }
1193                }
1194            }
1195            ArrayAttributeRead::ReadNone(builder) => builder.end(),
1196        })
1197    }
1198
1199    fn supported_thread_features(
1200        &self,
1201        _ctx: impl ReadContext,
1202    ) -> impl Future<Output = Result<ThreadCapabilitiesBitmap, Error>> {
1203        ready(Ok(self.net_ctl.supported_thread_features()))
1204    }
1205
1206    fn thread_version(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u16, Error>> {
1207        ready(Ok(self.net_ctl.thread_version()))
1208    }
1209
1210    fn networks<P: TLVBuilderParent>(
1211        &self,
1212        ctx: impl ReadContext,
1213        builder: ArrayAttributeRead<NetworkInfoStructArrayBuilder<P>, NetworkInfoStructBuilder<P>>,
1214    ) -> impl Future<Output = Result<P, Error>> {
1215        // A network reads as connected when the node has a network up *and* the
1216        // last network it connected to is this one. The liveness half has to
1217        // come from the diagnostics hook rather than `LastNetworkingStatus`,
1218        // which records the last operation and stays `Success` even after the
1219        // link has since dropped.
1220        let connected = |network_id: &[u8]| -> Result<bool, Error> {
1221            if !self.wireless_diag.connected()? {
1222                return Ok(false);
1223            }
1224
1225            self.net_ctl
1226                .last_network_id(|last| Ok(last == Some(network_id)))
1227        };
1228
1229        ready(ctx.networks().access(|networks| match builder {
1230            ArrayAttributeRead::ReadAll(builder) => builder.with(|builder| {
1231                let mut builder = Some(builder);
1232
1233                networks.networks(&mut |network_id| {
1234                    builder = Some(network_read_into(
1235                        network_id,
1236                        connected(network_id)?,
1237                        unwrap!(builder.take()).push()?,
1238                    )?);
1239
1240                    Ok(())
1241                })?;
1242
1243                unwrap!(builder.take()).end()
1244            }),
1245            ArrayAttributeRead::ReadOne(index, builder) => {
1246                let mut current = 0;
1247                let mut builder = Some(builder);
1248                let mut parent = None;
1249
1250                networks.networks(&mut |network_id| {
1251                    if current == index {
1252                        parent = Some(network_read_into(
1253                            network_id,
1254                            connected(network_id)?,
1255                            unwrap!(builder.take()),
1256                        )?);
1257                    }
1258
1259                    current += 1;
1260
1261                    Ok(())
1262                })?;
1263
1264                if let Some(parent) = parent {
1265                    Ok(parent)
1266                } else {
1267                    Err(ErrorCode::ConstraintError.into())
1268                }
1269            }
1270            ArrayAttributeRead::ReadNone(builder) => builder.end(),
1271        }))
1272    }
1273
1274    fn interface_enabled(
1275        &self,
1276        ctx: impl ReadContext,
1277    ) -> impl Future<Output = Result<bool, Error>> {
1278        ready(ctx.networks().access(|networks| networks.enabled()))
1279    }
1280
1281    fn last_networking_status(
1282        &self,
1283        ctx: impl ReadContext,
1284    ) -> impl Future<Output = Result<Nullable<NetworkCommissioningStatusEnum>, Error>> {
1285        ready((|| {
1286            if !Self::has_networks(&ctx)? {
1287                return Ok(Nullable::none());
1288            }
1289
1290            self.net_ctl.last_networking_status().map(Nullable::new)
1291        })())
1292    }
1293
1294    fn last_network_id<P: TLVBuilderParent>(
1295        &self,
1296        ctx: impl ReadContext,
1297        builder: NullableBuilder<P, OctetsBuilder<P>>,
1298    ) -> impl Future<Output = Result<P, Error>> {
1299        ready((|| {
1300            if !Self::has_networks(&ctx)? {
1301                return builder.null();
1302            }
1303
1304            self.net_ctl.last_network_id(|network_id| {
1305                if let Some(network_id) = network_id {
1306                    builder.non_null()?.set(Octets::new(network_id))
1307                } else {
1308                    builder.null()
1309                }
1310            })
1311        })())
1312    }
1313
1314    fn last_connect_error_value(
1315        &self,
1316        ctx: impl ReadContext,
1317    ) -> impl Future<Output = Result<Nullable<i32>, Error>> {
1318        ready((|| {
1319            if !Self::has_networks(&ctx)? {
1320                return Ok(Nullable::none());
1321            }
1322
1323            self.net_ctl.last_connect_error_value().map(Nullable::new)
1324        })())
1325    }
1326
1327    async fn set_interface_enabled(
1328        &self,
1329        ctx: impl WriteContext,
1330        value: bool,
1331    ) -> Result<(), Error> {
1332        let mut persist = Persist::new(ctx.kv());
1333
1334        ctx.exchange().with_state(|state| {
1335            ctx.networks().access(|networks| {
1336                networks.set_enabled(value)?;
1337
1338                // NOTE: Not sure this is a spec-compliant behavor:
1339                // If the failsafe is armed for _any_ fabric, we'll NOT persist the network changes until commissioning is complete.
1340                // And we'll LOSE those changes if the failsafe times out before commissioning completes.
1341                if !state.failsafe.is_armed() {
1342                    persist.store(NETWORKS_KEY, |buf| networks.save(buf))?;
1343                }
1344
1345                Ok(())
1346            })
1347        })?;
1348
1349        persist.run()
1350    }
1351
1352    async fn handle_scan_networks<P: TLVBuilderParent>(
1353        &self,
1354        ctx: impl InvokeContext,
1355        request: ScanNetworksRequest<'_>,
1356        response: ScanNetworksResponseBuilder<P>,
1357    ) -> Result<P, Error> {
1358        match self.net_ctl.net_type() {
1359            NetworkType::Thread => {
1360                let mut builder = Some(response);
1361                let mut array_builder = None;
1362
1363                let (status, _, _) = NetworkCommissioningStatusEnum::map_ctl(
1364                    self.net_ctl
1365                        .scan(
1366                            request
1367                                .ssid()?
1368                                .as_ref()
1369                                .and_then(|ssid| ssid.as_opt_ref())
1370                                .map(|ssid| ssid.0),
1371                            |network| {
1372                                let abuilder = if let Some(builder) = builder.take() {
1373                                    builder
1374                                        .networking_status(NetworkCommissioningStatusEnum::Success)?
1375                                        .debug_text(None)?
1376                                        .wi_fi_scan_results()?
1377                                        .none()
1378                                        .thread_scan_results()?
1379                                        .some()?
1380                                } else {
1381                                    unwrap!(array_builder.take())
1382                                };
1383
1384                                array_builder = Some(network.thread_read_into(abuilder.push()?)?);
1385
1386                                Ok(())
1387                            },
1388                        )
1389                        .await
1390                        .map(|_| 0),
1391                )?;
1392
1393                Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1394
1395                if let Some(builder) = builder {
1396                    builder
1397                        .networking_status(status)?
1398                        .debug_text(None)?
1399                        .wi_fi_scan_results()?
1400                        .none()
1401                        .thread_scan_results()?
1402                        .none()
1403                        .end()
1404                } else {
1405                    unwrap!(array_builder.take()).end()?.end()
1406                }
1407            }
1408            NetworkType::Wifi => {
1409                let mut builder = Some(response);
1410                let mut array_builder = None;
1411
1412                let (status, _, _) = NetworkCommissioningStatusEnum::map_ctl(
1413                    self.net_ctl
1414                        .scan(
1415                            request
1416                                .ssid()?
1417                                .as_ref()
1418                                .and_then(|ssid| ssid.as_opt_ref())
1419                                .map(|ssid| ssid.0),
1420                            |network| {
1421                                let abuilder = if let Some(builder) = builder.take() {
1422                                    builder
1423                                        .networking_status(NetworkCommissioningStatusEnum::Success)?
1424                                        .debug_text(None)?
1425                                        .wi_fi_scan_results()?
1426                                        .some()?
1427                                } else {
1428                                    unwrap!(array_builder.take())
1429                                };
1430
1431                                array_builder = Some(network.wifi_read_into(abuilder.push()?)?);
1432
1433                                Ok(())
1434                            },
1435                        )
1436                        .await
1437                        .map(|_| 0),
1438                )?;
1439
1440                Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1441
1442                if let Some(builder) = builder {
1443                    builder
1444                        .networking_status(status)?
1445                        .debug_text(None)?
1446                        .wi_fi_scan_results()?
1447                        .none()
1448                        .thread_scan_results()?
1449                        .none()
1450                        .end()
1451                } else {
1452                    unwrap!(array_builder.take())
1453                        .end()?
1454                        .thread_scan_results()?
1455                        .none()
1456                        .end()
1457                }
1458            }
1459            NetworkType::Ethernet => Err(ErrorCode::InvalidAction.into()),
1460        }
1461    }
1462
1463    async fn handle_add_or_update_wi_fi_network<P: TLVBuilderParent>(
1464        &self,
1465        ctx: impl InvokeContext,
1466        request: AddOrUpdateWiFiNetworkRequest<'_>,
1467        response: NetworkConfigResponseBuilder<P>,
1468    ) -> Result<P, Error> {
1469        let (status, _, index) = NetworkCommissioningStatusEnum::map(
1470            GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1471                ctx.networks().access(|networks| {
1472                    let index = networks.add_or_update(&WirelessCreds::Wifi {
1473                        ssid: request.ssid()?.0,
1474                        pass: request.credentials()?.0,
1475                    })?;
1476
1477                    Ok(index)
1478                })
1479            }),
1480        )?;
1481
1482        Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1483
1484        // Networks list mutated
1485        ctx.notify_own_cluster_changed();
1486
1487        status.read_into(index, response)
1488    }
1489
1490    async fn handle_add_or_update_thread_network<P: TLVBuilderParent>(
1491        &self,
1492        ctx: impl InvokeContext,
1493        request: AddOrUpdateThreadNetworkRequest<'_>,
1494        response: NetworkConfigResponseBuilder<P>,
1495    ) -> Result<P, Error> {
1496        let (status, _, index) = NetworkCommissioningStatusEnum::map(
1497            GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1498                ctx.networks().access(|networks| {
1499                    let index = networks.add_or_update(&WirelessCreds::Thread {
1500                        dataset_tlv: request.operational_dataset()?.0,
1501                    })?;
1502
1503                    Ok(index)
1504                })
1505            }),
1506        )?;
1507
1508        Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1509
1510        // Networks list mutated
1511        ctx.notify_own_cluster_changed();
1512
1513        status.read_into(index, response)
1514    }
1515
1516    async fn handle_remove_network<P: TLVBuilderParent>(
1517        &self,
1518        ctx: impl InvokeContext,
1519        request: RemoveNetworkRequest<'_>,
1520        response: NetworkConfigResponseBuilder<P>,
1521    ) -> Result<P, Error> {
1522        let (status, _, index) = NetworkCommissioningStatusEnum::map(
1523            GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1524                ctx.networks().access(|networks| {
1525                    let index = networks.remove(request.network_id()?.0)?;
1526
1527                    Ok(index)
1528                })
1529            }),
1530        )?;
1531
1532        Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1533
1534        // Networks list mutated
1535        ctx.notify_own_cluster_changed();
1536
1537        status.read_into(index, response)
1538    }
1539
1540    async fn handle_connect_network<P: TLVBuilderParent>(
1541        &self,
1542        ctx: impl InvokeContext,
1543        request: ConnectNetworkRequest<'_>,
1544        mut response: ConnectNetworkResponseBuilder<P>,
1545    ) -> Result<P, Error> {
1546        if request.network_id()?.0.len() > MAX_WIRELESS_NETWORK_ID_LEN {
1547            return Err(ErrorCode::ConstraintError.into());
1548        }
1549
1550        let (status, err_code) = match self.net_ctl.net_type() {
1551            NetworkType::Thread => {
1552                let dataset_buf = response.writer().available_space();
1553                let mut dataset_len = 0;
1554
1555                let (mut status, mut err_code, _) = NetworkCommissioningStatusEnum::map(
1556                    GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1557                        ctx.networks().access(|networks| {
1558                            let index = networks.creds(request.network_id()?.0, &mut |creds| {
1559                                let WirelessCreds::Thread { dataset_tlv } = creds else {
1560                                    error!("Thread creds expected");
1561                                    return Err(ErrorCode::InvalidAction.into());
1562                                };
1563
1564                                if dataset_tlv.len() > dataset_buf.len() {
1565                                    error!("Dataset too large");
1566                                    return Err(ErrorCode::ConstraintError.into());
1567                                }
1568
1569                                dataset_buf[..dataset_tlv.len()].copy_from_slice(dataset_tlv);
1570                                dataset_len = dataset_tlv.len();
1571
1572                                Ok(())
1573                            })?;
1574
1575                            // `ConnectNetwork` is a staged, fail-safe-gated
1576                            // change like the list mutations: flip the store
1577                            // to unmanaged so the `WirelessMgr` stands down
1578                            // for the rest of the window instead of racing
1579                            // the connect we are about to perform.
1580                            networks.set_managed(false)?;
1581
1582                            Ok(index)
1583                        })
1584                    }),
1585                )?;
1586
1587                if matches!(status, NetworkCommissioningStatusEnum::Success) {
1588                    (status, err_code, _) = NetworkCommissioningStatusEnum::map_ctl(
1589                        self.net_ctl
1590                            .connect(&WirelessCreds::Thread {
1591                                dataset_tlv: &dataset_buf[..dataset_len],
1592                            })
1593                            .await,
1594                    )?;
1595                }
1596
1597                (status, err_code)
1598            }
1599            NetworkType::Wifi => {
1600                let buf = response.writer().available_space();
1601                let (ssid_buf, pass_buf) = buf.split_at_mut(buf.len() / 2);
1602                let mut ssid_len = 0;
1603                let mut pass_len = 0;
1604
1605                let (mut status, mut err_code, _) = NetworkCommissioningStatusEnum::map(
1606                    GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1607                        ctx.networks().access(|networks| {
1608                            let index = networks.creds(request.network_id()?.0, &mut |creds| {
1609                                let WirelessCreds::Wifi { ssid, pass } = creds else {
1610                                    error!("Wifi creds expected");
1611                                    return Err(ErrorCode::InvalidAction.into());
1612                                };
1613
1614                                if ssid.len() > ssid_buf.len() {
1615                                    error!("SSID too large");
1616                                    return Err(ErrorCode::ConstraintError.into());
1617                                }
1618
1619                                if pass.len() > pass_buf.len() {
1620                                    error!("Password too large");
1621                                    return Err(ErrorCode::ConstraintError.into());
1622                                }
1623
1624                                ssid_buf[..ssid.len()].copy_from_slice(ssid);
1625                                ssid_len = ssid.len();
1626                                pass_buf[..pass.len()].copy_from_slice(pass);
1627                                pass_len = pass.len();
1628
1629                                Ok(())
1630                            })?;
1631
1632                            // As for the Thread branch: `ConnectNetwork` is a
1633                            // staged, fail-safe-gated change.
1634                            networks.set_managed(false)?;
1635
1636                            Ok(index)
1637                        })
1638                    }),
1639                )?;
1640
1641                if matches!(status, NetworkCommissioningStatusEnum::Success) {
1642                    (status, err_code, _) = NetworkCommissioningStatusEnum::map_ctl(
1643                        self.net_ctl
1644                            .connect(&WirelessCreds::Wifi {
1645                                ssid: &ssid_buf[..ssid_len],
1646                                pass: &pass_buf[..pass_len],
1647                            })
1648                            .await,
1649                    )?;
1650                }
1651
1652                (status, err_code)
1653            }
1654            NetworkType::Ethernet => {
1655                return Err(ErrorCode::InvalidAction.into());
1656            }
1657        };
1658
1659        Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1660
1661        // LastNetworkingStatus / LastNetworkID / LastConnectErrorValue mutated
1662        ctx.notify_own_cluster_changed();
1663
1664        response
1665            .networking_status(status)?
1666            .debug_text(None)?
1667            .error_value(Nullable::new(err_code))?
1668            .end()
1669    }
1670
1671    async fn handle_reorder_network<P: TLVBuilderParent>(
1672        &self,
1673        ctx: impl InvokeContext,
1674        request: ReorderNetworkRequest<'_>,
1675        response: NetworkConfigResponseBuilder<P>,
1676    ) -> Result<P, Error> {
1677        let (status, _, index) = NetworkCommissioningStatusEnum::map(
1678            GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1679                ctx.networks().access(|networks| {
1680                    let index =
1681                        networks.reorder(request.network_index()? as _, request.network_id()?.0)?;
1682
1683                    Ok(index)
1684                })
1685            }),
1686        )?;
1687
1688        Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1689
1690        // Networks order mutated
1691        ctx.notify_own_cluster_changed();
1692
1693        status.read_into(index, response)
1694    }
1695
1696    // `QueryIdentity` is not a `NetworkCommissioning` command in the Matter
1697    // 1.6.0 data model - it moved to the new (provisional)
1698    // `NetworkIdentityManagement` cluster, along with the rest of the Wi-Fi
1699    // per-device-credentials surface. The 1.6.1 IDL has it back on this
1700    // cluster, so the implementation is kept here, commented, to be restored
1701    // together with `CSA_STANDARD_CLUSTERS_IDL_V1_6_1_0`.
1702    //
1703    // fn handle_query_identity<P: TLVBuilderParent>(
1704    //     &self,
1705    //     _ctx: impl InvokeContext,
1706    //     _request: QueryIdentityRequest<'_>,
1707    //     _response: QueryIdentityResponseBuilder<P>,
1708    // ) -> impl Future<Output = Result<P, Error>> {
1709    //     ready(Err(ErrorCode::CommandNotFound.into()))
1710    // }
1711}
1712
1713impl<T> Debug for NetCommHandler<'_, T> {
1714    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1715        f.debug_struct("NetCommHandler")
1716            .field("dataver", &self.dataver.get())
1717            .finish()
1718    }
1719}
1720
1721#[cfg(feature = "defmt")]
1722impl<T> defmt::Format for NetCommHandler<'_, T> {
1723    fn format(&self, f: defmt::Formatter) {
1724        defmt::write!(f, "NetCommHandler {{ dataver: {} }}", self.dataver.get());
1725    }
1726}