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