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