Skip to main content

rs_matter_stack/wireless/
wifi.rs

1use core::future::Future;
2use core::marker::PhantomData;
3use core::pin::pin;
4
5use embassy_futures::select::{select, select3, select4};
6
7use rs_matter::crypto::{Crypto, RngCore};
8use rs_matter::dm::clusters::gen_comm::CommPolicy;
9use rs_matter::dm::clusters::gen_diag::GenDiag;
10use rs_matter::dm::clusters::gen_diag::NetifDiag;
11use rs_matter::dm::clusters::net_comm::{NetCtl, NetCtlStatus, NetworkType};
12use rs_matter::dm::clusters::sw_diag::SwDiag;
13use rs_matter::dm::clusters::wifi_diag::WifiDiag;
14use rs_matter::dm::endpoints::{wifi_sys_handler, WifiSysHandler, ROOT_ENDPOINT_ID};
15use rs_matter::dm::networks::wireless::{self, NetCtlWithStatusImpl, NoopWirelessNetCtl};
16use rs_matter::dm::networks::NetChangeNotif;
17use rs_matter::dm::{ChainedHandler, DataModel, Endpoint, EpClMatcher};
18use rs_matter::error::Error;
19use rs_matter::persist::KvBlobStoreAccess;
20use rs_matter::root_endpoint;
21use rs_matter::transport::network::NoNetwork;
22use rs_matter::utils::select::Coalesce;
23
24use crate::mdns::Mdns;
25use crate::nal::NetStack;
26use crate::network::Embedding;
27use crate::wireless::{GattPeripheral, MatterStackWirelessTask, WirelessNetCtl};
28use crate::{pin_alloc, UserTask};
29
30use super::{Gatt, GattTask, PreexistingWireless, WirelessMatterStack};
31
32/// A type alias for a Matter stack running over Wifi (and BLE, during commissioning).
33pub type WifiMatterStack<'a, const B: usize, E = ()> =
34    WirelessMatterStack<'a, B, wireless::Wifi, E>;
35
36impl<const B: usize, E> WirelessMatterStack<'_, B, wireless::Wifi, E>
37where
38    E: Embedding,
39{
40    /// Run the Matter stack for an already pre-established wireless network where the BLE and the Wifi stacks can co-exist.
41    ///
42    /// # Arguments
43    /// - `net_stack` - a user-provided `NetStack` implementation
44    /// - `netif` - a user-provided `Netif` implementation
45    /// - `controller` - a user-provided `Controller` implementation
46    /// - `mdns` - a user-provided `Mdns` implementation
47    /// - `gatt` - a user-provided `GattPeripheral` implementation
48    /// - `crypto` - a user-provided `Crypto` implementation
49    /// - `handler` - a user-provided DM handler implementation
50    /// - `kv` - a user-provided `KvBlobStoreAccess` implementation
51    /// - `user` - a user-provided future that will be polled only when the netif interface is up
52    #[allow(clippy::too_many_arguments)]
53    pub async fn run_preex<'t, U, N, Q, D, G, C, H, K, X>(
54        &'t self,
55        net_stack: U,
56        netif: N,
57        net_ctl: Q,
58        mdns: D,
59        gatt: G,
60        crypto: C,
61        handler: H,
62        kv: K,
63        user: X,
64    ) -> impl Future<Output = Result<(), Error>> + 't
65    where
66        U: NetStack + 't,
67        N: NetifDiag + NetChangeNotif + 't,
68        Q: NetCtl + WifiDiag + NetChangeNotif + 't,
69        D: Mdns + 't,
70        G: GattPeripheral + 't,
71        C: Crypto + 't,
72        H: DataModel + 't,
73        K: KvBlobStoreAccess + 't,
74        X: UserTask + 't,
75    {
76        self.run_coex(
77            PreexistingWireless::new(net_stack, netif, net_ctl, mdns, gatt),
78            crypto,
79            handler,
80            kv,
81            user,
82        )
83    }
84
85    /// Run the Matter stack for a wireless network where the BLE and the Wifi stacks can co-exist.
86    ///
87    /// # Arguments
88    /// - `wifi` - a user-provided `WifiCoex` implementation
89    /// - `crypto` - a user-provided `Crypto` implementation
90    /// - `handler` - a user-provided DM handler implementation
91    /// - `kv` - a user-provided `KvBlobStoreAccess` implementation
92    /// - `user` - a user-provided future that will be polled only when the netif interface is up
93    pub async fn run_coex<W, C, H, K, U>(
94        &self,
95        mut wifi: W,
96        crypto: C,
97        handler: H,
98        kv: K,
99        user: U,
100    ) -> Result<(), Error>
101    where
102        W: WifiCoex,
103        C: Crypto,
104        H: DataModel,
105        K: KvBlobStoreAccess,
106        U: UserTask,
107    {
108        let _lock = self.run_lock.lock().await;
109
110        info!("Matter Stack memory: {}b", core::mem::size_of_val(self));
111
112        // Since this is the last code executed in the method, resetting the allocator should be safe
113        // because all boxes returned by it should be dropped by then
114        let _defer = scopeguard::guard((), |_| unsafe {
115            self.bump.reset();
116        });
117
118        self.matter().reset_transport()?;
119
120        let net_task = pin_alloc!(
121            self.bump,
122            self.run_wifi_coex(&mut wifi, crypto, handler, kv, user)
123        );
124
125        net_task.await
126    }
127
128    /// Run the Matter stack for a wireless network where the BLE and the Wifi stacks cannot co-exist.
129    ///
130    /// # Arguments
131    /// - `wifi` - a user-provided `Wifi` + `Gatt` implementation
132    /// - `crypto` - a user-provided `Crypto` implementation
133    /// - `handler` - a user-provided DM handler implementation
134    /// - `kv` - a user-provided `KvBlobStoreAccess` implementation
135    /// - `user` - a user-provided future that will be polled only when the netif interface is up
136    pub async fn run<W, C, H, K, U>(
137        &self,
138        wifi: W,
139        crypto: C,
140        handler: H,
141        kv: K,
142        user: U,
143    ) -> Result<(), Error>
144    where
145        W: Wifi + Gatt,
146        C: Crypto,
147        H: DataModel,
148        K: KvBlobStoreAccess,
149        U: UserTask,
150    {
151        let _lock = self.run_lock.lock().await;
152
153        info!("Matter Stack memory: {}b", core::mem::size_of_val(self));
154
155        // Since this is the last code executed in the method, resetting the allocator should be safe
156        // because all boxes returned by it should be dropped by then
157        let _defer = scopeguard::guard((), |_| unsafe {
158            self.bump.reset();
159        });
160
161        self.matter().reset_transport()?;
162
163        let net_task = pin_alloc!(self.bump, self.run_wifi(wifi, crypto, handler, kv, user));
164
165        net_task.await
166    }
167
168    async fn run_wifi_coex<W, C, H, K, U>(
169        &self,
170        wifi: &mut W,
171        crypto: C,
172        handler: H,
173        kv: K,
174        user: U,
175    ) -> Result<(), Error>
176    where
177        W: WifiCoex,
178        C: Crypto,
179        H: DataModel,
180        K: KvBlobStoreAccess,
181        U: UserTask,
182    {
183        // The coex task never builds a `WirelessNetCtl` chain via `Q`, so its
184        // phantom net-ctl type is an irrelevant placeholder.
185        // `&kv` is also lent to the driver so it can persist its own state.
186        wifi.run(
187            MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, NoopWirelessNetCtl> {
188                stack: self,
189                crypto,
190                handler,
191                kv,
192                user_task: user,
193                _net_ctl: PhantomData,
194            },
195        )
196        .await
197    }
198
199    async fn run_wifi<W, C, H, K, U>(
200        &self,
201        mut wifi: W,
202        crypto: C,
203        handler: H,
204        kv: K,
205        mut user: U,
206    ) -> Result<(), Error>
207    where
208        W: Wifi + Gatt,
209        C: Crypto,
210        H: DataModel,
211        K: KvBlobStoreAccess,
212        U: UserTask,
213    {
214        loop {
215            // BLE carries a commissioning window that the device opened for itself; one that an
216            // administrator re-opened over CASE is advertised over the operational IP network
217            // alone. Deliberately not `has_fabrics`: a commissioned device that re-opens a basic
218            // window has to become reachable over BLE again, and an uncommissioned one whose
219            // window has expired has nothing left to advertise.
220            if self
221                .matter()
222                .comm_window_state()
223                .is_open_on_all_transports()
224            {
225                self.reset_net_ctl_state();
226
227                Gatt::run(
228                    &mut wifi,
229                    MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, <W as Wifi>::NetCtl<'_>> {
230                        stack: self,
231                        crypto: &crypto,
232                        handler: &handler,
233                        kv: &kv,
234                        user_task: &mut user,
235                        _net_ctl: PhantomData,
236                    },
237                )
238                .await?;
239            }
240
241            Wifi::run(
242                &mut wifi,
243                MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, <W as Wifi>::NetCtl<'_>> {
244                    stack: self,
245                    crypto: &crypto,
246                    handler: &handler,
247                    kv: &kv,
248                    user_task: &mut user,
249                    _net_ctl: PhantomData,
250                },
251            )
252            .await?;
253        }
254    }
255
256    /// Return a metadata for the root (Endpoint 0) of the Matter Node
257    /// configured for BLE+Wifi network.
258    pub const fn root_endpoint() -> Endpoint<'static> {
259        const ENDPOINT: Endpoint<'static> = root_endpoint!(wifi);
260
261        ENDPOINT
262    }
263
264    /// Return a handler for the root (Endpoint 0) of the Matter Node
265    /// configured for BLE+Wifi network.
266    #[allow(clippy::too_many_arguments)]
267    fn root_handler<'a, C>(
268        &'a self,
269        comm_policy: &'a dyn CommPolicy,
270        gen_diag: &'a dyn GenDiag,
271        netif_diag: &'a dyn NetifDiag,
272        net_ctl: &'a C,
273        sw_diag: &'a dyn SwDiag,
274        rand: impl RngCore + Copy,
275    ) -> WifiSysHandler<'a, &'a C>
276    where
277        C: NetCtl + NetCtlStatus + WifiDiag,
278    {
279        wifi_sys_handler(
280            comm_policy,
281            gen_diag,
282            netif_diag,
283            net_ctl,
284            sw_diag,
285            net_ctl,
286            rand,
287        )
288    }
289}
290
291/// A trait representing a task that needs access to the operational wireless interface (Wifi or Thread)
292/// (Netif, UDP stack and Wireless controller) to perform its work.
293pub trait WifiTask {
294    /// Run the task with the given network stack, network interface, wireless controller and mDNS
295    async fn run<S, N, C, M>(
296        &mut self,
297        net_stack: S,
298        netif: N,
299        net_ctl: C,
300        mdns: M,
301    ) -> Result<(), Error>
302    where
303        S: NetStack,
304        N: NetifDiag + NetChangeNotif,
305        C: NetCtl + WifiDiag + NetChangeNotif,
306        M: Mdns;
307}
308
309impl<T> WifiTask for &mut T
310where
311    T: WifiTask,
312{
313    fn run<S, N, C, M>(
314        &mut self,
315        net_stack: S,
316        netif: N,
317        net_ctl: C,
318        mdns: M,
319    ) -> impl Future<Output = Result<(), Error>>
320    where
321        S: NetStack,
322        N: NetifDiag + NetChangeNotif,
323        C: NetCtl + WifiDiag + NetChangeNotif,
324        M: Mdns,
325    {
326        T::run(*self, net_stack, netif, net_ctl, mdns)
327    }
328}
329
330/// A trait for running a task within a context where the wireless interface is initialized and operable
331pub trait Wifi {
332    /// The Wifi network controller type this driver produces in its operational
333    /// phase. Naming it here lets the commissioning and operational handler chains
334    /// be built with the SAME `WirelessNetCtl<Self::NetCtl<'_>>` net-ctl type,
335    /// yielding a single handler-chain monomorphization. The bound is Wifi's own
336    /// (`WifiDiag`) — a Wifi controller is never asked to be a Thread one.
337    type NetCtl<'a>: NetCtl + WifiDiag + NetChangeNotif
338    where
339        Self: 'a;
340
341    /// Setup the radio to operate in wireless (Wifi or Thread) mode
342    /// and run the given task.
343    async fn run<T>(&mut self, task: T) -> Result<(), Error>
344    where
345        T: WifiTask;
346}
347
348impl<T> Wifi for &mut T
349where
350    T: Wifi,
351{
352    type NetCtl<'a>
353        = T::NetCtl<'a>
354    where
355        Self: 'a;
356
357    fn run<A>(&mut self, task: A) -> impl Future<Output = Result<(), Error>>
358    where
359        A: WifiTask,
360    {
361        T::run(self, task)
362    }
363}
364
365/// A trait representing a task that needs access to the operational wireless interface (Wifi or Thread)
366/// as well as to the commissioning BTP GATT peripheral.
367///
368/// Typically, tasks performing the Matter concurrent commissioning workflow will implement this trait.
369pub trait WifiCoexTask {
370    /// Run the task with the given network stack, network interface, wireless controller and mDNS
371    async fn run<S, N, C, M, G>(
372        &mut self,
373        net_stack: S,
374        netif: N,
375        net_ctl: C,
376        mdns: M,
377        gatt: G,
378    ) -> Result<(), Error>
379    where
380        S: NetStack,
381        N: NetifDiag + NetChangeNotif,
382        C: NetCtl + WifiDiag + NetChangeNotif,
383        M: Mdns,
384        G: GattPeripheral;
385}
386
387impl<T> WifiCoexTask for &mut T
388where
389    T: WifiCoexTask,
390{
391    fn run<S, N, C, M, G>(
392        &mut self,
393        net_stack: S,
394        netif: N,
395        net_ctl: C,
396        mdns: M,
397        gatt: G,
398    ) -> impl Future<Output = Result<(), Error>>
399    where
400        S: NetStack,
401        N: NetifDiag + NetChangeNotif,
402        C: NetCtl + WifiDiag + NetChangeNotif,
403        M: Mdns,
404        G: GattPeripheral,
405    {
406        T::run(*self, net_stack, netif, net_ctl, mdns, gatt)
407    }
408}
409
410/// A trait for running a task within a context where both the wireless interface (Thread or Wifi)
411/// is initialized and operable, as well as the BLE GATT peripheral is also operable.
412///
413/// Typically, tasks performing the Matter concurrent commissioning workflow will ran by implementations
414/// of this trait.
415pub trait WifiCoex {
416    /// Setup the radio to operate in wireless coexist mode (Wifi or Thread + BLE)
417    /// and run the given task.
418    async fn run<T>(&mut self, task: T) -> Result<(), Error>
419    where
420        T: WifiCoexTask;
421}
422
423impl<T> WifiCoex for &mut T
424where
425    T: WifiCoex,
426{
427    fn run<A>(&mut self, task: A) -> impl Future<Output = Result<(), Error>>
428    where
429        A: WifiCoexTask,
430    {
431        T::run(self, task)
432    }
433}
434
435impl<S, N, C, M, P> Wifi for PreexistingWireless<S, N, C, M, P>
436where
437    S: NetStack,
438    N: NetifDiag + NetChangeNotif,
439    C: NetCtl + WifiDiag + NetChangeNotif,
440    M: Mdns,
441{
442    // The task receives `&self.net_ctl` (a `&C`), so the chain net-ctl type is
443    // `&'a C` (which satisfies the bounds via the blanket `impl Trait for &T`).
444    type NetCtl<'a>
445        = &'a C
446    where
447        Self: 'a;
448
449    async fn run<T>(&mut self, mut task: T) -> Result<(), Error>
450    where
451        T: WifiTask,
452    {
453        task.run(&self.net_stack, &self.netif, &self.net_ctl, &mut self.mdns)
454            .await
455    }
456}
457
458impl<S, N, C, M, P> WifiCoex for PreexistingWireless<S, N, C, M, P>
459where
460    S: NetStack,
461    N: NetifDiag + NetChangeNotif,
462    C: NetCtl + WifiDiag + NetChangeNotif,
463    M: Mdns,
464    P: GattPeripheral,
465{
466    async fn run<T>(&mut self, mut task: T) -> Result<(), Error>
467    where
468        T: WifiCoexTask,
469    {
470        task.run(
471            &self.net_stack,
472            &self.netif,
473            &self.net_ctl,
474            &mut self.mdns,
475            &mut self.gatt,
476        )
477        .await
478    }
479}
480
481impl<'a, const B: usize, E, C, H, K, U, Q> GattTask
482    for MatterStackWirelessTask<'a, B, wireless::Wifi, E, C, H, K, U, Q>
483where
484    E: Embedding,
485    C: Crypto,
486    H: DataModel,
487    K: KvBlobStoreAccess,
488    Q: NetCtl + WifiDiag + NetChangeNotif,
489{
490    async fn run<P>(&mut self, peripheral: P) -> Result<(), Error>
491    where
492        P: GattPeripheral,
493    {
494        let net_ctl = NetCtlWithStatusImpl::new(
495            &self.stack.network.net_state,
496            WirelessNetCtl::<Q>::Commissioning(NetworkType::Wifi),
497        );
498
499        let sys =
500            self.stack
501                .root_handler(&false, &(), &(), &net_ctl, &(), self.crypto.weak_rand()?);
502        let combined = ChainedHandler::new(
503            EpClMatcher::new(Some(ROOT_ENDPOINT_ID), None),
504            sys,
505            &self.handler,
506        );
507        // The network store comes from the stack's `state`; the (commissioning)
508        // net-ctl is threaded into the engine, whose `run` keeps its connection
509        // manager dormant while not commissioned.
510        let im = self
511            .stack
512            .im(&self.crypto, (&self.handler, combined), &self.kv, &net_ctl);
513
514        let mut btp_task = pin!(self.stack.run_btp(&self.crypto, peripheral));
515
516        let mut im_task = pin!(self.stack.run_im(&im));
517
518        select(&mut btp_task, &mut im_task).coalesce().await
519    }
520}
521
522impl<'a, const B: usize, E, C, H, K, X, Z> WifiTask
523    for MatterStackWirelessTask<'a, B, wireless::Wifi, E, C, H, K, X, Z>
524where
525    E: Embedding,
526    C: Crypto,
527    H: DataModel,
528    K: KvBlobStoreAccess,
529    X: UserTask,
530    Z: NetCtl + WifiDiag + NetChangeNotif,
531{
532    async fn run<T, N, Q, D>(
533        &mut self,
534        net_stack: T,
535        netif: N,
536        net_ctl: Q,
537        mut mdns: D,
538    ) -> Result<(), Error>
539    where
540        T: NetStack,
541        N: NetifDiag + NetChangeNotif,
542        Q: NetCtl + WifiDiag + NetChangeNotif,
543        D: Mdns,
544    {
545        info!("Wifi driver started");
546
547        let net_ctl_s = NetCtlWithStatusImpl::new(
548            &self.stack.network.net_state,
549            WirelessNetCtl::Operational(&net_ctl),
550        );
551
552        let sys = self.stack.root_handler(
553            &false,
554            &(),
555            &netif,
556            &net_ctl_s,
557            &(),
558            self.crypto.weak_rand()?,
559        );
560        let combined = ChainedHandler::new(
561            EpClMatcher::new(Some(ROOT_ENDPOINT_ID), None),
562            sys,
563            &self.handler,
564        );
565        // The operational `net_ctl` is threaded into the engine, which now drives
566        // the maintenance `WirelessMgr` itself (against the stack's networks store).
567        let im = self.stack.im(
568            &self.crypto,
569            (&self.handler, combined),
570            &self.kv,
571            &net_ctl_s,
572        );
573
574        let stack = &self.stack;
575
576        let mut net_task = pin!(stack.run_oper_net(
577            &self.crypto,
578            &net_stack,
579            0, // TODO
580            core::future::pending(),
581            Option::<(NoNetwork, NoNetwork)>::None
582        ));
583
584        let mut mdns_task =
585            pin!(stack.run_oper_netif_mdns(&self.crypto, &net_stack, &netif, &mut mdns));
586
587        // Non-concurrent commissioning deferred connect. In BLE-only commissioning
588        // the commissioner's `ConnectNetwork` is deferred (Wifi can't run during
589        // BLE) and must be replayed once the operational network is up but *before*
590        // `CommissioningComplete`. The engine's maintenance manager only connects
591        // *after* the device is commissioned, so this one-shot connect is still
592        // performed here. The target network ID is the one the commissioner
593        // selected, remembered in `NetCtlState`.
594        let deferred_connect_id = self.stack.network.net_state.lock(|state| {
595            let state = state.borrow();
596            state.is_prov_ready().then(|| state.network_id.clone())
597        });
598
599        if let Some(network_id) = deferred_connect_id {
600            info!("Non-concurrent commissioning: performing the deferred connect");
601
602            // The engine owns the networks + net-ctl; ask it to replay the
603            // deferred connect (no stack-owned `WirelessMgr`).
604            im.connect_once(&network_id).await?;
605        }
606
607        let mut im_task = pin!(self.stack.run_im(&im));
608
609        let mut user_task = pin!(self.user_task.run(&net_stack, &netif));
610
611        let mut oper_task =
612            pin!(select4(&mut net_task, &mut mdns_task, &mut im_task, &mut user_task).coalesce());
613
614        // Hand the radio back to BLE once a commissioning window that has to be advertised
615        // there appears. The window still open from the commissioning this phase is finishing
616        // is not one of them - non-concurrent commissioning completes over the operational
617        // network, and only then does the window close - so wait for it to go away first, and
618        // only then for the next one to open.
619        let mut comm_window_task = pin!(async {
620            self.stack.wait_next_comm_window().await;
621
622            info!("Commissioning window opened; handing the radio back to BLE");
623
624            Ok(())
625        });
626
627        select(&mut oper_task, &mut comm_window_task)
628            .coalesce()
629            .await
630    }
631}
632
633impl<'a, const B: usize, E, C, H, K, X, Z> WifiCoexTask
634    for MatterStackWirelessTask<'a, B, wireless::Wifi, E, C, H, K, X, Z>
635where
636    E: Embedding,
637    C: Crypto,
638    H: DataModel,
639    K: KvBlobStoreAccess,
640    X: UserTask,
641    Z: NetCtl + WifiDiag + NetChangeNotif,
642{
643    async fn run<T, N, Q, D, G>(
644        &mut self,
645        net_stack: T,
646        netif: N,
647        net_ctl: Q,
648        mut mdns: D,
649        mut gatt: G,
650    ) -> Result<(), Error>
651    where
652        T: NetStack,
653        N: NetifDiag + NetChangeNotif,
654        Q: NetCtl + WifiDiag + NetChangeNotif,
655        D: Mdns,
656        G: GattPeripheral,
657    {
658        info!("Wifi and BLE drivers started");
659
660        let net_ctl_s = NetCtlWithStatusImpl::new(
661            &self.stack.network.net_state,
662            WirelessNetCtl::Operational(&net_ctl),
663        );
664
665        let sys = self.stack.root_handler(
666            &true,
667            &(),
668            &netif,
669            &net_ctl_s,
670            &(),
671            self.crypto.weak_rand()?,
672        );
673        let combined = ChainedHandler::new(
674            EpClMatcher::new(Some(ROOT_ENDPOINT_ID), None),
675            sys,
676            &self.handler,
677        );
678        // The operational `net_ctl` is threaded into the engine, which drives the
679        // maintenance `WirelessMgr` itself; `run_net_coex` only runs the BTP coex
680        // transport now.
681        let im = self.stack.im(
682            &self.crypto,
683            (&self.handler, combined),
684            &self.kv,
685            &net_ctl_s,
686        );
687
688        let stack = &self.stack;
689        let bump = &stack.bump;
690
691        let mut net_task = pin_alloc!(
692            bump,
693            stack.run_net_coex(&self.crypto, &net_stack, &netif, &mut mdns, &mut gatt)
694        );
695
696        let mut im_task = pin_alloc!(bump, self.stack.run_im_with_bump(&im));
697
698        let mut user_task = pin_alloc!(bump, self.user_task.run(&net_stack, &netif));
699
700        select3(&mut net_task, &mut im_task, &mut user_task)
701            .coalesce()
702            .await
703    }
704}