Skip to main content

rs_matter_stack/
eth.rs

1use core::future::Future;
2
3use embassy_futures::select::select4;
4
5use rs_matter::crypto::{Crypto, RngCore};
6use rs_matter::dm::clusters::gen_comm::CommPolicy;
7use rs_matter::dm::clusters::gen_diag::{GenDiag, NetifDiag};
8use rs_matter::dm::clusters::net_comm::{DummyNetworks, NetworkType};
9use rs_matter::dm::clusters::sw_diag::SwDiag;
10use rs_matter::dm::endpoints::{eth_sys_handler, EthSysHandler, ROOT_ENDPOINT_ID};
11use rs_matter::dm::networks::wireless::NoopWirelessNetCtl;
12use rs_matter::dm::networks::NetChangeNotif;
13use rs_matter::dm::{ChainedHandler, DataModel, Endpoint, EpClMatcher};
14use rs_matter::error::Error;
15use rs_matter::pairing::DiscoveryCapabilities;
16use rs_matter::persist::{KvBlobStore, KvBlobStoreAccess};
17use rs_matter::root_endpoint;
18use rs_matter::transport::network::NoNetwork;
19use rs_matter::utils::init::{init, init_from_closure, Init};
20use rs_matter::utils::select::Coalesce;
21
22use crate::mdns::Mdns;
23use crate::nal::NetStack;
24use crate::network::{Embedding, Network};
25use crate::private::Sealed;
26use crate::{pin_alloc, DummyAttrNotifier, MatterStack, UserTask};
27
28/// An implementation of the `Network` trait for Ethernet.
29///
30/// Note that "Ethernet" - in the context of this crate - means
31/// not just the Ethernet transport, but also any other IP-based transport
32/// (like Wifi or Thread), where the Matter stack would not be concerned
33/// with the management of the network transport (as in re-connecting to the
34/// network on lost signal, managing network credentials and so on).
35///
36/// The expectation is nevertheless that for production use-cases
37/// the `Eth` network would really only be used for Ethernet.
38pub struct Eth<E = ()> {
39    embedding: E,
40}
41
42impl<E> Sealed for Eth<E> {}
43
44impl<E> Network for Eth<E>
45where
46    E: Embedding,
47{
48    const INIT: Self = Self { embedding: E::INIT };
49
50    type Embedding<'a>
51        = E
52    where
53        E: 'a;
54
55    // Ethernet does not manage network credentials, so use the no-op networks store.
56    type Networks = DummyNetworks;
57
58    const NETWORKS: Self::Networks = DummyNetworks;
59
60    fn init() -> impl Init<Self> {
61        init!(Self {
62            embedding <- E::init(),
63        })
64    }
65
66    fn init_networks() -> impl Init<Self::Networks> {
67        unsafe {
68            init_from_closure(|slot: *mut DummyNetworks| {
69                slot.write(DummyNetworks);
70                Ok(())
71            })
72        }
73    }
74
75    fn discovery_capabilities(&self) -> DiscoveryCapabilities {
76        DiscoveryCapabilities::IP
77    }
78
79    fn embedding(&self) -> &Self::Embedding<'_> {
80        &self.embedding
81    }
82}
83
84// A type alias for a Matter stack running over Ethernet.
85pub type EthMatterStack<'a, const B: usize, E = ()> = MatterStack<'a, B, Eth<E>>;
86
87/// A trait representing a task that needs access to the operational Ethernet interface
88/// (Network stack and Netif) to perform its work.
89pub trait EthernetTask {
90    /// Run the task with the given network stack, network interface and mDNS
91    async fn run<S, N, M>(&mut self, net_stack: S, netif: N, mdns: M) -> Result<(), Error>
92    where
93        S: NetStack,
94        N: NetifDiag + NetChangeNotif,
95        M: Mdns;
96}
97
98impl<T> EthernetTask for &mut T
99where
100    T: EthernetTask,
101{
102    fn run<S, N, M>(
103        &mut self,
104        net_stack: S,
105        netif: N,
106        mdns: M,
107    ) -> impl Future<Output = Result<(), Error>>
108    where
109        S: NetStack,
110        N: NetifDiag + NetChangeNotif,
111        M: Mdns,
112    {
113        (*self).run(net_stack, netif, mdns)
114    }
115}
116
117/// A trait for running a task within a context where the ethernet interface is initialized and operable
118pub trait Ethernet {
119    /// Setup Ethernet and run the given task
120    async fn run<T>(&mut self, task: T) -> Result<(), Error>
121    where
122        T: EthernetTask;
123}
124
125impl<T> Ethernet for &mut T
126where
127    T: Ethernet,
128{
129    fn run<A>(&mut self, task: A) -> impl Future<Output = Result<(), Error>>
130    where
131        A: EthernetTask,
132    {
133        (*self).run(task)
134    }
135}
136
137/// A utility type for running an ethernet task with a pre-existing ethernet interface
138/// rather than bringing up / tearing down the ethernet interface for the task.
139pub struct PreexistingEthernet<S, N, M> {
140    stack: S,
141    netif: N,
142    mdns: M,
143}
144
145impl<S, N, M> PreexistingEthernet<S, N, M> {
146    /// Create a new `PreexistingEthernet` instance with the given network interface, UDP stack and mDNS.
147    pub const fn new(stack: S, netif: N, mdns: M) -> Self {
148        Self { stack, netif, mdns }
149    }
150}
151
152impl<S, N, M> Ethernet for PreexistingEthernet<S, N, M>
153where
154    S: NetStack,
155    N: NetifDiag + NetChangeNotif,
156    M: Mdns,
157{
158    async fn run<T>(&mut self, mut task: T) -> Result<(), Error>
159    where
160        T: EthernetTask,
161    {
162        task.run(&self.stack, &self.netif, &mut self.mdns).await
163    }
164}
165
166/// A specialization of the `MatterStack` for Ethernet.
167impl<const B: usize, E> MatterStack<'_, B, Eth<E>>
168where
169    E: Embedding,
170{
171    /// Return a metadata for the root (Endpoint 0) of the Matter Node
172    /// configured for Ethernet network.
173    pub const fn root_endpoint() -> Endpoint<'static> {
174        const ENDPOINT: Endpoint<'static> = root_endpoint!(eth);
175
176        ENDPOINT
177    }
178
179    /// Return a handler for the root (Endpoint 0) of the Matter Node
180    /// configured for Ethernet network.
181    fn root_handler<'a>(
182        &self,
183        comm_policy: &'a dyn CommPolicy,
184        gen_diag: &'a dyn GenDiag,
185        netif_diag: &'a dyn NetifDiag,
186        sw_diag: &'a dyn SwDiag,
187        rand: impl RngCore + Copy,
188    ) -> EthSysHandler<'a> {
189        eth_sys_handler(comm_policy, gen_diag, netif_diag, sw_diag, rand)
190    }
191
192    /// Reset the Matter instance to the factory defaults by removing all fabrics and basic info settings
193    ///
194    /// `handler` is the same data model handler that is passed to `run`: the
195    /// Interaction Model broadcasts a `FactoryReset` lifecycle op to it, so
196    /// cluster handlers owning persisted state of their own can drop it too.
197    pub async fn reset<C, H, S>(&mut self, crypto: C, handler: H, store: S) -> Result<(), Error>
198    where
199        C: Crypto,
200        H: DataModel,
201        S: KvBlobStore,
202    {
203        let kv = self.matter.kv(store);
204
205        self.matter.factory_reset(&kv)?;
206
207        // Reset the events counter (and the - no-op for Ethernet - networks store)
208        // so we don't carry a stale watermark across a factory reset
209        // (Matter Core spec R1.5.1, ยง7.14.1.1).
210        self.im(
211            crypto,
212            handler,
213            &kv,
214            NoopWirelessNetCtl::new(NetworkType::Ethernet),
215        )
216        .factory_reset()
217        .await
218    }
219
220    /// Run the startup sequence of the stack: re-hydrate the persisted state and
221    /// open the basic commissioning window if the device is not commissioned yet.
222    ///
223    /// This is the `Matter`-level half of the startup (fabrics, basic info, RTC,
224    /// sessions). The Interaction Model half - the events watermark, the networks
225    /// store and the persisted subscriptions - is re-hydrated by `run`, because
226    /// `InteractionModel::startup` has to run on the very Interaction Model
227    /// instance that is then run: a resumed subscription borrows that instance's
228    /// IM buffers, and constructing an `InteractionModel` clears the
229    /// subscriptions table.
230    pub async fn startup<C, S>(&mut self, crypto: C, store: S) -> Result<(), Error>
231    where
232        C: Crypto,
233        S: KvBlobStore,
234    {
235        let kv = self.matter.kv(store);
236
237        self.matter.startup(&kv)?;
238
239        if !self.matter().has_fabrics() {
240            info!("Device is not commissioned yet, opening commissioning window...");
241
242            self.open_basic_comm_window(crypto, &DummyAttrNotifier)?;
243        } else {
244            info!("Device is already commissioned");
245        }
246
247        Ok(())
248    }
249
250    /// Run the Matter stack for a pre-existing Ethernet network.
251    ///
252    /// # Arguments
253    /// - `net_stack` - a user-provided network stack implementation
254    /// - `netif` - a user-provided `Netif` implementation for the Ethernet network
255    /// - `mdns` - a user-provided mDNS implementation
256    /// - `crypto` - a user-provided crypto implementation
257    /// - `handler` - a user-provided DM handler implementation
258    /// - `kv` - a user-provided `KvBlobStoreAccess` implementation for loading the persisted state of the stack
259    /// - `user` - a user-provided future that will be polled only when the netif interface is up
260    #[allow(clippy::too_many_arguments)]
261    pub fn run_preex<'t, U, N, M, C, H, K, X>(
262        &'t self,
263        net_stack: U,
264        netif: N,
265        mdns: M,
266        crypto: C,
267        handler: H,
268        kv: K,
269        user: X,
270    ) -> impl Future<Output = Result<(), Error>> + 't
271    where
272        U: NetStack + 't,
273        N: NetifDiag + NetChangeNotif + 't,
274        M: Mdns + 't,
275        C: Crypto + 't,
276        H: DataModel + 't,
277        K: KvBlobStoreAccess + 't,
278        X: UserTask + 't,
279    {
280        self.run(
281            PreexistingEthernet::new(net_stack, netif, mdns),
282            crypto,
283            handler,
284            kv,
285            user,
286        )
287    }
288
289    /// Run the Matter stack for an Ethernet network.
290    ///
291    /// # Arguments
292    /// - `ethernet` - a user-provided `Ethernet` implementation
293    /// - `crypto` - a user-provided crypto implementation
294    /// - `handler` - a user-provided DM handler implementation
295    /// - `kv` - a user-provided `KvBlobStoreAccess` implementation for loading the persisted state of the stack
296    /// - `user` - a user-provided future that will be polled only when the netif interface is up
297    pub async fn run<N, C, H, K, X>(
298        &self,
299        mut ethernet: N,
300        crypto: C,
301        handler: H,
302        kv: K,
303        user: X,
304    ) -> Result<(), Error>
305    where
306        N: Ethernet,
307        C: Crypto,
308        H: DataModel,
309        K: KvBlobStoreAccess,
310        X: UserTask,
311    {
312        let _lock = self.run_lock.lock().await;
313
314        info!("Matter Stack memory: {}b", core::mem::size_of_val(self));
315
316        // Since this is the last code executed in the method, resetting the allocator should be safe
317        // because all boxes returned by it should be dropped by then
318        let _defer = scopeguard::guard((), |_| unsafe {
319            self.bump.reset();
320        });
321
322        self.matter().reset_transport()?;
323
324        let net_task = pin_alloc!(
325            self.bump,
326            self.run_ethernet(&mut ethernet, crypto, handler, &kv, user)
327        );
328
329        net_task.await
330    }
331
332    fn run_ethernet<'t, N, C, H, K, X>(
333        &'t self,
334        ethernet: &'t mut N,
335        crypto: C,
336        handler: H,
337        kv: K,
338        user: X,
339    ) -> impl Future<Output = Result<(), Error>> + 't
340    where
341        N: Ethernet + 't,
342        C: Crypto + 't,
343        H: DataModel + 't,
344        K: KvBlobStoreAccess + 't,
345        X: UserTask + 't,
346    {
347        Ethernet::run(
348            ethernet,
349            MatterStackEthernetTask {
350                stack: self,
351                crypto,
352                handler,
353                kv,
354                user_task: user,
355            },
356        )
357    }
358}
359
360struct MatterStackEthernetTask<'a, const B: usize, E, C, H, K, X>
361where
362    E: Embedding,
363    C: Crypto,
364    H: DataModel,
365    K: KvBlobStoreAccess,
366    X: UserTask,
367{
368    stack: &'a MatterStack<'a, B, Eth<E>>,
369    crypto: C,
370    handler: H,
371    kv: K,
372    user_task: X,
373}
374
375impl<const B: usize, E, C, H, K, X> EthernetTask for MatterStackEthernetTask<'_, B, E, C, H, K, X>
376where
377    E: Embedding,
378    C: Crypto,
379    H: DataModel,
380    K: KvBlobStoreAccess,
381    X: UserTask,
382{
383    async fn run<N, I, M>(&mut self, net_stack: N, netif: I, mut mdns: M) -> Result<(), Error>
384    where
385        N: NetStack,
386        I: NetifDiag + NetChangeNotif,
387        M: Mdns,
388    {
389        info!("Ethernet driver started");
390
391        // The sys-handler chain (built per phase so per-phase `NetCtl` /
392        // diag implementations can vary) covers every cluster on the
393        // root endpoint; route anything on EP0 to it, and any other
394        // endpoint to the user's handler. `&self.handler` doubles as
395        // the `Metadata` provider (`(M, H)` form for `InteractionModel::new`).
396        let sys = self
397            .stack
398            .root_handler(&false, &(), &netif, &(), self.crypto.weak_rand()?);
399        let combined = ChainedHandler::new(
400            EpClMatcher::new(Some(ROOT_ENDPOINT_ID), None),
401            sys,
402            &self.handler,
403        );
404        // Ethernet does not manage networks, so use the inert wireless net-ctl;
405        // the engine's connection-manager branch then stays dormant.
406        let im = self.stack.im(
407            &self.crypto,
408            (&self.handler, combined),
409            &self.kv,
410            NoopWirelessNetCtl::new(NetworkType::Ethernet),
411        );
412
413        let mut net_task = pin_alloc!(
414            self.stack.bump,
415            self.stack.run_oper_net(
416                &self.crypto,
417                &net_stack,
418                0, // TODO
419                core::future::pending(),
420                Option::<(NoNetwork, NoNetwork)>::None,
421            )
422        );
423
424        let mut mdns_task = pin_alloc!(
425            self.stack.bump,
426            self.stack
427                .run_oper_netif_mdns(&self.crypto, &net_stack, &netif, &mut mdns)
428        );
429
430        let mut im_task = pin_alloc!(self.stack.bump, self.stack.run_im_with_bump(&im));
431
432        let mut user_task = pin_alloc!(self.stack.bump, self.user_task.run(&net_stack, &netif));
433
434        select4(&mut net_task, &mut mdns_task, &mut im_task, &mut user_task)
435            .coalesce()
436            .await
437    }
438}