Skip to main content

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