Skip to main content

rs_matter/
lib.rs

1/*
2 *
3 *    Copyright (c) 2022-2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18//! Native Rust implementation of the Matter protocol by CSA-IOT.
19//!
20//! This crate implements the Matter specification that can be run on embedded devices
21//! to build Matter-compatible smart-home/IoT devices.
22//!
23//! Look at the examples project in the workspace for example applications built using this crate, and the tests directory for unit tests.
24//!
25//! Start off exploring by going to the [Matter] object.
26#![cfg_attr(not(feature = "std"), no_std)]
27#![allow(async_fn_in_trait)]
28#![allow(unknown_lints)]
29#![allow(clippy::uninlined_format_args)]
30#![recursion_limit = "1024"]
31
32use crate::crypto::Crypto;
33use crate::dm::clusters::basic_info::{
34    self, BasicInfoConfig, BasicInfoSettings, FULL_CLUSTER as BASIC_INFO_CLUSTER,
35};
36use crate::dm::clusters::dev_att::DeviceAttestation;
37use crate::dm::clusters::time_sync::Rtc;
38use crate::dm::endpoints::ROOT_ENDPOINT_ID;
39use crate::dm::AttrChangeNotifier;
40use crate::error::{Error, ErrorCode};
41use crate::fabric::Fabrics;
42use crate::failsafe::FailSafe;
43use crate::pairing::qr::{
44    no_optional_data, CommFlowType, NoOptionalData, Qr, QrPayload, QrTextType,
45};
46use crate::pairing::DiscoveryCapabilities;
47use crate::persist::{KvBlobStore, KvBlobStoreAccess, Persist, BASIC_INFO_KEY};
48use crate::sc::pase::spake2p::{Spake2pVerifierPassword, SPAKE2P_VERIFIER_SALT_ZEROED};
49use crate::sc::pase::Pase;
50use crate::transport::network::MatterLocalService;
51use crate::transport::network::{NetworkMulticast, NetworkReceive, NetworkSend};
52use crate::transport::session::Sessions;
53use crate::transport::{
54    PacketBufferExternalAccess, Transport, TransportRunner, MAX_RX_BUF_SIZE, MAX_TX_BUF_SIZE,
55};
56use crate::utils::cell::RefCell;
57use crate::utils::init::{init, Init};
58use crate::utils::storage::pooled::Buffers;
59use crate::utils::sync::blocking::Mutex;
60
61use rand_core::RngCore;
62
63#[cfg(feature = "alloc")]
64extern crate alloc;
65
66// This mod MUST go first, so that the others see its macros.
67pub(crate) mod fmt;
68
69pub mod acl;
70pub mod attest;
71pub mod bdx;
72pub mod cert;
73pub mod crypto;
74pub mod dm;
75pub mod error;
76pub mod fabric;
77pub mod failsafe;
78pub mod group_keys;
79pub mod im;
80pub mod onboard;
81pub mod pairing;
82pub mod persist;
83pub mod respond;
84pub mod sc;
85pub mod tlv;
86pub mod transport;
87pub mod utils;
88
89/// Re-export several crates
90///
91/// This is necessary for crates used in the code generated by the proc-macros
92pub mod reexport {
93    pub use bitflags;
94    #[cfg(feature = "defmt")]
95    pub use defmt;
96    #[cfg(feature = "log")]
97    pub use log;
98    pub use strum;
99}
100
101#[cfg(feature = "alloc")]
102#[macro_export]
103macro_rules! alloc {
104    ($val:expr) => {
105        alloc::boxed::Box::new($val)
106    };
107}
108
109#[cfg(not(feature = "alloc"))]
110#[macro_export]
111macro_rules! alloc {
112    ($val:expr) => {
113        $val
114    };
115}
116
117/// The Matter UDP port
118pub const MATTER_PORT: u16 = 5540;
119
120/// Device basic commissioning data
121#[derive(Debug, Clone)]
122#[cfg_attr(feature = "defmt", derive(defmt::Format))]
123pub struct BasicCommData {
124    /// The password which is necessary to authenticate the device in either
125    /// initial commissioning, or when the basic commissioning window is opened
126    pub password: Spake2pVerifierPassword,
127    /// The 12-bit discriminator used to differentiate between multiple devices
128    pub discriminator: u16,
129}
130
131/// The primary Matter Object
132pub struct Matter<'a> {
133    /// The internal state of the Matter Object, protected by a mutex for concurrent access from different threads and async tasks.
134    state: Mutex<RefCell<MatterState>>,
135    /// The transport state of the Matter Object
136    transport: Transport,
137    /// The basic information configuration for this Matter device
138    dev_det: &'a BasicInfoConfig<'a>,
139    /// The basic commissioning data for this Matter device
140    dev_comm: BasicCommData,
141    /// The device attestation data fetcher for this Matter device
142    dev_att: &'a dyn DeviceAttestation,
143    /// The port number on which the Matter stack will listen for incoming connections
144    port: u16,
145    /// The scratch buffer used by the key-value persistence machinery for
146    /// (de)serializing BLOBs. Behind a blocking mutex so [`Matter::kv`] can
147    /// recombine it with the user's raw [`KvBlobStore`](crate::persist::KvBlobStore)
148    /// into a full [`KvBlobStoreAccess`](crate::persist::KvBlobStoreAccess). Its
149    /// size is set by the `kv-blob-store-*` Cargo features (see
150    /// [`KV_BUF_SIZE`](crate::persist::KV_BUF_SIZE)).
151    kv_buf: Mutex<RefCell<[u8; crate::persist::KV_BUF_SIZE]>>,
152}
153
154impl<'a> Matter<'a> {
155    /// Create a new Matter object.
156    ///
157    /// # Parameters
158    /// * dev_det: An object of type [BasicInfoConfig].
159    /// * dev_comm: An object of type [BasicCommData]. This object contains the basic commissioning
160    ///   data required for the device.
161    /// * dev_att: An object that implements the trait [DevAttDataFetcher]. Any Matter device
162    ///   requires a set of device attestation certificates and keys. It is the responsibility of
163    ///   this object to return the device attestation details when queried upon.
164    /// * port: The port number on which the Matter stack will listen for incoming connections.
165    #[inline(always)]
166    pub const fn new(
167        dev_det: &'a BasicInfoConfig<'a>,
168        dev_comm: BasicCommData,
169        dev_att: &'a dyn DeviceAttestation,
170        port: u16,
171    ) -> Self {
172        Self {
173            state: Mutex::new(RefCell::new(MatterState::new())),
174            transport: Transport::new(dev_det),
175            dev_det,
176            dev_comm,
177            dev_att,
178            port,
179            kv_buf: Mutex::new(RefCell::new([0; crate::persist::KV_BUF_SIZE])),
180        }
181    }
182
183    /// Create an in-place initializer for a Matter object.
184    ///
185    /// # Parameters
186    /// * dev_det: An object of type [BasicInfoConfig].
187    /// * dev_comm: An object of type [BasicCommData]. This object contains the basic commissioning
188    ///   data required for the device.
189    /// * dev_att: An object that implements the trait [DevAttDataFetcher]. Any Matter device
190    ///   requires a set of device attestation certificates and keys. It is the responsibility of
191    ///   this object to return the device attestation details when queried upon.
192    /// * port: The port number on which the Matter stack will listen for incoming connections.
193    pub fn init(
194        dev_det: &'a BasicInfoConfig<'a>,
195        dev_comm: BasicCommData,
196        dev_att: &'a dyn DeviceAttestation,
197        port: u16,
198    ) -> impl Init<Self> {
199        init!(
200            Self {
201                state <- Mutex::init(RefCell::init(MatterState::init())),
202                transport <- Transport::init(dev_det),
203                dev_det,
204                dev_comm,
205                dev_att,
206                port,
207                kv_buf <- Mutex::init(RefCell::init(crate::utils::init::zeroed())),
208            }
209        )
210    }
211
212    pub fn dev_det(&self) -> &BasicInfoConfig<'_> {
213        self.dev_det
214    }
215
216    pub fn dev_att(&self) -> &dyn DeviceAttestation {
217        self.dev_att
218    }
219
220    pub fn dev_comm(&self) -> &BasicCommData {
221        &self.dev_comm
222    }
223
224    pub fn port(&self) -> u16 {
225        self.port
226    }
227
228    /// Combine a user-provided raw [`KvBlobStore`] with the scratch buffer owned
229    /// by this `Matter` object to obtain a full [`KvBlobStoreAccess`].
230    ///
231    /// This is the single entry point for persistence: the application passes its
232    /// raw store (sync `load`/`store`/`remove`) and gets back an access object
233    /// that recombines it with `Matter`'s feature-sized scratch buffer (see
234    /// [`KV_BUF_SIZE`](crate::persist::KV_BUF_SIZE)). The returned value is then
235    /// lent (by `&`) to [`Matter::load_persist`], [`Matter::reset_persist`],
236    /// [`InteractionModelState::load_persist`](crate::im::InteractionModelState::load_persist)
237    /// and [`InteractionModel::new`](crate::im::InteractionModel::new).
238    ///
239    /// # Arguments
240    /// - `store` - the raw [`KvBlobStore`] implementation to wrap
241    pub fn kv<'s, S: KvBlobStore + 's>(&'s self, store: S) -> impl KvBlobStoreAccess + 's {
242        crate::persist::SharedKvBlobStore::new(store, &self.kv_buf)
243    }
244
245    /// Get a reference to the transport state of this Matter object.
246    ///
247    /// All transport-related state and operations (mDNS change/resolve
248    /// rendezvous, session/group notifications, RX/TX buffers, exchange
249    /// initiation/acceptance) live on [`Transport`].
250    #[inline(always)]
251    pub const fn transport(&self) -> &Transport {
252        &self.transport
253    }
254
255    pub fn transport_rx_buffer(&self) -> PacketBufferExternalAccess<'_, MAX_RX_BUF_SIZE> {
256        self.transport().rx_buffer()
257    }
258
259    pub fn transport_tx_buffer(&self) -> PacketBufferExternalAccess<'_, MAX_TX_BUF_SIZE> {
260        self.transport().tx_buffer()
261    }
262
263    /// A utility method to replace the initial Device Attestation with another one.
264    pub fn replace_dev_att(&mut self, dev_att: &'a dyn DeviceAttestation) {
265        self.dev_att = dev_att;
266    }
267
268    /// Print the standard QR code text to the console
269    ///
270    /// The printed QR code text corresponds to the standard commissioning flow (i.e. `CommFlowType::Standard`)
271    /// and contains no optional data.
272    ///
273    /// This method is useful primarily during development, when the Matter device is
274    /// attached to a console. It is expected that the developer will call this method prior to running the Matter transport.
275    ///
276    /// # Arguments
277    /// - `disc_caps`: The discovery capabilities to be used in the QR code payload
278    pub fn print_standard_qr_text(&self, disc_caps: DiscoveryCapabilities) -> Result<(), Error> {
279        let rx_buf = self.transport().rx_buffer();
280
281        let mut buf = rx_buf.get_immediate().ok_or(ErrorCode::NoMemory)?;
282        let buf = &mut *buf;
283
284        let payload = self.standard_qr_payload(disc_caps)?;
285
286        let (text, _) = payload.as_str(buf)?;
287
288        // Do not remove this logging line or change its formatting.
289        // C++ E2E tests rely on this log line to grep the QR code
290        info!("SetupQRCode: [{}]", text);
291
292        Ok(())
293    }
294
295    /// Print the standard QR code to the console
296    ///
297    /// The printed QR code corresponds to the standard commissioning flow (i.e. `CommFlowType::Standard`)
298    /// and contains no optional data.
299    ///
300    /// This method is useful primarily during development, when the Matter device is
301    /// attached to a console. It is expected that the developer will call this method prior to running the Matter transport.
302    ///
303    /// # Arguments
304    /// - `text_type`: The type of text representation to use when printing the QR code
305    /// - `disc_caps`: The discovery capabilities to be used in the QR code payload
306    pub fn print_standard_qr_code(
307        &self,
308        text_type: QrTextType,
309        disc_caps: DiscoveryCapabilities,
310    ) -> Result<(), Error> {
311        // Also print the pairing code for convenience
312        info!(
313            "PairingCode: [{}]",
314            self.dev_comm.compute_pretty_pairing_code()
315        );
316
317        let rx_buf = self.transport().rx_buffer();
318
319        let mut buf = rx_buf.get_immediate().ok_or(ErrorCode::NoMemory)?;
320        let buf = &mut *buf;
321
322        let payload = self.standard_qr_payload(disc_caps)?;
323
324        let (text, buf) = payload.as_str(buf)?;
325
326        let (tmp_buf, out_buf) = buf.split_at_mut(buf.len() / 2);
327
328        let qr = Qr::compute(text, tmp_buf, out_buf)?;
329
330        const BORDER_SIZE: u8 = 4;
331
332        for y in qr.lines_range(text_type, BORDER_SIZE) {
333            info!(
334                "{}",
335                qr.line_as_str(text_type, BORDER_SIZE, false, false, y, tmp_buf)?
336                    .0
337            );
338        }
339
340        Ok(())
341    }
342
343    /// Return the standard QR code payload
344    ///
345    /// The returned QR code payload corresponds to the standard commissioning flow (i.e. `CommFlowType::Standard`)
346    /// and contains no optional data.
347    ///
348    /// # Arguments
349    /// - `disc_caps`: The discovery capabilities to be used in the QR code payload
350    fn standard_qr_payload(
351        &self,
352        disc_caps: DiscoveryCapabilities,
353    ) -> Result<QrPayload<'_, NoOptionalData>, Error> {
354        let payload = QrPayload::new_from_basic_info(
355            disc_caps,
356            CommFlowType::Standard,
357            self.dev_comm.clone(),
358            self.dev_det,
359            no_optional_data as _,
360        );
361
362        Ok(payload)
363    }
364
365    /// Return `true` if there is at least one commissioned fabric
366    //
367    // TODO:
368    // The implementation of this method needs to change in future,
369    // because the current implementation does not really track whether
370    // `CommissioningComplete` had been actually received for the fabric.
371    //
372    // The fabric is created once we receive `AddNoc`, but that's just
373    // not enough. The fabric should NOT be considered commissioned until
374    // after we receive `CommissioningComplete` on behalf of a Case session
375    // for the fabric in question.
376    pub fn is_commissioned(&self) -> bool {
377        self.with_state(|state| state.fabrics.iter().count() > 0)
378    }
379
380    /// Open a basic commissioning window
381    ///
382    /// The method will return an error if the commissioning window cannot be opened
383    /// (due to another window already being opened, for example).
384    ///
385    /// # Arguments
386    /// - `timeout_secs`: The timeout in seconds for the basic commissioning window
387    ///
388    /// **Note:** This is the low-level building block that mutates PASE
389    /// state and routes a `notify_cluster_changed(...)` to subscribers
390    /// via `notify`, but does **not** bump the per-cluster `Dataver` of
391    /// `AdministratorCommissioning` — a subsequent dataver-filtered
392    /// read could therefore cache-hit and miss the change. Application
393    /// code that holds a `InteractionModel` should prefer
394    /// [`crate::im::InteractionModel::open_basic_comm_window`], which delegates
395    /// here and additionally bumps dataver via its
396    /// [`AttrChangeNotifier`] impl.
397    pub fn open_basic_comm_window<C: Crypto>(
398        &self,
399        timeout_secs: u16,
400        crypto: C,
401        notify: &dyn AttrChangeNotifier,
402    ) -> Result<(), Error> {
403        let notify_mdns = || self.transport().notify_mdns_changed();
404        let notify_change = |endpt_id, clust_id| notify.notify_cluster_changed(endpt_id, clust_id);
405
406        self.with_state(|state| {
407            let mut rand = crypto.rand()?;
408
409            let mdns_id = rand.next_u64();
410
411            let mut salt = SPAKE2P_VERIFIER_SALT_ZEROED;
412            rand.fill_bytes(salt.access_mut());
413
414            state.pase.open_basic_comm_window(
415                mdns_id,
416                salt.access(),
417                self.dev_comm.password.reference(),
418                self.dev_comm.discriminator,
419                timeout_secs,
420                None,
421                notify_mdns,
422                notify_change,
423            )
424        })
425    }
426
427    /// Close the commissioning window (basic or other)
428    ///
429    /// The method will return Ok(false) if there is no active PASE commissioning window to close.
430    ///
431    /// **Note:** As with [`Matter::open_basic_comm_window`], this does
432    /// not bump the per-cluster `Dataver` of
433    /// `AdministratorCommissioning`. Prefer
434    /// [`crate::im::InteractionModel::close_comm_window`] when a `InteractionModel`
435    /// is available.
436    pub fn close_comm_window(&self, notify: &dyn AttrChangeNotifier) -> Result<bool, Error> {
437        let notify_mdns = || self.transport().notify_mdns_changed();
438        let notify_change = |endpt_id, clust_id| notify.notify_cluster_changed(endpt_id, clust_id);
439
440        self.with_state(|state| state.pase.close_comm_window(notify_mdns, notify_change))
441    }
442
443    /// Bump `BasicInformation::ConfigurationVersion` by one, persist
444    /// the new value via `kv`, and route an attribute-change
445    /// notification to subscribers via `notify`.
446    ///
447    /// Per Matter Core Spec, the device MUST bump
448    /// this attribute on any change to its exposed fixed-quality
449    /// surface (a firmware update that adds or removes functionality,
450    /// internal reconfiguration that changes any `F`-quality attribute,
451    /// bridged-node add/remove on a bridge). `rs-matter` cannot detect
452    /// such events on its own — the application drives the bump.
453    ///
454    /// **Note:** Like the other low-level mutators on `Matter`, this
455    /// does **not** bump the per-cluster `Dataver` of
456    /// `BasicInformation`. A subsequent dataver-filtered read could
457    /// therefore cache-hit and miss the change. Application code that
458    /// holds a `InteractionModel` should prefer
459    /// [`crate::im::InteractionModel::bump_configuration_version`], which
460    /// delegates here and additionally bumps dataver via its
461    /// [`AttrChangeNotifier`] impl.
462    ///
463    /// Returns the new `ConfigurationVersion` value.
464    pub fn bump_configuration_version<S: KvBlobStoreAccess>(
465        &self,
466        kv: S,
467        notify: &dyn AttrChangeNotifier,
468    ) -> Result<u32, Error> {
469        let mut persist = Persist::new(kv);
470
471        let new_version = self.with_state(|state| {
472            let new_version = state.basic_info_settings.bump_configuration_version();
473
474            persist.store_tlv(BASIC_INFO_KEY, &state.basic_info_settings)?;
475
476            notify.notify_attr_changed(
477                ROOT_ENDPOINT_ID,
478                BASIC_INFO_CLUSTER.id,
479                basic_info::AttributeId::ConfigurationVersion as _,
480            );
481
482            Ok::<_, Error>(new_version)
483        })?;
484
485        persist.run()?;
486
487        Ok(new_version)
488    }
489
490    /// Create a new transport runner instance
491    pub fn transport_runner<C: Crypto>(&self, crypto: C) -> TransportRunner<'_, C> {
492        TransportRunner::new(self, crypto)
493    }
494
495    /// Run the Matter transport layer.
496    ///
497    /// # Arguments
498    /// - `crypto`: The crypto backend
499    /// - `send`: The network send interface
500    /// - `recv`: The network receive interface
501    /// - `multicast`: The multicast network interface (for receiving groupcast messages)
502    ///   When running on top of non-IP networks like BLE pass a no-op implementation like `NoNetwork` here and the multicast functionality will be disabled.
503    pub async fn run<C, S, R, M>(
504        &self,
505        crypto: C,
506        send: S,
507        recv: R,
508        multicast: M,
509    ) -> Result<(), Error>
510    where
511        C: Crypto,
512        S: NetworkSend,
513        R: NetworkReceive,
514        M: NetworkMulticast,
515    {
516        let mut transport_runner = self.transport_runner(crypto);
517
518        transport_runner.run(send, recv, multicast).await
519    }
520
521    /// Access the Matter state by invoking a closure with a mutable reference to the state.
522    pub fn with_state<F, R>(&self, f: F) -> R
523    where
524        F: FnOnce(&mut MatterState) -> R,
525    {
526        self.state.lock(|state| {
527            let mut state = state.borrow_mut();
528            f(&mut state)
529        })
530    }
531
532    /// Access the Real-Time-clock by invoking a closure with a mutable reference to it.
533    pub fn with_rtc<F, R>(&self, f: F) -> R
534    where
535        F: FnOnce(&mut Rtc) -> R,
536    {
537        self.with_state(|state| f(&mut state.rtc))
538    }
539
540    /// Reset the transport layer by clearing all sessions, exchanges, the RX buffer and the TX buffer
541    /// NOTE: User should be careful _not_ to call this method while the transport layer and/or the built-in mDNS is running.
542    pub fn reset_transport(&self) -> Result<(), Error> {
543        self.with_state(|state| {
544            state.sessions.reset();
545
546            self.transport().reset()
547        })
548    }
549
550    /// Reset the Matter persistable state by removing all fabrics and resetting basic info settings
551    ///
552    /// Arguments:
553    /// - `kv`: The key-value store access (obtained via [`Matter::kv`]) to remove the fabrics
554    ///   and basic info settings from. Provides both the store and the scratch buffer.
555    pub async fn reset_persist<K: KvBlobStoreAccess>(&self, kv: K) -> Result<(), Error> {
556        self.with_state(|state| {
557            // The KV ops are sync, so do them all inside a single `access` closure.
558            kv.access(|mut store, buf| {
559                state.fabrics.reset_persist(&mut store, buf)?;
560                state.basic_info_settings.reset_persist(&mut store, buf)?;
561                state.rtc.reset_persist(&mut store, buf)?;
562
563                Ok::<_, Error>(())
564            })
565        })?;
566
567        self.transport().notify_mdns_changed();
568
569        Ok(())
570    }
571
572    /// Load fabrics from the given data
573    ///
574    /// Arguments:
575    /// - `kv`: The key-value store access (obtained via [`Matter::kv`]) to load the fabrics
576    ///   and basic info settings from. Provides both the store and the scratch buffer.
577    pub async fn load_persist<K: KvBlobStoreAccess>(&self, kv: K) -> Result<(), Error> {
578        self.with_state(|state| {
579            // The KV ops are sync, so do them all inside a single `access` closure.
580            kv.access(|mut store, buf| {
581                state.fabrics.load_persist(&mut store, buf)?;
582                state.basic_info_settings.load_persist(&mut store, buf)?;
583                state.rtc.load_persist(&mut store, buf)?;
584
585                Ok::<_, Error>(())
586            })
587        })?;
588
589        self.transport().notify_mdns_changed();
590
591        Ok(())
592    }
593
594    /// Invoke the given closure for each currently published Matter mDNS service.
595    pub fn mdns_services<F>(&self, mut f: F) -> Result<(), Error>
596    where
597        F: FnMut(MatterLocalService) -> Result<(), Error>,
598    {
599        debug!("=== Currently published mDNS services");
600
601        self.with_state(|state| {
602            if let Some(comm_window) = state.pase.comm_window() {
603                // Do not remove this logging line or change its formatting.
604                // C++ E2E tests rely on this log line to determine when the mDNS service is published
605                debug!("mDNS service published: {:?}", comm_window.mdns_service());
606
607                f(comm_window.mdns_service())?;
608            }
609
610            for fabric in state.fabrics.iter() {
611                if let Some(service) = fabric.mdns_service() {
612                    // Do not remove this logging line or change its formatting.
613                    // C++ E2E tests rely on this log line to determine when the mDNS service is published
614                    debug!("mDNS service published: {:?}", service);
615
616                    f(service)?;
617                }
618            }
619
620            debug!("===");
621
622            Ok(())
623        })
624    }
625}
626
627/// The internal state of the Matter Object
628///
629/// Public for unit tests.
630pub struct MatterState {
631    /// All fabrics
632    ///
633    /// Public for unit tests
634    pub fabrics: Fabrics,
635    /// All sessions
636    sessions: Sessions,
637    /// The PASE session state
638    pase: Pase,
639    /// The Failsafe state
640    failsafe: FailSafe,
641    /// The mutable basic information settings
642    basic_info_settings: BasicInfoSettings,
643    /// Real Time Clock state and Last-Known-Good UTC Time tracking (Matter Core spec).
644    rtc: Rtc,
645}
646
647impl MatterState {
648    /// Create a new instance of MatterState
649    #[inline(always)]
650    const fn new() -> Self {
651        Self {
652            fabrics: Fabrics::new(),
653            sessions: Sessions::new(),
654            pase: Pase::new(),
655            failsafe: FailSafe::new(),
656            basic_info_settings: BasicInfoSettings::new(),
657            rtc: Rtc::new(),
658        }
659    }
660
661    /// Return an in-place initializer for MatterState
662    fn init() -> impl Init<Self> {
663        init!(Self {
664            fabrics <- Fabrics::init(),
665            sessions <- Sessions::init(),
666            pase <- Pase::init(),
667            failsafe <- FailSafe::init(),
668            basic_info_settings <- BasicInfoSettings::init(),
669            rtc <- Rtc::init(),
670        })
671    }
672}
673
674#[cfg(test)]
675pub mod test {
676    use crate::Matter;
677
678    pub fn test_matter() -> Matter<'static> {
679        Matter::new(
680            &crate::dm::devices::test::TEST_DEV_DET,
681            crate::dm::devices::test::TEST_DEV_COMM,
682            &crate::dm::devices::test::TEST_DEV_ATT,
683            0,
684        )
685    }
686}