Skip to main content

rs_matter_stack/
lib.rs

1#![no_std]
2#![allow(async_fn_in_trait)]
3#![allow(unknown_lints)]
4#![allow(renamed_and_removed_lints)]
5#![allow(unexpected_cfgs)]
6#![allow(clippy::declare_interior_mutable_const)]
7#![allow(clippy::uninlined_format_args)]
8#![warn(clippy::large_futures)]
9#![warn(clippy::large_stack_frames)]
10#![warn(clippy::large_types_passed_by_value)]
11
12use core::cell::Cell;
13use core::fmt::Debug;
14use core::future::Future;
15use core::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6};
16use core::pin::pin;
17
18use cfg_if::cfg_if;
19
20use edge_nal::{UdpBind, UdpSplitMulticast};
21
22use embassy_futures::select::{select, select_slice};
23use embassy_time::Duration;
24
25use rs_matter::crypto::Crypto;
26use rs_matter::dm::clusters::basic_info::BasicInfoConfig;
27use rs_matter::dm::clusters::dev_att::DeviceAttestation;
28use rs_matter::dm::clusters::gen_diag::NetifDiag;
29use rs_matter::dm::clusters::net_comm::{NetCtl, NetCtlStatus, Networks};
30use rs_matter::dm::clusters::wifi_diag::WirelessDiag;
31use rs_matter::dm::networks::NetChangeNotif;
32use rs_matter::dm::{AttrChangeNotifier, AttrId, ClusterId, DataModel, EndptId};
33use rs_matter::error::{Error, ErrorCode};
34use rs_matter::im::{InteractionModel, InteractionModelState};
35use rs_matter::pairing::qr::QrTextType;
36use rs_matter::persist::{KvBlobStore, KvBlobStoreAccess};
37use rs_matter::respond::{DefaultResponder, ExchangeHandler, Responder};
38use rs_matter::sc::pase::MAX_COMM_WINDOW_TIMEOUT_SECS;
39use rs_matter::transport::exchange::MatterBuffers;
40use rs_matter::transport::network::{
41    Address, ChainedNetwork, NetworkMulticast, NetworkReceive, NetworkSend, NoNetwork,
42};
43use rs_matter::utils::init::{init, Init};
44use rs_matter::utils::select::Coalesce;
45use rs_matter::utils::sync::blocking::Mutex;
46use rs_matter::utils::sync::{DynBase, IfMutex};
47use rs_matter::{BasicCommData, Matter, MATTER_PORT};
48
49use crate::bump::Bump;
50use crate::mdns::Mdns;
51use crate::nal::NetStack;
52use crate::network::Network;
53
54#[cfg(feature = "std")]
55#[allow(unused_imports)]
56#[macro_use]
57extern crate std;
58
59#[allow(unused_imports)]
60#[macro_use]
61extern crate alloc;
62
63// This mod MUST go first, so that the others see its macros.
64pub(crate) mod fmt;
65
66pub mod ble;
67pub mod bump;
68pub mod eth;
69pub mod matter;
70pub mod mdns;
71pub mod nal;
72pub mod network;
73pub mod rand;
74pub mod udp;
75pub mod utils;
76pub mod wireless;
77
78mod private {
79    /// A marker super-trait for sealed traits
80    pub trait Sealed {}
81
82    impl Sealed for () {}
83}
84
85cfg_if! {
86    if #[cfg(feature = "max-subscriptions-32")] {
87        /// Max number of subscriptions
88        const MAX_SUBSCRIPTIONS: usize = 32;
89    } else if #[cfg(feature = "max-subscriptions-16")] {
90        /// Max number of subscriptions
91        const MAX_SUBSCRIPTIONS: usize = 16;
92    } else if #[cfg(feature = "max-subscriptions-8")] {
93        /// Max number of subscriptions
94        const MAX_SUBSCRIPTIONS: usize = 8;
95    } else if #[cfg(feature = "max-subscriptions-7")] {
96        /// Max number of subscriptions
97        const MAX_SUBSCRIPTIONS: usize = 7;
98    } else if #[cfg(feature = "max-subscriptions-6")] {
99        /// Max number of subscriptions
100        const MAX_SUBSCRIPTIONS: usize = 6;
101    } else if #[cfg(feature = "max-subscriptions-5")] {
102        /// Max number of subscriptions
103        const MAX_SUBSCRIPTIONS: usize = 5;
104    } else if #[cfg(feature = "max-subscriptions-4")] {
105        /// Max number of subscriptions
106        const MAX_SUBSCRIPTIONS: usize = 4;
107    } else if #[cfg(feature = "max-subscriptions-3")] {
108        /// Max number of subscriptions
109        const MAX_SUBSCRIPTIONS: usize = 3;
110    } else if #[cfg(feature = "max-subscriptions-2")] {
111        /// Max number of subscriptions
112        const MAX_SUBSCRIPTIONS: usize = 2;
113    } else if #[cfg(feature = "max-subscriptions-1")] {
114        /// Max number of subscriptions
115        const MAX_SUBSCRIPTIONS: usize = 1;
116    } else {
117        /// Max number of subscriptions
118        const MAX_SUBSCRIPTIONS: usize = 3;
119    }
120}
121
122cfg_if! {
123    if #[cfg(feature = "events-ringbuf-size-2048")] {
124        /// Events ringbuf size
125        const EVENTS_RINGBUF_SIZE: usize = 2048;
126    } else if #[cfg(feature = "events-ringbuf-size-1024")] {
127        /// Events ringbuf size
128        const EVENTS_RINGBUF_SIZE: usize = 1024;
129    } else if #[cfg(feature = "events-ringbuf-size-512")] {
130        /// Events ringbuf size
131        const EVENTS_RINGBUF_SIZE: usize = 512;
132    } else if #[cfg(feature = "events-ringbuf-size-256")] {
133        /// Events ringbuf size
134        const EVENTS_RINGBUF_SIZE: usize = 256;
135    } else if #[cfg(feature = "events-ringbuf-size-128")] {
136        /// Events ringbuf size
137        const EVENTS_RINGBUF_SIZE: usize = 128;
138    } else if #[cfg(feature = "events-ringbuf-size-64")] {
139        /// Events ringbuf size
140        const EVENTS_RINGBUF_SIZE: usize = 64;
141    } else if #[cfg(feature = "events-ringbuf-size-0")] {
142        /// Events ringbuf size
143        const EVENTS_RINGBUF_SIZE: usize = 0;
144    } else {
145        /// Events ringbuf size
146        const EVENTS_RINGBUF_SIZE: usize = 256;
147    }
148}
149
150cfg_if! {
151    if #[cfg(feature = "max-im-buffers-64")] {
152        /// Max number of IM buffers
153        const MAX_IM_BUFFERS: usize = 64;
154    } else if #[cfg(feature = "max-im-buffers-32")] {
155        /// Max number of IM buffers
156        const MAX_IM_BUFFERS: usize = 32;
157    } else if #[cfg(feature = "max-im-buffers-16")] {
158        /// Max number of IM buffers
159        const MAX_IM_BUFFERS: usize = 16;
160    } else if #[cfg(feature = "max-im-buffers-10")] {
161        /// Max number of IM buffers
162        const MAX_IM_BUFFERS: usize = 10;
163    } else if #[cfg(feature = "max-im-buffers-9")] {
164        /// Max number of IM buffers
165        const MAX_IM_BUFFERS: usize = 9;
166    } else if #[cfg(feature = "max-im-buffers-8")] {
167        /// Max number of IM buffers
168        const MAX_IM_BUFFERS: usize = 8;
169    } else if #[cfg(feature = "max-im-buffers-7")] {
170        /// Max number of IM buffers
171        const MAX_IM_BUFFERS: usize = 7;
172    } else if #[cfg(feature = "max-im-buffers-6")] {
173        /// Max number of IM buffers
174        const MAX_IM_BUFFERS: usize = 6;
175    } else if #[cfg(feature = "max-im-buffers-5")] {
176        /// Max number of IM buffers
177        const MAX_IM_BUFFERS: usize = 5;
178    } else if #[cfg(feature = "max-im-buffers-4")] {
179        /// Max number of IM buffers
180        const MAX_IM_BUFFERS: usize = 4;
181    } else {
182        /// Max number of IM buffers
183        const MAX_IM_BUFFERS: usize = 10;
184    }
185}
186
187cfg_if! {
188    if #[cfg(feature = "max-responders-32")] {
189        /// Max number of concurrent responders
190        const MAX_RESPONDERS: usize = 32;
191    } else if #[cfg(feature = "max-responders-16")] {
192        /// Max number of concurrent responders
193        const MAX_RESPONDERS: usize = 16;
194    } else if #[cfg(feature = "max-responders-8")] {
195        /// Max number of concurrent responders
196        const MAX_RESPONDERS: usize = 8;
197    } else if #[cfg(feature = "max-responders-7")] {
198        /// Max number of concurrent responders
199        const MAX_RESPONDERS: usize = 7;
200    } else if #[cfg(feature = "max-responders-6")] {
201        /// Max number of concurrent responders
202        const MAX_RESPONDERS: usize = 6;
203    } else if #[cfg(feature = "max-responders-5")] {
204        /// Max number of concurrent responders
205        const MAX_RESPONDERS: usize = 5;
206    } else if #[cfg(feature = "max-responders-4")] {
207        /// Max number of concurrent responders
208        const MAX_RESPONDERS: usize = 4;
209    } else if #[cfg(feature = "max-responders-3")] {
210        /// Max number of concurrent responders
211        const MAX_RESPONDERS: usize = 3;
212    } else if #[cfg(feature = "max-responders-2")] {
213        /// Max number of concurrent responders
214        const MAX_RESPONDERS: usize = 2;
215    } else if #[cfg(feature = "max-responders-1")] {
216        /// Max number of concurrent responders
217        const MAX_RESPONDERS: usize = 1;
218    } else {
219        /// Max number of concurrent responders
220        const MAX_RESPONDERS: usize = 4;
221    }
222}
223
224const MAX_BUSY_RESPONDERS: usize = 2;
225
226pub type MatterStackInteractionModel<'a, C, H, K, RN, NC> = InteractionModel<
227    'a,
228    C,
229    MatterBuffers<MAX_IM_BUFFERS>,
230    H,
231    K,
232    RN,
233    NC,
234    // The accessory role: inbound `ReportData` is disowned.
235    (),
236    MAX_SUBSCRIPTIONS,
237    EVENTS_RINGBUF_SIZE,
238>;
239
240/// The `InteractionModelState` specialization owned by `MatterStack`.
241///
242/// It owns the subscriptions table, the events queue and the `rs-matter`
243/// networks store as a single unit. The KV scratch buffer now lives in `Matter`.
244pub type MatterStackInteractionModelState<RN> =
245    InteractionModelState<RN, MAX_SUBSCRIPTIONS, EVENTS_RINGBUF_SIZE>;
246
247/// The `MatterStack` struct is the main entry point for the Matter stack.
248///
249/// It wraps the actual `rs-matter` Matter instance and provides a simplified API for running the stack.
250pub struct MatterStack<'a, const B: usize, N>
251where
252    N: Network,
253{
254    matter: Matter<'a>,
255    buffers: MatterBuffers<MAX_IM_BUFFERS>,
256    /// The interaction-model state: subscriptions table, events queue, the
257    /// `rs-matter` networks store, and the KV scratch buffer, owned as one unit.
258    state: MatterStackInteractionModelState<N::Networks>,
259    bump: Bump<B>,
260    run_lock: IfMutex<()>,
261    /// Whether the Interaction Model state (events watermark, networks store,
262    /// persisted subscriptions) has already been re-hydrated from the KV store.
263    ///
264    /// `InteractionModel::startup` cannot be driven from `MatterStack::startup`,
265    /// because it has to run on the very Interaction Model instance that is then
266    /// run: a resumed subscription borrows that instance's IM buffers, and
267    /// constructing an `InteractionModel` clears the subscriptions table. So the
268    /// stack hydrates from `run_im` instead.
269    ///
270    /// The stack however builds one Interaction Model *per phase* (BLE
271    /// commissioning, then operational), and only the first one may hydrate:
272    /// re-loading the networks store on the phase switch would drop the
273    /// credentials that non-concurrent commissioning has in memory but - with the
274    /// failsafe still armed - not yet persisted.
275    im_hydrated: Mutex<Cell<bool>>,
276    #[allow(unused)]
277    network: N,
278    //netif_conf: Signal<Option<NetifConf>>,
279}
280
281impl<'a, const B: usize, N> MatterStack<'a, B, N>
282where
283    N: Network,
284{
285    /// Create a new `MatterStack` instance.
286    #[allow(clippy::large_stack_frames)]
287    #[inline(always)]
288    pub const fn new(
289        dev_det: &'a BasicInfoConfig,
290        dev_comm: BasicCommData,
291        dev_att: &'a dyn DeviceAttestation,
292    ) -> Self {
293        Self {
294            matter: Matter::new(dev_det, dev_comm, dev_att, MATTER_PORT),
295            buffers: MatterBuffers::new(),
296            state: MatterStackInteractionModelState::new(N::NETWORKS),
297            bump: Bump::new(),
298            run_lock: IfMutex::new(()),
299            im_hydrated: Mutex::new(Cell::new(false)),
300            network: N::INIT,
301            //netif_conf: Signal::new(None),
302        }
303    }
304
305    #[allow(clippy::large_stack_frames)]
306    pub fn init(
307        dev_det: &'a BasicInfoConfig,
308        dev_comm: BasicCommData,
309        dev_att: &'a dyn DeviceAttestation,
310    ) -> impl Init<Self> {
311        init!(Self {
312            matter <- Matter::init(
313                dev_det,
314                dev_comm,
315                dev_att,
316                MATTER_PORT,
317            ),
318            buffers <- MatterBuffers::init(),
319            state <- MatterStackInteractionModelState::init(N::init_networks()),
320            bump <- Bump::init(),
321            run_lock <- IfMutex::init(()),
322            im_hydrated: Mutex::new(Cell::new(false)),
323            network <- N::init(),
324            //netif_conf: Signal::new(None),
325        })
326    }
327
328    /// A utility method to replace the initial Device Attestation Data Fetcher with another one.
329    ///
330    /// Reasoning and use-cases explained in the documentation of `replace_mdns`.
331    pub fn replace_dev_att(&mut self, dev_att: &'a dyn DeviceAttestation) {
332        self.matter.replace_dev_att(dev_att);
333    }
334
335    /// Get a reference to the `Matter` instance.
336    pub const fn matter(&self) -> &Matter<'a> {
337        &self.matter
338    }
339
340    /// Get a reference to the `Network` instance.
341    /// Useful when the user instantiates `MatterStack` with a custom network type.
342    pub const fn network(&self) -> &N {
343        &self.network
344    }
345
346    /// Create a new shared `KvBlobStore` instance, which is used to read and write blobs from the storage.
347    ///
348    /// The user needs to provide a `KvBlobStore` implementation, which is used to actually read and write the blobs from the storage.
349    ///
350    /// # Arguments
351    /// - `store` - the raw [`KvBlobStore`] implementation to wrap
352    pub fn kv<'s, S: KvBlobStore + 's>(&'s self, store: S) -> impl KvBlobStoreAccess + 's {
353        self.matter().kv(store)
354    }
355
356    // /// User code hook to get the state of the netif passed to the
357    // /// `run_with_netif` method.
358    // ///
359    // /// Useful when user code needs to bring up/down its own IP services depending on
360    // /// when the netif controlled by Matter goes up, down or changes its IP configuration.
361    // pub async fn get_netif_conf(&self) -> Option<NetifConf> {
362    //     self.netif_conf
363    //         .wait(|netif_conf| Some(netif_conf.clone()))
364    //         .await
365    // }
366
367    // fn update_netif_conf(&self, netif_conf: Option<&NetifConf>) -> bool {
368    //     self.netif_conf.modify(|global_ip_info| {
369    //         if global_ip_info.as_ref() != netif_conf {
370    //             *global_ip_info = netif_conf.cloned();
371    //             (true, true)
372    //         } else {
373    //             (false, false)
374    //         }
375    //     })
376    // }
377
378    // /// User code hook to detect changes to the IP state of the netif passed to the
379    // /// `run_with_netif` method.
380    // ///
381    // /// Useful when user code needs to bring up/down its own IP services depending on
382    // /// when the netif controlled by Matter goes up, down or changes its IP configuration.
383    // pub async fn wait_netif_changed(
384    //     &self,
385    //     prev_netif_info: Option<&NetifConf>,
386    // ) -> Option<NetifConf> {
387    //     self.netif_conf
388    //         .wait(|netif_info| (netif_info.as_ref() != prev_netif_info).then(|| netif_info.clone()))
389    //         .await
390    // }
391
392    /// Open the basic communication window, which allows commissioning tools to discover and commission the device.
393    ///
394    /// # Arguments
395    /// - `crypto` - a user-provided crypto implementation, necessary for the secure sessions establishment that happens in the basic communication window
396    /// - `notify` - a user-provided `AttrChangeNotifier`; typically, `Data Model::change_notify`; used to notify the Matter instance about changes in the state of the clusters' attributes, so that it can notify commissioning tools about them
397    pub fn open_basic_comm_window<C>(
398        &self,
399        crypto: C,
400        notify: &dyn AttrChangeNotifier,
401    ) -> Result<(), Error>
402    where
403        C: Crypto,
404    {
405        self.matter()
406            .open_basic_comm_window(MAX_COMM_WINDOW_TIMEOUT_SECS, crypto, notify)?;
407
408        self.matter()
409            .print_standard_qr_text(self.network.discovery_capabilities())?;
410
411        self.matter()
412            .print_standard_qr_code(QrTextType::Unicode, self.network.discovery_capabilities())
413    }
414
415    /// This method is a specialization of `run_transport_net` over the UDP transport (both IPv4 and IPv6).
416    /// It calls `run_transport_net`.
417    ///
418    /// #Arguments
419    /// - `crypto` - a user-provided crypto implementation, necessary for the secure sessions establishment that happens in the operational network
420    /// - `net_stack` - a user-provided network stack that implements `UdpBind`, `UdpConnect`, `TcpBind`, `TcpConnect`, and `Dns`
421    /// - `netif` - a user-provided `Netif` implementation
422    /// - `until` - the method will return once this future becomes ready
423    /// - `comm` - a tuple of additional and optional `NetworkReceive` and `NetworkSend` transport implementations
424    ///   (useful when a second transport needs to run in parallel with the operational Matter transport,
425    ///   i.e. when using concurrent commissisoning)
426    async fn run_oper_net<C, U, X, R, S>(
427        &self,
428        crypto: C,
429        net_stack: U,
430        net_interface: u32,
431        until: X,
432        mut comm: Option<(R, S)>,
433    ) -> Result<(), Error>
434    where
435        C: Crypto,
436        U: NetStack,
437        X: Future<Output = Result<(), Error>>,
438        R: NetworkReceive,
439        S: NetworkSend,
440    {
441        fn map_err<E: Debug>(e: E) -> Error {
442            warn!("Matter UDP network error: {:?}", debug2format!(e));
443            ErrorCode::StdIoError.into() // TODO
444        }
445
446        let udp_bind = unwrap!(net_stack.udp_bind());
447
448        let mut socket = udp_bind
449            .bind(SocketAddr::V6(SocketAddrV6::new(
450                Ipv6Addr::UNSPECIFIED,
451                MATTER_PORT,
452                0,
453                net_interface,
454            )))
455            .await
456            .map_err(map_err)?;
457
458        let (recv, send, m4, m6) = socket.split_multicast();
459
460        let multicast = udp::Udp(udp::Multicast::new(
461            m4,
462            // rs-matter does not really use IPv4 multicast for Groups, so we can just use `Ipv4Addr::UNSPECIFIED` here.
463            Ipv4Addr::UNSPECIFIED,
464            m6,
465            net_interface,
466        ));
467
468        let mut until_task = pin!(until);
469
470        if let Some((comm_recv, comm_send)) = comm.as_mut() {
471            info!("Running operational and commissioning networks");
472
473            let mut netw_task = pin!(self.run_transport_net(
474                &crypto,
475                ChainedNetwork::new(Address::is_udp, udp::Udp(send), comm_send),
476                ChainedNetwork::new(Address::is_udp, udp::Udp(recv), comm_recv),
477                ChainedNetwork::new(Address::is_udp, multicast, NoNetwork),
478            ));
479
480            select(&mut netw_task, &mut until_task).coalesce().await
481        } else {
482            info!("Running operational network");
483
484            let mut netw_task =
485                pin!(self.run_transport_net(&crypto, udp::Udp(send), udp::Udp(recv), multicast,));
486
487            select(&mut netw_task, &mut until_task).coalesce().await
488        }
489    }
490
491    /// This method runs the mDNS service.
492    ///
493    /// The netif instance is necessary, so that the loop can monitor the network and bring up/down
494    /// the mDNS service when the netif goes up/down or changes its IP addresses.
495    ///
496    /// This is necessary because mDNS needs to know the current IP addresses and
497    /// also needs to stop when the netif goes down.
498    ///
499    /// # Arguments
500    /// - `crypto` - a user-provided crypto implementation
501    /// - `net_stack` - a user-provided network stack that implements `UdpBind`, `UdpConnect`, `TcpBind`, `TcpConnect`, and `Dns`
502    /// - `netif` - a user-provided `Netif` implementation
503    /// - `mdns` - a user-provided mDNS implementation
504    async fn run_oper_netif_mdns<C, U, I, M>(
505        &self,
506        crypto: C,
507        net_stack: U,
508        netif: I,
509        mut mdns: M,
510    ) -> Result<(), Error>
511    where
512        C: Crypto,
513        U: NetStack,
514        I: NetifDiag + NetChangeNotif,
515        M: Mdns,
516    {
517        #[derive(Clone, Debug, Eq, PartialEq, Hash)]
518        #[cfg_attr(feature = "defmt", derive(defmt::Format))]
519        struct NetifState {
520            ipv6: Ipv6Addr,
521            ipv4: Ipv4Addr,
522            mac: [u8; 8],
523            operational: bool,
524            netif_index: u32,
525        }
526
527        impl NetifState {
528            pub const fn new() -> Self {
529                Self {
530                    ipv6: Ipv6Addr::UNSPECIFIED,
531                    ipv4: Ipv4Addr::UNSPECIFIED,
532                    mac: [0; 8],
533                    operational: false,
534                    netif_index: 0,
535                }
536            }
537        }
538
539        fn load_netif_state<I>(net_diag: I, state: &mut NetifState) -> Result<(), Error>
540        where
541            I: NetifDiag,
542        {
543            state.operational = false;
544            state.ipv6 = Ipv6Addr::UNSPECIFIED;
545            state.ipv4 = Ipv4Addr::UNSPECIFIED;
546            state.mac = [0; 8];
547
548            net_diag.netifs(&mut |ni| {
549                if ni.operational && !ni.ipv6_addrs.is_empty() {
550                    state.operational = true;
551                    state.ipv6 = ni.ipv6_addrs[0];
552                    state.ipv4 = ni
553                        .ipv4_addrs
554                        .first()
555                        .copied()
556                        .unwrap_or(Ipv4Addr::UNSPECIFIED);
557                    state.mac = *ni.hw_addr;
558                    state.netif_index = ni.netif_index;
559                }
560
561                Ok(())
562            })
563        }
564
565        async fn wait_changed<I>(
566            net_diag: I,
567            cur_state: &NetifState,
568            new_state: &mut NetifState,
569        ) -> Result<(), Error>
570        where
571            I: NetifDiag + NetChangeNotif,
572        {
573            loop {
574                load_netif_state(&net_diag, new_state)?;
575
576                if &*new_state != cur_state {
577                    info!(
578                        "Netif change detected.\n    Old: {:?}\n    New: {:?}",
579                        cur_state, new_state
580                    );
581                    break Ok(());
582                }
583
584                trace!("No change");
585                net_diag.wait_changed().await;
586            }
587        }
588
589        // let _guard = scopeguard::guard((), |_| {
590        //     self.update_netif_conf(None);
591        // });
592
593        let mut new_state = NetifState::new();
594        load_netif_state(&netif, &mut new_state)?;
595
596        loop {
597            let cur_state = new_state.clone();
598
599            let mut netif_changed_task = pin!(wait_changed(&netif, &cur_state, &mut new_state));
600
601            let mut mdns_task = pin!(async {
602                if cur_state.operational {
603                    info!("Netif up: {:?}", cur_state);
604
605                    let udp_bind = unwrap!(net_stack.udp_bind());
606
607                    info!("Running mDNS");
608
609                    loop {
610                        let _result = mdns
611                            .run(
612                                self.matter(),
613                                &crypto,
614                                &udp_bind,
615                                &cur_state.mac,
616                                cur_state.ipv4,
617                                cur_state.ipv6,
618                                cur_state.netif_index,
619                            )
620                            .await;
621
622                        warn!("mDNS failed with {:?}, retrying in 5s...", _result);
623                        embassy_time::Timer::after(Duration::from_secs(5)).await;
624                    }
625                } else {
626                    info!("Netif down");
627                    core::future::pending::<()>().await;
628                }
629
630                Ok(())
631            });
632
633            select(&mut netif_changed_task, &mut mdns_task)
634                .coalesce()
635                .await?;
636        }
637    }
638
639    #[inline(always)]
640    fn im<C, H, K, NC>(
641        &self,
642        crypto: C,
643        handler: H,
644        kv: K,
645        net_ctl: NC,
646    ) -> MatterStackInteractionModel<'_, C, H, K, N::Networks, NC>
647    where
648        C: Crypto,
649        H: DataModel,
650        K: KvBlobStoreAccess,
651        NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif,
652    {
653        MatterStackInteractionModel::new_with_net_ctl(
654            self.matter(),
655            crypto,
656            &self.buffers,
657            handler,
658            kv,
659            net_ctl,
660            &self.state,
661        )
662    }
663
664    async fn run_im<C, H, K, RN, NC>(
665        &self,
666        im: &MatterStackInteractionModel<'_, C, H, K, RN, NC>,
667    ) -> Result<(), Error>
668    where
669        C: Crypto,
670        H: DataModel,
671        K: KvBlobStoreAccess,
672        RN: Networks,
673        NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif,
674    {
675        // TODO
676        // Reset the Matter transport buffers and all sessions first
677        // self.matter().reset_transport()?;
678
679        self.startup_im(im).await?;
680
681        let mut responder = pin!(self.run_responder(im));
682        let mut im_job = pin!(im.run());
683
684        select(&mut responder, &mut im_job).coalesce().await
685    }
686
687    async fn run_im_with_bump<C, H, K, RN, NC>(
688        &self,
689        im: &MatterStackInteractionModel<'_, C, H, K, RN, NC>,
690    ) -> Result<(), Error>
691    where
692        C: Crypto,
693        H: DataModel,
694        K: KvBlobStoreAccess,
695        RN: Networks,
696        NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif,
697    {
698        // TODO
699        // Reset the Matter transport buffers and all sessions first
700        // self.matter().reset_transport()?;
701
702        self.startup_im(im).await?;
703
704        let mut responder = pin_alloc!(self.bump, self.run_responder_with_bump(im));
705        let mut im_job = pin!(im.run());
706
707        select(&mut responder, &mut im_job).coalesce().await
708    }
709
710    /// Re-hydrate the Interaction Model state (events watermark, networks store,
711    /// persisted subscriptions) and deliver the `Startup` lifecycle op to the
712    /// cluster handlers - but only for the first Interaction Model instance that
713    /// this stack runs (see `im_hydrated`).
714    ///
715    /// `BasicInformation::StartUp` is emitted by `InteractionModel::run` itself,
716    /// once per process lifetime.
717    async fn startup_im<C, H, K, RN, NC>(
718        &self,
719        im: &MatterStackInteractionModel<'_, C, H, K, RN, NC>,
720    ) -> Result<(), Error>
721    where
722        C: Crypto,
723        H: DataModel,
724        K: KvBlobStoreAccess,
725        RN: Networks,
726        NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif,
727    {
728        if self.im_hydrated.lock(|hydrated| hydrated.replace(true)) {
729            return Ok(());
730        }
731
732        im.startup().await
733    }
734
735    async fn run_responder<C, H, K, RN, NC>(
736        &self,
737        im: &MatterStackInteractionModel<'_, C, H, K, RN, NC>,
738    ) -> Result<(), Error>
739    where
740        C: Crypto,
741        H: DataModel,
742        K: KvBlobStoreAccess,
743        RN: Networks,
744        NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif,
745    {
746        let responder = DefaultResponder::new(im);
747
748        // Run the responder with up to MAX_RESPONDERS handlers (i.e. MAX_RESPONDERS exchanges can be handled simultenously)
749        // Clients trying to open more exchanges than the ones currently running will get "I'm busy, please try again later"
750        pin!(responder.run::<MAX_RESPONDERS, MAX_BUSY_RESPONDERS>()).await?;
751
752        Ok(())
753    }
754
755    async fn run_responder_with_bump<C, H, K, RN, NC>(
756        &self,
757        im: &MatterStackInteractionModel<'_, C, H, K, RN, NC>,
758    ) -> Result<(), Error>
759    where
760        C: Crypto,
761        H: DataModel,
762        K: KvBlobStoreAccess,
763        RN: Networks,
764        NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif,
765    {
766        let responder = DefaultResponder::new(im);
767
768        let mut actual = pin_alloc!(
769            self.bump,
770            self.run_one_responder_with_bump::<MAX_RESPONDERS, _>(responder.responder())
771        );
772        let mut busy = pin_alloc!(
773            self.bump,
774            self.run_one_responder_with_bump::<MAX_BUSY_RESPONDERS, _>(responder.busy_responder())
775        );
776
777        select(&mut actual, &mut busy).coalesce().await
778    }
779
780    /// Run a responder with Q handlers using the provided bump allocator.
781    async fn run_one_responder_with_bump<const Q: usize, T>(
782        &self,
783        responder: &Responder<'_, T>,
784    ) -> Result<(), Error>
785    where
786        T: ExchangeHandler,
787    {
788        info!("{}: Creating {} handlers", responder.name(), Q);
789
790        let mut handlers = heapless::Vec::<_, Q>::new();
791        debug!(
792            "{}: Handlers size: {}B",
793            responder.name(),
794            core::mem::size_of_val(&handlers)
795        );
796
797        for handler_id in 0..Q {
798            unwrap!(handlers
799                .push(pin_alloc!(self.bump, responder.handle(handler_id)))
800                .map_err(|_| ())); // Cannot fail because the vector has size N
801        }
802
803        let handlers = pin!(handlers);
804        let handlers = unsafe { handlers.map_unchecked_mut(|handlers| handlers.as_mut_slice()) };
805
806        select_slice(handlers).await.0
807    }
808
809    fn run_transport_net<'t, C, S, R, M>(
810        &'t self,
811        crypto: C,
812        send: S,
813        recv: R,
814        multicast: M,
815    ) -> impl Future<Output = Result<(), Error>> + 't
816    where
817        C: Crypto + 't,
818        S: NetworkSend + 't,
819        R: NetworkReceive + 't,
820        M: NetworkMulticast + 't,
821    {
822        self.matter().run(crypto, send, recv, multicast)
823    }
824}
825
826/// A trait representing a user task that needs access to the operational network interface
827/// (Netif and net stack) to perform its work.
828///
829/// Note that the task would be started only when `rs-matter`
830/// brings up the operational interface (eth, wifi or thread)
831/// and if the interface goes down, the user task would be stopped.
832/// Upon re-connection, the task would be started again.
833pub trait UserTask {
834    /// Run the task with the given network stack and network interface
835    async fn run<S, N>(&mut self, net_stack: S, netif: N) -> Result<(), Error>
836    where
837        S: NetStack,
838        N: NetifDiag + NetChangeNotif;
839}
840
841impl<T> UserTask for &mut T
842where
843    T: UserTask,
844{
845    fn run<S, N>(&mut self, net_stack: S, netif: N) -> impl Future<Output = Result<(), Error>>
846    where
847        S: NetStack,
848        N: NetifDiag + NetChangeNotif,
849    {
850        (*self).run(net_stack, netif)
851    }
852}
853
854impl UserTask for () {
855    fn run<S, N>(&mut self, _net_stack: S, _netif: N) -> impl Future<Output = Result<(), Error>>
856    where
857        S: NetStack,
858        N: NetifDiag + NetChangeNotif,
859    {
860        core::future::pending::<Result<(), Error>>()
861    }
862}
863
864// The data model is not created yet, so we don't have to notify anything
865pub(crate) struct DummyAttrNotifier;
866
867impl DynBase for DummyAttrNotifier {}
868
869impl AttrChangeNotifier for DummyAttrNotifier {
870    fn notify_attr_changed(&self, _endpoint_id: EndptId, _cluster_id: ClusterId, _attr_id: AttrId) {
871    }
872
873    fn notify_cluster_changed(&self, _endpoint_id: EndptId, _cluster_id: ClusterId) {}
874
875    fn notify_endpoint_changed(&self, _endpoint_id: EndptId) {}
876
877    fn notify_all_changed(&self) {}
878}