Skip to main content

rs_matter/
im.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//! The Interaction Model as defined by the Matter Core spec: the interactions
19//! (Read, Subscribe/Report, Write, Invoke, Timed) and their TLV-serde encoding
20//! types, plus the engine - [`InteractionModel`] - that drives those
21//! interactions against a [`crate::dm`] data model.
22//!
23//! It also contains a requestor-side IM client ([`client`]) and a very simple
24//! responder - [`busy`] - which always returns a busy status code to all
25//! incoming IM requests.
26
27use core::cell::Cell;
28use core::num::NonZeroU8;
29
30use crate::utils::sync::blocking::Mutex;
31use core::pin::pin;
32
33use embassy_futures::select::{select3, select4};
34use embassy_time::{Instant, Timer};
35
36use crate::acl::Accessor;
37use crate::crypto::Crypto;
38use crate::dm::clusters::net_comm::{
39    NetCtl, NetCtlStatus, NetworkType, Networks, NetworksAccess, SharedNetworks,
40};
41use crate::dm::clusters::wifi_diag::WirelessDiag;
42use crate::dm::networks::eth::EthNetwork;
43use crate::dm::networks::wireless::{NoopWirelessNetCtl, WirelessMgr, MAX_CREDS_SIZE};
44use crate::dm::networks::NetChangeNotif;
45use crate::dm::{
46    AsyncHandler, AttrChangeNotifier, AttrDetails, Attribute, DataModel, EventEmitter,
47    HandlerContext, LifecycleOp, MatchContextInstance, Metadata, ReportDataHandler,
48};
49use crate::error::{Error, ErrorCode};
50use crate::im::events::{EventReader, EventTLVWrite, Events, DEFAULT_MAX_EVENTS_BUF_SIZE};
51use crate::im::invoker::HandlerInvoker;
52use crate::im::subscriptions::{
53    ReportContext, Subscriptions, SubscriptionsBuffers, DEFAULT_MAX_SUBSCRIPTIONS,
54};
55use crate::persist::{KvBlobStoreAccess, NETWORKS_KEY};
56use crate::respond::ExchangeHandler;
57use crate::tlv::{get_root_node_struct, FromTLV, Nullable, TLVElement, TLVTag, TLVWrite, ToTLV};
58use crate::transport::exchange::{Exchange, ExchangeId, MAX_EXCHANGE_TX_BUF_SIZE};
59use crate::utils::init::{init, Init};
60use crate::utils::select::Coalesce;
61use crate::utils::storage::pooled::Buffers;
62use crate::utils::storage::WriteBuf;
63use crate::Matter;
64
65pub use encoding::*;
66pub use expand::{expand_invoke, expand_read, expand_write};
67
68pub mod busy;
69pub mod client;
70pub mod encoding;
71pub mod events;
72pub mod expand;
73pub mod invoker;
74pub mod subscriptions;
75
76/// Resource-utilisation metrics for the node, as reported by
77/// `GeneralDiagnostics::DeviceLoadStatus`.
78#[derive(Debug, Clone, Default, PartialEq, Eq)]
79#[cfg_attr(feature = "defmt", derive(defmt::Format))]
80pub struct DeviceLoad {
81    /// Subscriptions currently established on the node, across all fabrics.
82    pub current_subscriptions: u16,
83    /// Subscriptions currently established on the fabric of the reading
84    /// subject. Zero when the figures were gathered without a fabric in scope.
85    pub current_subscriptions_for_fabric: u16,
86    /// Subscriptions accepted since boot, including those since torn down.
87    pub total_subscriptions_established: u32,
88    /// Interaction Model messages sent since boot.
89    pub total_im_messages_sent: u32,
90    /// Interaction Model messages received since boot.
91    pub total_im_messages_received: u32,
92}
93
94/// Interaction-Model state threaded down to cluster handlers via
95/// [`HandlerContext::im_stats`](crate::dm::HandlerContext::im_stats).
96///
97pub trait ImStats {
98    /// The node's resource-utilisation metrics, for
99    /// `GeneralDiagnostics::DeviceLoadStatus`.
100    ///
101    /// `fab_idx` is the fabric of the reading subject, for
102    /// `CurrentSubscriptionsForFabric`; pass `None` when no fabric is in scope.
103    fn device_load(&self, fab_idx: Option<NonZeroU8>) -> DeviceLoad;
104}
105
106impl<T> ImStats for &T
107where
108    T: ImStats,
109{
110    fn device_load(&self, fab_idx: Option<NonZeroU8>) -> DeviceLoad {
111        (**self).device_load(fab_idx)
112    }
113}
114
115/// An `ExchangeHandler` implementation capable of handling responder exchanges for the Interaction Model protocol.
116/// The mutable, owned-together state a [`InteractionModel`] operates on: the
117/// subscriptions table, the events queue and the network store.
118///
119/// Allocating these as a single value (rather than three separate locals wired
120/// up by hand at every call site) is the whole point: construct one
121/// `InteractionModelState`, then hand a reference to it to [`InteractionModel::new`]. Each of
122/// the three pieces keeps its own lock internally (the proven multi-lock model);
123/// folding them behind a single mutex is a possible later refinement.
124///
125/// `N` is the (raw) [`Networks`] implementation — e.g.
126/// [`EthNetwork`](crate::dm::networks::eth::EthNetwork) or
127/// [`WirelessNetworks`](crate::dm::networks::wireless::WirelessNetworks); it is
128/// wrapped internally in a [`SharedNetworks`] so the data model and (later) the
129/// wireless manager can share it. `NS`/`NE` bound the subscription table and the
130/// event-buffer size and default to [`DEFAULT_MAX_SUBSCRIPTIONS`] /
131/// [`DEFAULT_MAX_EVENTS_BUF_SIZE`].
132pub struct InteractionModelState<
133    N,
134    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
135    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
136> {
137    subscriptions: Subscriptions<NS>,
138    events: Events<NE>,
139    networks: SharedNetworks<N>,
140    start_up_emitted: Mutex<Cell<bool>>,
141}
142
143impl<N, const NS: usize, const NE: usize> InteractionModelState<N, NS, NE> {
144    /// Create a new state instance backed by the given (raw) [`Networks`] store.
145    pub const fn new(networks: N) -> Self {
146        Self {
147            subscriptions: Subscriptions::new(),
148            events: Events::new(),
149            networks: SharedNetworks::new(networks),
150            start_up_emitted: Mutex::new(Cell::new(false)),
151        }
152    }
153
154    /// Return an in-place initializer for the state (for large `NE`, to
155    /// avoid a big temporary on the stack).
156    pub fn init(networks: impl Init<N>) -> impl Init<Self> {
157        init!(Self {
158            subscriptions <- Subscriptions::init(),
159            events <- Events::init(),
160            networks <- SharedNetworks::init(networks),
161            start_up_emitted: Mutex::new(Cell::new(false)),
162        })
163    }
164
165    /// Suppress the `BasicInformation::StartUp` event that
166    /// [`InteractionModel::run`] would otherwise emit when it first starts.
167    ///
168    /// Call before `run` when starting the stack does *not* correspond to a
169    /// device boot - e.g. a warm restart of the Matter stack within a running
170    /// process, or a test fixture that needs a deterministic events queue.
171    pub fn suppress_start_up_event(&self) {
172        self.start_up_emitted.lock(|flag| flag.set(true));
173    }
174
175    /// Reset this state's persisted contents to factory defaults - the
176    /// events-queue epoch, the network store and (if compiled in) the persisted
177    /// subscriptions - removing them from `kv` using the scratch buffer
178    /// provided by `kv`.
179    ///
180    /// Driven by [`InteractionModel::factory_reset`].
181    fn reset_persist<K>(&self, kv: K) -> Result<(), Error>
182    where
183        K: KvBlobStoreAccess,
184        N: Networks,
185    {
186        // The KV ops are sync, so do them all inside a single `access` closure.
187        kv.access(|store, buf| {
188            // The event-number epoch.
189            self.events.reset_persist(&mut *store, buf)?;
190
191            // The network store.
192            self.networks.with_raw(|networks| networks.reset())?;
193            store.remove(NETWORKS_KEY, buf)?;
194
195            // Every persisted subscription record.
196            #[cfg(feature = "persistent-subscriptions")]
197            self.subscriptions.reset_persist(&mut *store, buf)?;
198
199            Ok(())
200        })
201    }
202
203    /// Re-hydrate this state's persisted contents - the events-queue epoch (so
204    /// event numbers are not reused across reboots) and the network store - using
205    /// the scratch buffer provided by `kv`.
206    ///
207    /// Driven by [`InteractionModel::startup`].
208    fn load_persist<K>(&self, kv: K) -> Result<(), Error>
209    where
210        K: KvBlobStoreAccess,
211        N: Networks,
212    {
213        // The KV ops are sync, so do them all inside a single `access` closure.
214        kv.access(|store, buf| {
215            // The event-number epoch.
216            self.events.load_persist(&mut *store, buf)?;
217
218            // The network store.
219            self.networks.with_raw(|networks| {
220                networks.reset()?;
221                if let Some(data) = store.load(NETWORKS_KEY, buf)? {
222                    networks.load(data)?;
223                }
224
225                Ok(())
226            })
227        })
228    }
229
230    /// The subscriptions table.
231    pub const fn subscriptions(&self) -> &Subscriptions<NS> {
232        &self.subscriptions
233    }
234
235    /// The events queue.
236    pub const fn events(&self) -> &Events<NE> {
237        &self.events
238    }
239
240    /// The network store (wrapped for shared, change-notifying access).
241    pub const fn networks(&self) -> &SharedNetworks<N> {
242        &self.networks
243    }
244}
245
246/// The implementation needs a `DataModel` instance to interact with the underlying clusters of the data model.
247///
248/// `NC` is the network controller type driving the (optional) wireless connection
249/// manager from [`InteractionModel::run`]. It defaults to [`NoopWirelessNetCtl`], which is
250/// the right choice for Ethernet (and what the convenience [`InteractionModel::new`]
251/// constructor wires up); wireless devices pass a real controller via
252/// [`InteractionModel::new_with_net_ctl`].
253pub struct InteractionModel<
254    'a,
255    C,
256    B,
257    T,
258    K,
259    N,
260    NC = NoopWirelessNetCtl,
261    R = (),
262    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
263    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
264> where
265    B: Buffers<IMBuffer>,
266{
267    matter: &'a Matter<'a>,
268    crypto: C,
269    buffers: &'a B,
270    kv: K,
271    net_ctl: NC,
272    subscriptions_buffers: SubscriptionsBuffers<'a, B, NS>,
273    state: &'a InteractionModelState<N, NS, NE>,
274    handler: T,
275    /// The controller-side `ReportData` consumer. Defaults to `()`, which
276    /// disowns every inbound report (the accessory role). A controller injects a
277    /// real one via [`InteractionModel::new_with_reports`].
278    report_handler: R,
279}
280
281/// A [`InteractionModelState`] for an Ethernet device: its network store is a fixed
282/// [`EthNetwork`], so call sites need only the (defaulted) subscription/event
283/// sizes. Pairs with [`EthInteractionModel`].
284pub type EthInteractionModelState<
285    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
286    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
287> = InteractionModelState<EthNetwork<'static>, NS, NE>;
288
289/// A [`InteractionModel`] for an Ethernet device (network store fixed to [`EthNetwork`]),
290/// so the `N` generic disappears from call sites. Pairs with [`EthInteractionModelState`].
291pub type EthInteractionModel<
292    'a,
293    C,
294    B,
295    T,
296    K,
297    NC = NoopWirelessNetCtl,
298    R = (),
299    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
300    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
301> = InteractionModel<'a, C, B, T, K, EthNetwork<'static>, NC, R, NS, NE>;
302
303/// A [`InteractionModelState`] for a wireless device, parameterized by the concrete
304/// wireless network store `N` (e.g. `WifiNetworks<3>` or a Thread store). Pairs
305/// with [`WirelessInteractionModel`].
306pub type WirelessInteractionModelState<
307    N,
308    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
309    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
310> = InteractionModelState<N, NS, NE>;
311
312/// A [`InteractionModel`] for a wireless device (network store `N`, network controller
313/// `NC`). Pairs with [`WirelessInteractionModelState`].
314pub type WirelessInteractionModel<
315    'a,
316    C,
317    B,
318    T,
319    K,
320    N,
321    NC,
322    R = (),
323    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
324    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
325> = InteractionModel<'a, C, B, T, K, N, NC, R, NS, NE>;
326
327impl<'a, C, B, T, K, N, const NS: usize, const NE: usize>
328    InteractionModel<'a, C, B, T, K, N, NoopWirelessNetCtl, (), NS, NE>
329where
330    C: Crypto,
331    B: Buffers<IMBuffer>,
332    T: DataModel,
333    K: KvBlobStoreAccess,
334    N: Networks,
335{
336    /// Create the data model for a device that does not need an operational
337    /// wireless connection manager (typically an Ethernet device).
338    ///
339    /// This is a convenience wrapper around [`InteractionModel::new_with_net_ctl`] that
340    /// fixes the network controller to an inert [`NoopWirelessNetCtl`], so
341    /// [`InteractionModel::run`]'s connection-management branch stays dormant.
342    ///
343    /// # Arguments
344    /// - `matter` - a reference to the `Matter` instance
345    /// - `buffers` - a reference to an implementation of `Buffers<IMBuffer>` which is used for allocating RX and TX buffers on the fly, when necessary
346    /// - `handler` - an instance of type `T` which implements the `DataModel` trait. This instance is used for interacting with the underlying
347    ///   clusters of the data model. Note that the expectations is for the user to provide a handler that handles the Matter system clusters
348    ///   as well (Endpoint 0), possibly by decorating her own clusters with the `rs_matter::dm::root_endpoint::with_` methods
349    /// - `kv` - an instance of type `K` which implements the `KvBlobStoreAccess` trait
350    ///   (obtain one via [`Matter::kv`]). This instance is used for interacting with the key-value blob store.
351    /// - `state` - a reference to the [`InteractionModelState`] holding the subscriptions table, the
352    ///   events queue and the network store (the latter parameterized by the `Networks`
353    ///   implementation `N`).
354    #[inline(always)]
355    pub fn new(
356        matter: &'a Matter<'a>,
357        crypto: C,
358        buffers: &'a B,
359        handler: T,
360        kv: K,
361        state: &'a InteractionModelState<N, NS, NE>,
362    ) -> Self {
363        Self::new_with_net_ctl(
364            matter,
365            crypto,
366            buffers,
367            handler,
368            kv,
369            NoopWirelessNetCtl::new(NetworkType::Ethernet),
370            state,
371        )
372    }
373}
374
375impl<'a, C, B, T, K, N, NC, const NS: usize, const NE: usize>
376    InteractionModel<'a, C, B, T, K, N, NC, (), NS, NE>
377where
378    C: Crypto,
379    B: Buffers<IMBuffer>,
380    T: DataModel,
381    K: KvBlobStoreAccess,
382    N: Networks,
383{
384    /// Create the data model with an explicit network controller `net_ctl`.
385    ///
386    /// Use this for wireless devices: `net_ctl` drives the operational connection
387    /// manager run from [`InteractionModel::run`] (and is typically the same controller
388    /// instance also wired into the `NetworkCommissioning` cluster handler). For
389    /// Ethernet devices prefer the [`InteractionModel::new`] convenience constructor.
390    ///
391    /// # Arguments
392    /// - `matter` - a reference to the `Matter` instance
393    /// - `buffers` - a reference to an implementation of `Buffers<IMBuffer>` which is used for allocating RX and TX buffers on the fly, when necessary
394    /// - `handler` - an instance of type `T` which implements the `DataModel` trait. This instance is used for interacting with the underlying
395    ///   clusters of the data model. Note that the expectations is for the user to provide a handler that handles the Matter system clusters
396    ///   as well (Endpoint 0), possibly by decorating her own clusters with the `rs_matter::dm::root_endpoint::with_` methods
397    /// - `kv` - an instance of type `K` which implements the `KvBlobStoreAccess` trait
398    ///   (obtain one via [`Matter::kv`]). This instance is used for interacting with the key-value blob store.
399    /// - `net_ctl` - the network controller (`NetCtl` + `WirelessDiag` + `NetChangeNotif`) used by
400    ///   the operational wireless connection manager driven from [`InteractionModel::run`].
401    /// - `state` - a reference to the [`InteractionModelState`] holding the subscriptions table, the
402    ///   events queue and the network store (the latter parameterized by the `Networks`
403    ///   implementation `N`).
404    #[inline(always)]
405    pub fn new_with_net_ctl(
406        matter: &'a Matter<'a>,
407        crypto: C,
408        buffers: &'a B,
409        handler: T,
410        kv: K,
411        net_ctl: NC,
412        state: &'a InteractionModelState<N, NS, NE>,
413    ) -> Self {
414        state.subscriptions.clear();
415
416        Self {
417            matter,
418            crypto,
419            buffers,
420            kv,
421            net_ctl,
422            subscriptions_buffers: SubscriptionsBuffers::new(),
423            state,
424            handler,
425            report_handler: (),
426        }
427    }
428}
429
430impl<'a, C, B, T, K, N, NC, R, const NS: usize, const NE: usize>
431    InteractionModel<'a, C, B, T, K, N, NC, R, NS, NE>
432where
433    C: Crypto,
434    B: Buffers<IMBuffer>,
435    T: DataModel,
436    K: KvBlobStoreAccess,
437    N: Networks,
438    R: ReportDataHandler,
439{
440    /// Create the data model with an explicit network controller `net_ctl` and a
441    /// [`ReportDataHandler`] `report_handler` — the controller / subscriber role.
442    ///
443    /// This is [`InteractionModel::new_with_net_ctl`] plus a report consumer:
444    /// after this node establishes subscriptions (via the IM client), the
445    /// publishers push `ReportData` on fresh inbound exchanges, which the
446    /// `InteractionModel` routes to `report_handler`. The default
447    /// (`new`/`new_with_net_ctl`) constructors leave the report handler as `()`,
448    /// which disowns every report — correct for a pure accessory.
449    ///
450    /// # Arguments
451    /// Same as [`InteractionModel::new_with_net_ctl`], plus:
452    /// - `report_handler` - an instance of type `R` implementing
453    ///   [`ReportDataHandler`], invoked once per received `ReportData` chunk.
454    #[inline(always)]
455    #[allow(clippy::too_many_arguments)]
456    pub fn new_with_reports(
457        matter: &'a Matter<'a>,
458        crypto: C,
459        buffers: &'a B,
460        handler: T,
461        kv: K,
462        net_ctl: NC,
463        report_handler: R,
464        state: &'a InteractionModelState<N, NS, NE>,
465    ) -> Self {
466        state.subscriptions.clear();
467
468        Self {
469            matter,
470            crypto,
471            buffers,
472            kv,
473            net_ctl,
474            subscriptions_buffers: SubscriptionsBuffers::new(),
475            state,
476            handler,
477            report_handler,
478        }
479    }
480
481    /// Get a reference to the `Matter` instance this data model is associated with.
482    pub const fn matter(&self) -> &'a Matter<'a> {
483        self.matter
484    }
485
486    pub const fn crypto(&self) -> &C {
487        &self.crypto
488    }
489
490    /// Open the basic commissioning window.
491    ///
492    /// Equivalent to [`Matter::open_basic_comm_window`] but additionally
493    /// bumps the data version of the `AdministratorCommissioning`
494    /// cluster on the root endpoint and routes the change to
495    /// subscribers — both happen automatically because this `InteractionModel`
496    /// is itself the [`AttrChangeNotifier`] passed down.
497    ///
498    /// Prefer this entry point over the `Matter` one for any code path
499    /// that has a `InteractionModel` available; `Matter::open_basic_comm_window`
500    /// is the building block we delegate to and does not bump dataver
501    /// (see its docs).
502    pub fn open_basic_comm_window(&self, timeout_secs: u16) -> Result<(), Error> {
503        self.matter
504            .open_basic_comm_window(timeout_secs, &self.crypto, self)
505    }
506
507    /// Close the active commissioning window.
508    ///
509    /// Equivalent to [`Matter::close_comm_window`] but additionally
510    /// bumps the `AdministratorCommissioning` dataver and routes
511    /// subscribers via this `InteractionModel`'s [`AttrChangeNotifier`]. See
512    /// `open_basic_comm_window` for the rationale.
513    pub fn close_comm_window(&self) -> Result<bool, Error> {
514        self.matter.close_comm_window(self)
515    }
516
517    /// Bump `BasicInformation::ConfigurationVersion` by one, persist
518    /// the new value, and notify subscribers (which also bumps the
519    /// `BasicInformation` cluster's dataver via this `InteractionModel`'s
520    /// [`AttrChangeNotifier`]).
521    ///
522    /// Per Matter Core Spec, callers MUST invoke this
523    /// whenever the node's exposed fixed-quality surface changes —
524    /// typically after a firmware update that adds or removes
525    /// functionality, after an internal reconfiguration that changes
526    /// any `F`-quality attribute (Descriptor::ServerList,
527    /// PartsList, …), or (for bridges) after a bridged node is added
528    /// or removed. It is not invoked automatically by `rs-matter`
529    /// because the library has no way to know about an application's
530    /// reconfiguration events.
531    ///
532    /// Returns the new `ConfigurationVersion` value.
533    pub fn bump_configuration_version(&self) -> Result<u32, Error> {
534        // Delegate to `Matter::bump_configuration_version` for the
535        // in-memory bump + persist; pass `self` as the
536        // `AttrChangeNotifier` so the cluster's `Dataver` is bumped
537        // too (the `Matter`-level call by itself only routes
538        // subscribers and persists).
539        self.matter.bump_configuration_version(&self.kv, self)
540    }
541
542    /// Bring the Data Model to its operational state after a reboot.
543    ///
544    /// Call once, after constructing the `InteractionModel` and before running
545    /// it or serving exchanges. In order:
546    /// - Re-hydrates the [`InteractionModelState`] - the events-queue epoch (so
547    ///   event numbers are not reused across reboots) and the network store;
548    /// - Replays any persisted subscriptions into the reporter's table (if the
549    ///   `persistent-subscriptions` feature is enabled), so a subscriber that
550    ///   had a subscription before this reboot keeps receiving reports instead
551    ///   of having to notice the loss and re-subscribe;
552    /// - Broadcasts [`LifecycleOp::Startup`] to every cluster handler in the
553    ///   data model, so handlers with a persistence story of their own (which
554    ///   the Interaction Model otherwise treats as opaque) can re-hydrate their
555    ///   state from [`HandlerContext::kv`].
556    ///
557    /// The `Matter`-level counterpart is [`Matter::startup`] - call that one
558    /// first.
559    pub async fn startup(&self) -> Result<(), Error> {
560        self.state.load_persist(&self.kv)?;
561
562        // Needs the events watermark loaded just above.
563        self.resume_subscriptions()?;
564
565        self.handler.lifecycle(self, LifecycleOp::Startup)
566    }
567
568    /// Factory-reset the Data Model persistable state.
569    ///
570    /// The counterpart of [`InteractionModel::startup`]. In order:
571    /// - Resets the [`InteractionModelState`] persisted contents to factory
572    ///   defaults - the events-queue epoch, the network store and (if compiled
573    ///   in) the persisted subscriptions - removing them from the KV store;
574    /// - Broadcasts [`LifecycleOp::FactoryReset`] to every cluster handler in
575    ///   the data model, so handlers can reset their own state and remove their
576    ///   persisted data from [`HandlerContext::kv`].
577    ///
578    /// Call when the node is factory-reset, alongside the `Matter`-level
579    /// counterpart, [`Matter::factory_reset`].
580    pub async fn factory_reset(&self) -> Result<(), Error> {
581        self.state.reset_persist(&self.kv)?;
582
583        self.handler.lifecycle(self, LifecycleOp::FactoryReset)
584    }
585
586    /// Run the Data Model instance.
587    ///
588    /// This drives the IM timeout checks, the data-model handler's own background
589    /// job, the subscriptions reporting loop, and - for wireless devices - the
590    /// operational connection manager (inert for Ethernet, where `net_ctl` is a
591    /// [`NoopWirelessNetCtl`]).
592    /// Emit the `BasicInformation::StartUp` event, once per process lifetime.
593    ///
594    /// Matter Core spec: the node emits `StartUp` "upon completion of a boot
595    /// or reboot process"; the closest stack-observable moment is the start of
596    /// [`Self::run`], i.e. when the node becomes operational. The stack owns
597    /// this (rather than a public emit API the application must remember to
598    /// call) so every device is conformant by default. Event numbers stay
599    /// monotonic across reboots via the events store's epoch persistence, so
600    /// each boot's `StartUp` is a fresh, correctly-numbered event.
601    ///
602    /// Failure to emit (e.g. events buffer exhaustion) is logged, not fatal:
603    /// a missing diagnostic event must not take the node down.
604    fn emit_start_up_once(&self) {
605        let emit = self.state.start_up_emitted.lock(|flag| !flag.replace(true));
606
607        if emit {
608            let result = crate::dm::clusters::decl::basic_information::StartUp::emit_for(
609                self,
610                crate::dm::endpoints::ROOT_ENDPOINT_ID,
611                |event| event.software_version(self.matter.dev_det().sw_ver)?.end(),
612            );
613
614            if let Err(e) = result {
615                warn!("Failed to emit the StartUp event: {:?}", e);
616            }
617        }
618    }
619
620    pub async fn run(&self) -> Result<(), Error>
621    where
622        NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif,
623    {
624        self.emit_start_up_once();
625
626        let mut timeouts = pin!(self.run_timeout_checks());
627        let mut handler = pin!(self.handler.run(self));
628        let mut subs = pin!(self.process_subscriptions(self.matter));
629        let mut net = pin!(self.run_net_mgr());
630
631        select4(&mut timeouts, &mut handler, &mut subs, &mut net)
632            .coalesce()
633            .await
634    }
635
636    /// Drive the operational wireless connection manager.
637    ///
638    /// For Ethernet devices (`net_ctl.net_type() == NetworkType::Ethernet`) there
639    /// is nothing to manage, so this future simply pends forever. For wireless
640    /// devices it runs a [`WirelessMgr`] over the network store, cycling through
641    /// the registered networks and (re)connecting as needed once commissioned.
642    async fn run_net_mgr(&self) -> Result<(), Error>
643    where
644        NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif,
645    {
646        if self.net_ctl.net_type() == NetworkType::Ethernet {
647            // Nothing to manage for a wired device - just pend forever.
648            return core::future::pending().await;
649        }
650
651        let mut buf = [0u8; MAX_CREDS_SIZE];
652        let mut mgr = WirelessMgr::new(self.state.networks(), &self.net_ctl, &mut buf);
653
654        mgr.run().await
655    }
656
657    /// Perform a single, one-shot connect to the wireless network with the given
658    /// ID, immediately and regardless of the commissioning status.
659    ///
660    /// This drives the same [`WirelessMgr`] used by [`InteractionModel::run`]
661    /// (over this model's network controller and the network store owned by its
662    /// [`InteractionModelState`]), but calls [`WirelessMgr::connect_once`] rather
663    /// than the operational loop. It exists so a stack performing **non-concurrent**
664    /// (BLE-only) commissioning can replay the deferred `ConnectNetwork` once the
665    /// operational radio is up but before commissioning completes - without having
666    /// to own a `WirelessMgr` (or the networks) itself.
667    pub async fn connect_once(&self, network_id: &[u8]) -> Result<(), Error>
668    where
669        NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif,
670    {
671        let mut buf = [0u8; MAX_CREDS_SIZE];
672        let mut mgr = WirelessMgr::new(self.state.networks(), &self.net_ctl, &mut buf);
673
674        mgr.connect_once(network_id).await
675    }
676
677    async fn run_timeout_checks(&self) -> Result<(), Error> {
678        const CHECK_INTERVAL_SECS: u64 = 1;
679
680        loop {
681            Timer::after_secs(CHECK_INTERVAL_SECS).await;
682
683            self.check_timeouts(None)?;
684        }
685    }
686
687    fn check_timeouts(&self, exch_id: Option<ExchangeId>) -> Result<(), Error> {
688        let mut notify_mdns = || self.matter.transport().notify_mdns_changed();
689        let mut notify_change =
690            |endpt_id, clust_id| self.notify_cluster_changed(endpt_id, clust_id);
691
692        let removed_fabric = self.matter.with_state(|state| {
693            let expire_sess_id = exch_id.and_then(|exch_id| {
694                state
695                    .sessions
696                    .get(exch_id.session_id())
697                    .map(|sess| sess.id())
698            });
699
700            // Disarm the failsafe on timeout
701            let removed_fabric = state.failsafe.check_failsafe_timeout(
702                &mut state.fabrics,
703                &mut state.sessions,
704                &self.state.networks,
705                &self.kv,
706                expire_sess_id,
707                &mut notify_mdns,
708                &mut notify_change,
709            )?;
710
711            // Close the commissioning window on timeout
712            state
713                .pase
714                .check_comm_window_timeout(&mut notify_mdns, &mut notify_change)?;
715
716            Ok::<_, Error>(removed_fabric)
717        })?;
718
719        // Outside `with_state`, since the broadcast runs the handlers inline
720        // and they are free to access the Matter state themselves
721        if let Some(fab_idx) = removed_fabric {
722            self.notify_fabric_removed(fab_idx);
723        }
724
725        Ok(())
726    }
727
728    /// Answer a responding exchange using the `DataModel` instance wrapped by this exchange handler.
729    pub async fn handle(&self, exchange: &mut Exchange<'_>) -> Result<(), Error> {
730        let fetch_meta = |exchange: &mut Exchange| {
731            let meta = exchange.rx()?.meta();
732            if meta.proto_id != PROTO_ID_INTERACTION_MODEL {
733                Err(ErrorCode::InvalidProto)?;
734            }
735
736            Result::<_, Error>::Ok(meta)
737        };
738
739        if exchange.rx().is_err() {
740            exchange.recv_fetch().await?;
741        }
742
743        let is_groupcast = exchange.is_groupcast()?;
744
745        let mut meta = fetch_meta(exchange)?;
746
747        let timeout_instant = if !is_groupcast && meta.opcode::<OpCode>()? == OpCode::TimedRequest {
748            let timeout = self.timed(exchange).await?;
749
750            exchange.recv_fetch().await?;
751            meta = fetch_meta(exchange)?;
752
753            Some(timeout)
754        } else {
755            None
756        };
757
758        self.check_timeouts(Some(exchange.id()))?;
759
760        // TODO: Handle the cases where we receive a timeout request
761        // before read and subscribe. This is probably not allowed.
762
763        match meta.opcode::<OpCode>()? {
764            OpCode::ReadRequest if is_groupcast => {
765                error!("Received a groupcast message for opcode: ReadRequest")
766            }
767            OpCode::ReadRequest if !is_groupcast => self.read(exchange).await?,
768            OpCode::WriteRequest => self.write(exchange, timeout_instant, is_groupcast).await?,
769            OpCode::InvokeRequest => self.invoke(exchange, timeout_instant, is_groupcast).await?,
770            OpCode::SubscribeRequest if is_groupcast => {
771                error!("Received a groupcast message for opcode: SubscribeRequest")
772            }
773            OpCode::SubscribeRequest if !is_groupcast => self.subscribe(exchange).await?,
774            OpCode::ReportData if is_groupcast => {
775                error!("Received a groupcast message for opcode: ReportData")
776            }
777            OpCode::ReportData if !is_groupcast => self.handle_report_data(exchange).await?,
778            OpCode::TimedRequest if !is_groupcast => {
779                Self::send_status(exchange, IMStatusCode::InvalidAction).await?
780            }
781            _ if is_groupcast => {
782                // Silently drop unsupported opcodes for group messages
783            }
784            opcode => {
785                error!("Invalid opcode: {:?}", opcode);
786                Err(ErrorCode::InvalidOpcode)?
787            }
788        }
789
790        if !is_groupcast {
791            exchange.acknowledge().await?;
792        }
793
794        Ok(())
795    }
796
797    /// Respond to a `ReadReq` request.
798    async fn read(&self, exchange: &mut Exchange<'_>) -> Result<(), Error> {
799        let Some((mut tx, rx)) = self.buffers(exchange).await? else {
800            return Ok(());
801        };
802
803        let read_req = ReadReq::new(TLVElement::new(&rx));
804        debug!("IM: Read request: {:?}", read_req);
805
806        if let Err(err) = Self::validate_read(&read_req) {
807            error!("Invalid read request: {:?}", err);
808            return Self::send_status(exchange, err.code().into()).await;
809        }
810
811        let req = ReportDataReq::Read(&read_req);
812
813        let mut wb = WriteBuf::new(&mut tx);
814
815        // Honor the `fabricFiltered` flag on the originating Read request.
816        // When set, fabric-sensitive events emitted on other fabrics are
817        // dropped before they reach the wire (Matter Core spec).
818        let fabric_filtered = req.fabric_filtered().unwrap_or(true);
819
820        let mut resp = ReportDataResponder::new(
821            &req,
822            None,
823            HandlerInvoker::new(exchange, self),
824            EventReader::new(0, u64::MAX, fabric_filtered),
825            &self.state.events,
826        );
827
828        resp.respond(&mut wb, true, true, &self.handler, |_, _, _| true)
829            .await?;
830
831        Ok(())
832    }
833
834    /// Validate a `ReadReq` request prior to processing.
835    fn validate_read(req: &ReadReq<'_>) -> Result<(), Error> {
836        if let Some(attr_requests) = req.attr_requests()? {
837            for attr_req in attr_requests {
838                Self::validate_attr_wildcard_path(&attr_req?)?;
839            }
840        }
841
842        Ok(())
843    }
844
845    /// Per-spec validation of an `AttrPath` that may contain wildcards.
846    ///
847    /// Per Matter spec, when a path uses a wildcard cluster
848    /// but specifies a concrete attribute id, that attribute id must be a
849    /// global (system) attribute. Any other combination must be rejected with
850    /// `INVALID_ACTION`.
851    fn validate_attr_wildcard_path(path: &AttrPath) -> Result<(), Error> {
852        if path.cluster.is_none() {
853            if let Some(attr_id) = path.attr {
854                if !Attribute::is_system_attr(attr_id) {
855                    return Err(ErrorCode::InvalidAction.into());
856                }
857            }
858        }
859
860        Ok(())
861    }
862
863    /// Respond to a `WriteReq` request.
864    ///
865    /// Arguments:
866    /// - `exchange` - the exchange to respond to
867    /// - `timeout_instant` - an optional timeout instant, if the request is a timed request
868    async fn write(
869        &self,
870        exchange: &mut Exchange<'_>,
871        timeout_instant: Option<Instant>,
872        is_groupcast: bool,
873    ) -> Result<(), Error> {
874        while exchange.rx().is_ok() {
875            // Loop while there are more write request chunks to process
876
877            let Some((mut tx, rx)) = self.buffers(exchange).await? else {
878                break;
879            };
880
881            let req = WriteReq::new(TLVElement::new(&rx));
882            debug!("IM: Write request: {:?}", req);
883
884            let timed = req.timed_request()?;
885
886            if self.timed_out(exchange, timeout_instant, timed).await? {
887                break;
888            }
889
890            let mut wb = WriteBuf::new(&mut tx);
891
892            let mut resp = WriteResponder::new(&req, HandlerInvoker::new(exchange, self));
893
894            resp.respond(&mut wb, &self.handler, is_groupcast).await?;
895
896            if req.more_chunks()? {
897                // This write request is just one of the chunks, so we need to wait and process
898                // the next chunk as well
899                exchange.recv_fetch().await?;
900            }
901        }
902
903        Ok(())
904    }
905
906    /// Respond to an `InvokeReq` request.
907    ///
908    /// Arguments:
909    /// - `exchange` - the exchange to respond to
910    /// - `timeout_instant` - an optional timeout instant, if the request is a timed request
911    async fn invoke(
912        &self,
913        exchange: &mut Exchange<'_>,
914        timeout_instant: Option<Instant>,
915        is_groupcast: bool,
916    ) -> Result<(), Error> {
917        let Some((mut tx, rx)) = self.buffers(exchange).await? else {
918            return Ok(());
919        };
920
921        let req = InvReq::new(TLVElement::new(&rx));
922        debug!("IM: Invoke request: {:?}", req);
923
924        let timed = req.timed_request()?;
925
926        if self.timed_out(exchange, timeout_instant, timed).await? {
927            return Ok(());
928        }
929
930        let max_paths = exchange.matter().dev_det().max_paths_per_invoke as usize;
931
932        if let Some(reqs) = req.inv_requests()? {
933            let mut count = 0;
934            for r in &reqs {
935                let _ = r?;
936                count += 1;
937            }
938
939            if count > max_paths {
940                return Self::send_status(exchange, IMStatusCode::InvalidAction).await;
941            }
942
943            // Per Matter Core spec: when an `InvokeRequestMessage`
944            // carries multiple `CommandDataIB` entries, each MUST include a unique
945            // `CommandRef` and the request paths SHALL be unique. `count` is bounded
946            // by `max_paths_per_invoke` (typically a single-digit number), so the
947            // O(n²) pairwise check below is cheaper than allocating buffers.
948            if count > 1 {
949                for (i, req_i) in reqs.iter().enumerate() {
950                    let req_i = req_i?;
951                    if req_i.command_ref.is_none() {
952                        return Self::send_status(exchange, IMStatusCode::InvalidAction).await;
953                    }
954                    for req_j in reqs.iter().skip(i + 1) {
955                        let req_j = req_j?;
956                        if req_i.path == req_j.path || req_i.command_ref == req_j.command_ref {
957                            return Self::send_status(exchange, IMStatusCode::InvalidAction).await;
958                        }
959                    }
960                }
961            }
962        }
963
964        let mut wb = WriteBuf::new(&mut tx);
965
966        let mut resp = InvokeResponder::new(&req, HandlerInvoker::new(exchange, self));
967
968        resp.respond(&mut wb, &self.handler, is_groupcast).await
969    }
970
971    /// Respond to a `SubscribeReq` request by priming the subscription (i.e. doing an initial data report)
972    /// and if the priming is successful, sending a `SubscribeResp` response to the peer and registering
973    /// the subscription details in the `Subscriptions` instance.
974    async fn subscribe(&self, exchange: &mut Exchange<'_>) -> Result<(), Error> {
975        let Some((mut tx, rx)) = self.buffers(exchange).await? else {
976            return Ok(());
977        };
978
979        let req = SubscribeReq::new(TLVElement::new(&rx));
980        debug!("IM: Subscribe request: {:?}", req);
981
982        let accessor = exchange.accessor(&self.handler)?;
983
984        if let Err(err) = self.validate_subscribe(&req, &accessor) {
985            error!("Invalid subscribe request: {:?}", err);
986            return Self::send_status(exchange, err.code().into()).await;
987        }
988
989        let (fab_idx, peer_node_id) = exchange.with_state(|state| {
990            let sess = exchange.id().session(&mut state.sessions);
991
992            let fab_idx = NonZeroU8::new(sess.get_local_fabric_idx()).ok_or(ErrorCode::Invalid)?;
993            let peer_node_id = sess.get_peer_node_id().ok_or(ErrorCode::Invalid)?;
994
995            Ok((fab_idx, peer_node_id))
996        })?;
997
998        if !req.keep_subs()? {
999            self.state
1000                .subscriptions
1001                .remove(&self.subscriptions_buffers, |sub| {
1002                    (sub.ids().fab_idx == fab_idx && sub.ids().peer_node_id == peer_node_id)
1003                        .then_some("new subscription request")
1004                });
1005        }
1006
1007        let max_int_secs = core::cmp::max(req.max_int_ceil()?, 40); // Say we need at least 4 secs for potential latencies
1008        let min_int_secs = req.min_int_floor()?;
1009
1010        let now = Instant::now();
1011
1012        let Some(mut rctx) = self.state.subscriptions.add(
1013            now,
1014            fab_idx,
1015            peer_node_id,
1016            min_int_secs,
1017            max_int_secs,
1018            self.state.events.watermark(),
1019            rx,
1020            &self.subscriptions_buffers,
1021        ) else {
1022            return Self::send_status(exchange, IMStatusCode::ResourceExhausted).await;
1023        };
1024
1025        let primed = self.report_data(&mut rctx, &mut tx, exchange, true).await?;
1026
1027        if primed {
1028            exchange
1029                .send_with(|_, wb| {
1030                    SubscribeResp::write(wb, rctx.subscription().ids().id, max_int_secs)?;
1031                    Ok(Some(OpCode::SubscribeResponse.into()))
1032                })
1033                .await?;
1034
1035            rctx.set_keep();
1036
1037            info!("Subscription {:?} primed", rctx.subscription().ids());
1038
1039            // Commit the subscription into the table now (its `report_complete`
1040            // runs on `Drop`) and then wake the reporter so it can account for
1041            // the new subscription's deadline.
1042            drop(rctx);
1043
1044            // Persist the (now-committed) table so this subscription can be
1045            // resumed across a reboot.
1046            self.persist_subscriptions();
1047
1048            self.state.subscriptions.notification.notify();
1049        }
1050
1051        Ok(())
1052    }
1053
1054    /// Handle a server-initiated `ReportData` message — the controller /
1055    /// subscriber side of a subscription.
1056    ///
1057    /// After a controller establishes a subscription (via the IM client), the
1058    /// publisher pushes `ReportData` messages on fresh inbound exchanges for the
1059    /// life of the subscription. Those exchanges land here (the `InteractionModel`
1060    /// owns all inbound IM traffic); we parse each chunk, hand it to the
1061    /// `DataModel`'s [`ReportDataHandler`](crate::dm::ReportDataHandler) side, and
1062    /// reply `StatusResponse` — `Success` when the handler accepts, or the
1063    /// handler's chosen status (e.g. `InvalidSubscription`) when it disowns the
1064    /// report. A pure accessory's handler always disowns reports, so this is inert
1065    /// for the accessory role.
1066    ///
1067    /// Mirrors the priming-chunk loop the IM client walks during subscribe
1068    /// establishment: for each chunk we ack with `StatusResponse(Success)` and
1069    /// fetch the next until `more_chunks` clears. The terminal MRP ack is left to
1070    /// the caller's `exchange.acknowledge()` (idempotent after our `send_status`).
1071    async fn handle_report_data(&self, exchange: &mut Exchange<'_>) -> Result<(), Error> {
1072        // The peer identity is a property of the (secure) session, constant
1073        // across every chunk of this report.
1074        let (fabric_idx, peer_node_id) = exchange.with_state(|state| {
1075            let sess = exchange.id().session(&mut state.sessions);
1076
1077            let fabric_idx =
1078                NonZeroU8::new(sess.get_local_fabric_idx()).ok_or(ErrorCode::Invalid)?;
1079            let peer_node_id = sess.get_peer_node_id().ok_or(ErrorCode::Invalid)?;
1080
1081            Ok((fabric_idx, peer_node_id))
1082        })?;
1083
1084        loop {
1085            let (result, more_chunks, suppress_response) = {
1086                let rx = exchange.rx()?;
1087                let report = ReportDataResp::from_tlv(&TLVElement::new(rx.payload()))?;
1088
1089                let subscription = crate::dm::SubscriptionCtx {
1090                    fabric_idx,
1091                    peer_node_id,
1092                    subscription_id: report.subscription_id,
1093                };
1094
1095                // The report context is a `HandlerContext` (matter/crypto/kv/…)
1096                // over a shared borrow of the exchange, valid only for the
1097                // duration of this handler call — after which we resume the
1098                // mutable ack/fetch loop below.
1099                let ctx = crate::dm::ReportContextInstance::new(&*exchange, self, subscription);
1100
1101                let result = self.report_handler.handle_report(ctx, &report).await;
1102
1103                (
1104                    result,
1105                    report.more_chunks.unwrap_or(false),
1106                    report.suppress_response.unwrap_or(false),
1107                )
1108            };
1109
1110            // Spec forbids `suppress_response=true` alongside `more_chunks=true`.
1111            if more_chunks && suppress_response {
1112                return Self::send_status(exchange, IMStatusCode::InvalidAction).await;
1113            }
1114
1115            // A non-Success handler result disowns the (sub)report; report the
1116            // status and stop — no point ack-ing further chunks of a report we
1117            // rejected.
1118            //
1119            // `SuppressResponse` only suppresses the *Success* StatusResponse: a
1120            // failure status MUST always be sent (Matter Core spec), so the
1121            // publisher can tear down a subscription we no longer track (e.g. our
1122            // `InvalidSubscription`). Sending it regardless of `suppress_response`.
1123            if let Err(status) = result {
1124                return Self::send_status(exchange, status).await;
1125            }
1126
1127            if !suppress_response {
1128                Self::send_status(exchange, IMStatusCode::Success).await?;
1129            }
1130
1131            if !more_chunks {
1132                break;
1133            }
1134
1135            // Next chunk rides in on the same exchange.
1136            exchange.recv_fetch().await?;
1137        }
1138
1139        Ok(())
1140    }
1141
1142    /// Validates the subscription request
1143    fn validate_subscribe(
1144        &self,
1145        req: &SubscribeReq<'_>,
1146        accessor: &Accessor<'_>,
1147    ) -> Result<(), Error> {
1148        // As per spec, we need to validate that the subscription request
1149        // contains existing endpoints, clusters and attributes, and if not
1150        // we should (a bit surprisingly) return InvalidAction
1151
1152        self.handler.access(|node| {
1153            let mut has_attrs = false;
1154            let mut has_events = false;
1155
1156            if let Some(attr_requests) = req.attr_requests()? {
1157                has_attrs = true;
1158
1159                for attr_req in attr_requests {
1160                    let path = attr_req?;
1161
1162                    if path.is_wildcard() {
1163                        Self::validate_attr_wildcard_path(&path)?;
1164
1165                        if !node.has_accessible_attr(&path, accessor) {
1166                            return Err(ErrorCode::InvalidAction.into());
1167                        }
1168                    } else {
1169                        node.validate_attr_path(&path, false, false, accessor)
1170                            .map_err(|_| ErrorCode::InvalidAction)?;
1171                    }
1172                }
1173            }
1174
1175            if let Some(event_reqs) = req.event_requests()? {
1176                has_events = true;
1177
1178                for event_req in event_reqs {
1179                    let path = event_req?;
1180
1181                    if !path.is_wildcard() {
1182                        node.validate_event_path(&path, accessor)
1183                            .map_err(|_| ErrorCode::InvalidAction)?;
1184                    }
1185                }
1186            }
1187
1188            if !has_attrs && !has_events {
1189                // Empty subscribe requests are not allowed either
1190                return Err(ErrorCode::InvalidAction.into());
1191            }
1192
1193            Ok(())
1194        })
1195    }
1196
1197    /// Persist the current subscription table to `kv`, one record per key.
1198    ///
1199    /// A no-op when the store is a dummy (no scratch buffer). Best-effort: a
1200    /// persistence failure is logged but never propagated, so it cannot break
1201    /// reporting — persisting subscriptions is spec-optional.
1202    ///
1203    /// Compiled to a no-op unless the `persistent-subscriptions` feature is
1204    /// enabled, so the whole persistence path (TLV serialization, the key-value
1205    /// store writes) is dropped from a device that does not want it.
1206    #[cfg(feature = "persistent-subscriptions")]
1207    fn persist_subscriptions(&self) {
1208        let result = self.kv.access(|store, buf| {
1209            self.state
1210                .subscriptions
1211                .persist_all(&self.subscriptions_buffers, store, buf)
1212        });
1213
1214        if let Err(e) = result {
1215            warn!("Failed to persist subscriptions: {:?}", e);
1216        }
1217    }
1218
1219    #[cfg(not(feature = "persistent-subscriptions"))]
1220    #[inline(always)]
1221    fn persist_subscriptions(&self) {}
1222
1223    /// Re-hydrate the subscription table from `kv` and re-arm the reporter.
1224    ///
1225    /// Driven by [`InteractionModel::startup`], after the events state has been
1226    /// loaded (the replayed subscriptions are primed against the loaded events
1227    /// watermark). Each persisted subscription is replayed into the table as a
1228    /// live (already primed) subscription; the reporter then reaches the
1229    /// subscriber on demand by establishing a session when the next report is
1230    /// due. No session is opened proactively here.
1231    ///
1232    /// Compiled to a no-op (returning `Ok`) unless the `persistent-subscriptions`
1233    /// feature is enabled.
1234    #[cfg(feature = "persistent-subscriptions")]
1235    fn resume_subscriptions(&self) -> Result<(), Error> {
1236        let now = Instant::now();
1237        let watermark = self.state.events.watermark();
1238
1239        self.kv.access(|store, buf| {
1240            self.state.subscriptions.load_persist(
1241                self.buffers,
1242                &self.subscriptions_buffers,
1243                store,
1244                buf,
1245                now,
1246                watermark,
1247            )
1248        })?;
1249
1250        // Wake the reporter so it accounts for the resumed subscriptions'
1251        // liveness deadlines.
1252        self.state.subscriptions.notification.notify();
1253
1254        Ok(())
1255    }
1256
1257    /// See the `persistent-subscriptions` variant above; a no-op when that
1258    /// feature is off.
1259    #[cfg(not(feature = "persistent-subscriptions"))]
1260    #[inline(always)]
1261    fn resume_subscriptions(&self) -> Result<(), Error> {
1262        Ok(())
1263    }
1264
1265    /// Process all valid subscriptions in an endless loop, checking for changes
1266    /// and reporting them to the peers.
1267    async fn process_subscriptions(&self, matter: &Matter<'_>) -> Result<(), Error> {
1268        loop {
1269            // Sleep until the soonest subscription deadline: the end of a
1270            // `min_int` quiet period for a subscription holding back a change,
1271            // or the chosen liveness wake point. A change, event, removal, a torn
1272            // down session, or a newly accepted subscription (the accept path
1273            // notifies) all wake the loop early. With no subscription there is
1274            // no deadline, so just wait to be notified.
1275            let mut notification = pin!(self.state.subscriptions.notification.wait());
1276            let mut session_removed = pin!(matter.transport().wait_session_removed());
1277
1278            // With no subscription (or none primed) the deadline is `Instant::MAX`,
1279            // so the timer effectively never fires and the loop just waits to be
1280            // notified.
1281            let deadline = self
1282                .state
1283                .subscriptions
1284                .next_report_at(self.state.events.watermark(), &self.subscriptions_buffers);
1285            let mut timeout = pin!(Timer::at(deadline));
1286
1287            select3(&mut notification, &mut timeout, &mut session_removed).await;
1288
1289            let now = Instant::now();
1290
1291            // First remove all expired or no-longer valid subscriptions
1292
1293            let mut removed_any = false;
1294            loop {
1295                let removed = self
1296                    .state
1297                    .subscriptions
1298                    .remove(&self.subscriptions_buffers, |sub| {
1299                        if sub.is_expired(now) {
1300                            return Some("expired");
1301                        }
1302
1303                        matter.with_state(|state| {
1304                            if state.fabrics.get(sub.ids().fab_idx).is_none() {
1305                                return Some("fabric removed");
1306                            }
1307
1308                            // A subscription is NOT dropped merely because the
1309                            // session it was accepted on is gone (eviction,
1310                            // peer-side re-handshake, unreachable peer, a received
1311                            // Close, ...): reports route by `(fabric, node)` and a
1312                            // fresh session is established on demand. A session
1313                            // ending is a transport event, not a subscription
1314                            // teardown. It ends only through its own lifecycle —
1315                            // `max_int` liveness timeout (handled above as
1316                            // "expired"), or the subscriber answering a report with
1317                            // a non-success status (handled in the report loop, which
1318                            // then purges the persisted record).
1319                            None
1320                        })
1321                    });
1322
1323                removed_any |= removed;
1324
1325                if !removed {
1326                    break;
1327                }
1328            }
1329
1330            // Keep the persisted set an exact mirror of the (now-smaller) table:
1331            // any dropped subscription's record is removed so it will not be
1332            // resumed on the next reboot.
1333            if removed_any {
1334                self.persist_subscriptions();
1335            }
1336
1337            // Now report while there are subscriptions which are due for reporting
1338
1339            let event_numbers_watermark = self.state.events.watermark();
1340
1341            // Track whether any subscription left the table during reporting (the
1342            // subscriber answered a report with a non-success status, i.e. a
1343            // deliberate unsubscribe), so its persisted record can be purged.
1344            let mut dropped_any = false;
1345
1346            loop {
1347                let Some(mut rctx) = self.state.subscriptions.report(
1348                    now,
1349                    event_numbers_watermark,
1350                    &self.subscriptions_buffers,
1351                ) else {
1352                    break;
1353                };
1354
1355                let result = self.process_subscription(matter, &mut rctx).await;
1356
1357                match result {
1358                    Ok(true) => rctx.set_keep(),
1359                    // Not kept: the subscriber tore the subscription down (or we
1360                    // could not report). Dropping it from the table on `rctx`
1361                    // drop means its persisted record must be purged too.
1362                    Ok(false) => dropped_any = true,
1363                    Err(e) => {
1364                        // Reporting failed — typically because the session to the
1365                        // subscriber died (peer unreachable, MRP retransmissions
1366                        // exhausted). Drop that session so the next report to this
1367                        // peer establishes a fresh one, and keep the subscription
1368                        // so it retries rather than being torn down.
1369                        let (fab_idx, peer_node_id) = {
1370                            let ids = rctx.subscription().ids();
1371                            (ids.fab_idx, ids.peer_node_id)
1372                        };
1373
1374                        warn!(
1375                            "Error processing subscription (fab {}, node {:x}): {:?}; dropping its session, will retry",
1376                            fab_idx.get(),
1377                            peer_node_id,
1378                            e
1379                        );
1380
1381                        matter.with_state(|state| {
1382                            if let Some(id) = state
1383                                .sessions
1384                                .get_for_node(fab_idx, peer_node_id)
1385                                .map(|s| s.id)
1386                            {
1387                                state.sessions.remove(id);
1388                            }
1389                        });
1390
1391                        // Keep the subscription to retry, but do NOT advance its
1392                        // watermarks: the changes/events this report was carrying
1393                        // never reached the subscriber and must be re-sent.
1394                        rctx.set_keep_retry();
1395                    }
1396                }
1397            }
1398
1399            // A subscription that was torn down during reporting is now gone from
1400            // the table; re-persist so the on-disk set stays an exact mirror and
1401            // the torn-down subscription is not resumed on the next reboot.
1402            if dropped_any {
1403                self.persist_subscriptions();
1404            }
1405
1406            // Periodically trim changed-attr entries that have been reported by every
1407            // subscription, so the table does not accumulate stale promoted wildcards.
1408            self.state.subscriptions.purge_reported_changes();
1409        }
1410    }
1411
1412    /// Process one valid subscription, reporting the data to the peer.
1413    async fn process_subscription(
1414        &self,
1415        matter: &Matter<'_>,
1416        rctx: &mut ReportContext<'_, '_, B, NS>,
1417    ) -> Result<bool, Error> {
1418        // Route the report by the subscriber's `(fabric, node)`: reuse the best
1419        // live session to that peer, or (with the `case-responder-only` feature
1420        // off) establish a fresh one on demand. A subscription is identified by
1421        // its id, not bound to the session it was accepted on.
1422        let ids = rctx.subscription().ids();
1423        let mut exchange =
1424            Exchange::initiate(matter, self.crypto(), ids.fab_idx, ids.peer_node_id).await?;
1425
1426        if let Some(mut tx) = self.buffers.get().await {
1427            // Always safe as `IMBuffer` is defined to be `MAX_EXCHANGE_RX_BUF_SIZE`, which is bigger than `MAX_EXCHANGE_TX_BUF_SIZE`
1428            unwrap!(tx.resize_default(MAX_EXCHANGE_TX_BUF_SIZE));
1429
1430            let primed = self
1431                .report_data(rctx, &mut tx, &mut exchange, false)
1432                .await?;
1433
1434            exchange.acknowledge().await?;
1435
1436            Ok(primed)
1437        } else {
1438            error!(
1439                "No TX buffer available for processing subscription {:?}",
1440                rctx.subscription().ids(),
1441            );
1442
1443            Ok(false)
1444        }
1445    }
1446
1447    /// Process a `TimedReq` request, which is used to set a timeout for the following Write/Invoke request.
1448    async fn timed(&self, exchange: &mut Exchange<'_>) -> Result<Instant, Error> {
1449        let req = TimedReq::from_tlv(&get_root_node_struct(exchange.rx()?.payload())?)?;
1450        debug!("IM: Timed request: {:?}", req);
1451
1452        let timeout_instant = req.timeout_instant();
1453
1454        Self::send_status(exchange, IMStatusCode::Success).await?;
1455
1456        Ok(timeout_instant)
1457    }
1458
1459    /// A utility to check whether a timed request has timed out, and if so, send a timeout status response
1460    async fn timed_out(
1461        &self,
1462        exchange: &mut Exchange<'_>,
1463        timeout_instant: Option<Instant>,
1464        timed_req: bool,
1465    ) -> Result<bool, Error> {
1466        let status = {
1467            if timed_req != timeout_instant.is_some() {
1468                Some(IMStatusCode::TimedRequestMisMatch)
1469            } else if timeout_instant
1470                .map(|timeout_instant| Instant::now() > timeout_instant)
1471                .unwrap_or(false)
1472            {
1473                Some(IMStatusCode::Timeout)
1474            } else {
1475                None
1476            }
1477        };
1478
1479        if let Some(status) = status {
1480            Self::send_status(exchange, status).await?;
1481
1482            Ok(true)
1483        } else {
1484            Ok(false)
1485        }
1486    }
1487
1488    /// A utility to respond with a `ReportData` response to a subscription request, which is used to report data to the peer.
1489    ///
1490    /// Arguments:
1491    /// - `id` - the subscription ID
1492    /// - `fabric_idx` - the fabric index of the peer
1493    /// - `peer_node_id` - the node ID of the peer
1494    /// - `min_event_number` - the minimum event number to report
1495    /// - `rx` - the received data for the subscription, when the subscription was primed
1496    /// - `tx` - the TX buffer to write the response to
1497    /// - `exchange` - the exchange to respond to
1498    /// - `with_dataver` - whether to include the data version in the response
1499    #[allow(clippy::too_many_arguments)]
1500    async fn report_data(
1501        &self,
1502        rctx: &mut ReportContext<'_, '_, B, NS>,
1503        tx: &mut [u8],
1504        exchange: &mut Exchange<'_>,
1505        with_dataver: bool,
1506    ) -> Result<bool, Error>
1507    where
1508        T: DataModel,
1509    {
1510        let mut wb = WriteBuf::new(tx);
1511
1512        let sub_req = SubscribeReq::new(TLVElement::new(rctx.rx()));
1513        let req = if with_dataver {
1514            ReportDataReq::Subscribe(&sub_req)
1515        } else {
1516            ReportDataReq::SubscribeReport(&sub_req)
1517        };
1518
1519        // Honor the `fabricFiltered` flag on the originating Subscribe request.
1520        // When set, fabric-sensitive events emitted on other fabrics are
1521        // dropped before they reach the wire (Matter Core spec).
1522        let fabric_filtered = req.fabric_filtered().unwrap_or(true);
1523
1524        let mut resp = ReportDataResponder::new(
1525            &req,
1526            Some(rctx.subscription().ids().id),
1527            HandlerInvoker::new(exchange, self),
1528            EventReader::new(
1529                rctx.max_seen_event_number(),
1530                rctx.next_max_seen_event_number(),
1531                fabric_filtered,
1532            ),
1533            &self.state.events,
1534        );
1535
1536        let sub_valid = resp
1537            .respond(
1538                &mut wb,
1539                false,
1540                rctx.should_send_if_empty(),
1541                &self.handler,
1542                |e, c, a| rctx.should_report_attr(e, c, a),
1543            )
1544            .await?;
1545
1546        if !sub_valid {
1547            warn!(
1548                "Subscription {:?} removed during reporting",
1549                rctx.subscription().ids()
1550            );
1551        }
1552
1553        Ok(sub_valid)
1554    }
1555
1556    /// A utility to fetch a pair of TX/RX buffers for processing an Interaction Model request.
1557    ///
1558    /// If there are no free buffers available, this method will send a `Busy` status response to the peer.
1559    ///
1560    /// Upon returning:
1561    /// - The RX buffer will contain the payload of the received Interaction Model request
1562    /// - The TX buffer will be resized to `MAX_EXCHANGE_TX_BUF_SIZE` and will be ready to be written to
1563    ///
1564    /// Returns:
1565    /// - `Ok(Some((tx, rx)))` - if both TX and RX buffers are available
1566    /// - `Ok(None)` - if no buffers are available, and a `Busy` status response has been sent
1567    /// - `Err(Error)` - if an error occurred while fetching the buffers or sending the status response
1568    async fn buffers(
1569        &self,
1570        exchange: &mut Exchange<'_>,
1571    ) -> Result<Option<(B::Buffer<'a>, B::Buffer<'a>)>, Error> {
1572        if let Some(tx) = self.tx_buffer(exchange).await? {
1573            if let Some(rx) = self.rx_buffer(exchange).await? {
1574                return Ok(Some((tx, rx)));
1575            }
1576        }
1577
1578        Ok(None)
1579    }
1580
1581    /// A utility to fetch a RX buffer for processing an Interaction Model request.
1582    ///
1583    /// If there are no free buffers available, this method will send a `Busy` status response to the peer.
1584    ///
1585    /// Upon returning, the RX buffer will contain the payload of the received Interaction Model request.
1586    ///
1587    /// Returns:
1588    /// - `Ok(Some(rx))` - if a RX buffer is available
1589    /// - `Ok(None)` - if no RX buffer is available, and a `Busy` status response has been sent
1590    /// - `Err(Error)` - if an error occurred while fetching the buffer or sending the status response
1591    async fn rx_buffer(&self, exchange: &mut Exchange<'_>) -> Result<Option<B::Buffer<'a>>, Error> {
1592        if let Some(mut buffer) = self.buffer(exchange).await? {
1593            let rx = exchange.rx()?;
1594
1595            buffer.clear();
1596
1597            // Safe to unwrap, as `IMBuffer` is defined to be `MAX_EXCHANGE_RX_BUF_SIZE`, i.e. it cannot be overflown
1598            // by the payload of the received exchange.
1599            unwrap!(buffer.extend_from_slice(rx.payload()));
1600
1601            exchange.rx_done()?;
1602
1603            Ok(Some(buffer))
1604        } else {
1605            Ok(None)
1606        }
1607    }
1608
1609    /// A utility to fetch a TX buffer for processing an Interaction Model request.
1610    ///
1611    /// If there are no free buffers available, this method will send a `Busy` status response to the peer.
1612    ///
1613    /// Upon returning, the TX buffer will be resized to `MAX_EXCHANGE_TX_BUF_SIZE` and will be ready to be written to.
1614    ///
1615    /// Returns:
1616    /// - `Ok(Some(tx))` - if a TX buffer is available
1617    /// - `Ok(None)` - if no TX buffer is available, and a `Busy` status response has been sent
1618    /// - `Err(Error)` - if an error occurred while fetching the buffer or sending the status response
1619    async fn tx_buffer(&self, exchange: &mut Exchange<'_>) -> Result<Option<B::Buffer<'a>>, Error> {
1620        if let Some(mut buffer) = self.buffer(exchange).await? {
1621            // Always safe as `IMBuffer` is defined to be `MAX_EXCHANGE_RX_BUF_SIZE`, which is bigger than `MAX_EXCHANGE_TX_BUF_SIZE`
1622            unwrap!(buffer.resize_default(MAX_EXCHANGE_TX_BUF_SIZE));
1623
1624            Ok(Some(buffer))
1625        } else {
1626            Ok(None)
1627        }
1628    }
1629
1630    /// A utility to fetch a buffer for processing an Interaction Model request.
1631    ///
1632    /// If there are no free buffers available, this method will send a `Busy` status response to the peer.
1633    ///
1634    /// Upon returning, the buffer will be UNINITIALIZED. I.e. it is up to the user to resize it appropriately
1635    /// if it is to be used for sending a response, or to fill it with data, if it is to be used for receiving data.
1636    ///
1637    /// Returns:
1638    /// - `Ok(Some(buffer))` - if a buffer is available
1639    /// - `Ok(None)` - if no buffer is available, and a `Busy` status response has been sent
1640    /// - `Err(Error)` - if an error occurred while fetching the buffer or sending the status response
1641    async fn buffer(&self, exchange: &mut Exchange<'_>) -> Result<Option<B::Buffer<'a>>, Error> {
1642        if let Some(buffer) = self.buffers.get().await {
1643            Ok(Some(buffer))
1644        } else {
1645            Self::send_status(exchange, IMStatusCode::Busy).await?;
1646
1647            Ok(None)
1648        }
1649    }
1650
1651    /// A utility to send a status response to the peer.
1652    async fn send_status(exchange: &mut Exchange<'_>, status: IMStatusCode) -> Result<(), Error> {
1653        exchange
1654            .send_with(|_, wb| {
1655                StatusResp::write(wb, status)?;
1656
1657                Ok(Some(OpCode::StatusResponse.into()))
1658            })
1659            .await
1660    }
1661}
1662
1663impl<C, B, T, K, N, NC, R, const NS: usize, const NE: usize> ExchangeHandler
1664    for InteractionModel<'_, C, B, T, K, N, NC, R, NS, NE>
1665where
1666    C: Crypto,
1667    B: Buffers<IMBuffer>,
1668    T: DataModel,
1669    K: KvBlobStoreAccess,
1670    N: Networks,
1671    R: ReportDataHandler,
1672{
1673    async fn handle(&self, mut exchange: Exchange<'_>) -> Result<(), Error> {
1674        InteractionModel::handle(self, &mut exchange).await
1675    }
1676}
1677
1678impl<C, B, T, K, N, NC, R, const NS: usize, const NE: usize>
1679    InteractionModel<'_, C, B, T, K, N, NC, R, NS, NE>
1680where
1681    C: Crypto,
1682    B: Buffers<IMBuffer>,
1683    T: DataModel,
1684    K: KvBlobStoreAccess,
1685    N: Networks,
1686{
1687    /// The node's resource-utilisation metrics, for the `GeneralDiagnostics`
1688    /// cluster's `DeviceLoadStatus` attribute.
1689    ///
1690    /// `fab_idx` is the fabric of the reading subject, for
1691    /// `CurrentSubscriptionsForFabric`; pass `None` when no fabric is in scope.
1692    ///
1693    /// Handlers reach this through [`ImStats::device_load`] rather than calling
1694    /// it directly, but it is public so a controller can query its own load.
1695    ///
1696    /// Deliberately free of the `R: ReportDataHandler` bound the other
1697    /// `InteractionModel` methods carry: the figures come from the transport and
1698    /// the subscriptions table, so both the accessory and controller roles can
1699    /// report them.
1700    pub fn load_stats(&self, fab_idx: Option<NonZeroU8>) -> DeviceLoad {
1701        let message_counters = self.matter.transport().counters();
1702
1703        DeviceLoad {
1704            total_im_messages_sent: message_counters.im_sent,
1705            total_im_messages_received: message_counters.im_received,
1706            ..self.state.subscriptions().load_stats(fab_idx)
1707        }
1708    }
1709}
1710
1711impl<C, B, T, K, N, NC, R, const NS: usize, const NE: usize> ImStats
1712    for InteractionModel<'_, C, B, T, K, N, NC, R, NS, NE>
1713where
1714    C: Crypto,
1715    B: Buffers<IMBuffer>,
1716    T: DataModel,
1717    K: KvBlobStoreAccess,
1718    N: Networks,
1719{
1720    fn device_load(&self, fab_idx: Option<NonZeroU8>) -> DeviceLoad {
1721        self.load_stats(fab_idx)
1722    }
1723}
1724
1725impl<C, B, T, K, N, NC, R, const NS: usize, const NE: usize> HandlerContext
1726    for InteractionModel<'_, C, B, T, K, N, NC, R, NS, NE>
1727where
1728    C: Crypto,
1729    B: Buffers<IMBuffer>,
1730    T: DataModel,
1731    K: KvBlobStoreAccess,
1732    N: Networks,
1733{
1734    fn matter(&self) -> &Matter<'_> {
1735        self.matter
1736    }
1737
1738    fn crypto(&self) -> impl Crypto + '_ {
1739        &self.crypto
1740    }
1741
1742    fn kv(&self) -> impl KvBlobStoreAccess + '_ {
1743        &self.kv
1744    }
1745
1746    fn networks(&self) -> impl NetworksAccess + '_ {
1747        &self.state.networks
1748    }
1749
1750    fn metadata(&self) -> impl Metadata + '_ {
1751        &self.handler
1752    }
1753
1754    fn handler(&self) -> impl AsyncHandler + '_ {
1755        &self.handler
1756    }
1757
1758    fn buffers(&self) -> impl Buffers<IMBuffer> + '_ {
1759        self.buffers
1760    }
1761
1762    fn im_stats(&self) -> impl ImStats + '_ {
1763        self
1764    }
1765
1766    fn notify_fabric_removed(&self, fab_idx: NonZeroU8) {
1767        if let Err(e) = self
1768            .handler
1769            .lifecycle(self, LifecycleOp::FabricRemoval { fab_idx })
1770        {
1771            warn!(
1772                "Failed to broadcast the removal of fabric {}: {:?}",
1773                fab_idx, e
1774            );
1775        }
1776
1777        #[cfg(feature = "groups")]
1778        self.matter.transport().notify_groups_changed();
1779    }
1780}
1781
1782impl<C, B, T, K, N, NC, R, const NS: usize, const NE: usize> AttrChangeNotifier
1783    for InteractionModel<'_, C, B, T, K, N, NC, R, NS, NE>
1784where
1785    C: Crypto,
1786    B: Buffers<IMBuffer>,
1787    T: DataModel,
1788    K: KvBlobStoreAccess,
1789    N: Networks,
1790{
1791    fn notify_attr_changed(&self, endpoint_id: EndptId, cluster_id: ClusterId, attr_id: AttrId) {
1792        self.handler.bump_dataver(MatchContextInstance::new(
1793            Some(endpoint_id),
1794            Some(cluster_id),
1795        ));
1796        self.state
1797            .subscriptions
1798            .notify_attr_changed(endpoint_id, cluster_id, attr_id);
1799    }
1800
1801    fn notify_cluster_changed(&self, endpoint_id: EndptId, cluster_id: ClusterId) {
1802        self.handler.bump_dataver(MatchContextInstance::new(
1803            Some(endpoint_id),
1804            Some(cluster_id),
1805        ));
1806        self.state
1807            .subscriptions
1808            .notify_cluster_changed(endpoint_id, cluster_id);
1809    }
1810
1811    fn notify_endpoint_changed(&self, endpoint_id: EndptId) {
1812        self.handler
1813            .bump_dataver(MatchContextInstance::new(Some(endpoint_id), None));
1814        self.state
1815            .subscriptions
1816            .notify_endpoint_changed(endpoint_id)
1817    }
1818
1819    fn notify_all_changed(&self) {
1820        self.handler
1821            .bump_dataver(MatchContextInstance::new(None, None));
1822        self.state.subscriptions.notify_all_changed()
1823    }
1824}
1825
1826impl<C, B, T, K, N, NC, R, const NS: usize, const NE: usize> EventEmitter
1827    for InteractionModel<'_, C, B, T, K, N, NC, R, NS, NE>
1828where
1829    C: Crypto,
1830    B: Buffers<IMBuffer>,
1831    T: DataModel,
1832    K: KvBlobStoreAccess,
1833    N: Networks,
1834{
1835    fn emit_event<F>(
1836        &self,
1837        endpoint_id: EndptId,
1838        cluster_id: ClusterId,
1839        event_id: EventId,
1840        priority: EventPriority,
1841        f: F,
1842    ) -> Result<u64, Error>
1843    where
1844        F: FnOnce(EventTLVWrite<'_>) -> Result<(), Error>,
1845    {
1846        let event_number =
1847            self.state
1848                .events
1849                .push(endpoint_id, cluster_id, event_id, priority, &self.kv, f)?;
1850
1851        self.state
1852            .subscriptions
1853            .notify_event_emitted(endpoint_id, cluster_id, event_id);
1854
1855        Ok(event_number)
1856    }
1857}
1858
1859pub enum RespondOutcome {
1860    Accepted,
1861    Rejected,
1862    Empty,
1863}
1864
1865/// This type responds with a `ReportData` response to all of:
1866/// - A `ReadReq`
1867/// - A `SubscribeReq`
1868/// - A `SubscribeReportReq` (i.e. once a valid recorded subscription is detected as in a need to be reported on)
1869///
1870/// The responder handles chunking as needed. I.e. if reported data is too large to fit into a single
1871/// Matter message, it will send the data in multiple chunks (i.e. with multiple Matter messages), waiting for
1872/// a `Success` response from the peer after each chunk, and then continuing to send the next chunk until all data is sent.
1873struct ReportDataResponder<'a, 'b, 'c, const NE: usize, C> {
1874    req: &'a ReportDataReq<'a>,
1875    subscription_id: Option<u32>,
1876    invoker: HandlerInvoker<'b, 'c, C>,
1877    event_reader: EventReader,
1878    events: &'a Events<NE>,
1879}
1880
1881impl<'a, 'b, 'c, const NE: usize, C> ReportDataResponder<'a, 'b, 'c, NE, C>
1882where
1883    C: HandlerContext,
1884{
1885    // This is the amount of space we reserve for the structure/array closing TLVs
1886    // to be attached towards the end of long reads
1887    const LONG_READS_TLV_RESERVE_SIZE: usize = 24;
1888
1889    /// Create a new `ReportDataResponder`.
1890    const fn new(
1891        req: &'a ReportDataReq<'a>,
1892        subscription_id: Option<u32>,
1893        invoker: HandlerInvoker<'b, 'c, C>,
1894        event_reader: EventReader,
1895        events: &'a Events<NE>,
1896    ) -> Self {
1897        Self {
1898            req,
1899            subscription_id,
1900            invoker,
1901            event_reader,
1902            events,
1903        }
1904    }
1905
1906    /// Respond to the request with a `ReportData` response, possibly with more than one
1907    /// chunk if the data is too large to fit into a single Matter message.
1908    ///
1909    /// Arguments:
1910    /// - `wb` - the buffer to use while sending the response
1911    /// - `suppress_last_resp` - whether to suppress the response from the peer. When multiple Matter messages are
1912    ///   being sent due to chunking, this is valid for the last chunk only, as the others - by necessity need to have a
1913    ///   status response by the other peer
1914    async fn respond<M, F>(
1915        &mut self,
1916        wb: &mut WriteBuf<'_>,
1917        suppress_last_resp: bool,
1918        send_if_empty: bool,
1919        metadata: M,
1920        mut filter: F,
1921    ) -> Result<bool, Error>
1922    where
1923        M: Metadata,
1924        F: FnMut(EndptId, ClusterId, u32) -> bool,
1925    {
1926        let mut empty = true;
1927
1928        self.start_reply(wb)?;
1929
1930        if !self
1931            .report_attributes(wb, &mut empty, &metadata, &mut filter)
1932            .await?
1933        {
1934            return Ok(false);
1935        }
1936
1937        if !self.report_events(wb, &mut empty, &metadata).await? {
1938            return Ok(false);
1939        }
1940
1941        if send_if_empty || !empty {
1942            self.send(ReportDataChunkState::Done, suppress_last_resp, wb)
1943                .await
1944        } else {
1945            debug!("No data to report, skipping sending ReportData response");
1946
1947            Ok(true)
1948        }
1949    }
1950
1951    async fn report_attributes<M, F>(
1952        &mut self,
1953        wb: &mut WriteBuf<'_>,
1954        empty: &mut bool,
1955        metadata: M,
1956        mut filter: F,
1957    ) -> Result<bool, Error>
1958    where
1959        M: Metadata,
1960        F: FnMut(EndptId, ClusterId, u32) -> bool,
1961    {
1962        let accessor = self.invoker.exchange().accessor(&metadata)?;
1963
1964        if self.req.attr_requests()?.is_some() {
1965            wb.start_array(&TLVTag::Context(ReportDataRespTag::AttributeReports as u8))?;
1966
1967            for item in expand_read(&metadata, self.req, &accessor, &mut filter)? {
1968                let item = item?;
1969
1970                *empty = false;
1971
1972                loop {
1973                    let result = self.invoker.process_read(&item, &mut *wb).await;
1974
1975                    match result {
1976                        Ok(()) => break,
1977                        Err(err) if err.code() == ErrorCode::NoSpace => {
1978                            let array_attr = item.as_ref().ok().filter(|attr| {
1979                                attr.list_index.is_none()
1980                                    // The whole attribute is requested
1981                                    // Check if it is an array, and if so, send it as individual items instead
1982                                    && attr.array
1983                            });
1984
1985                            if let Some(array_attr) = array_attr {
1986                                if self.send_array_items(array_attr, wb).await? {
1987                                    break;
1988                                } else {
1989                                    return Ok(false);
1990                                }
1991                            } else {
1992                                debug!("<<< No TX space, chunking >>>");
1993                                if !self
1994                                    .send(ReportDataChunkState::ChunkingAttributes, false, wb)
1995                                    .await?
1996                                {
1997                                    return Ok(false);
1998                                }
1999                            }
2000                        }
2001                        Err(err) => Err(err)?,
2002                    }
2003                }
2004            }
2005
2006            wb.end_container()?;
2007        }
2008
2009        Ok(true)
2010    }
2011
2012    async fn report_events<M>(
2013        &mut self,
2014        wb: &mut WriteBuf<'_>,
2015        empty: &mut bool,
2016        metadata: M,
2017    ) -> Result<bool, Error>
2018    where
2019        M: Metadata,
2020    {
2021        let accessor = self.invoker.exchange().accessor(&metadata)?;
2022
2023        if let Some(event_reqs) = self.req.event_requests()? {
2024            wb.start_array(&TLVTag::Context(ReportDataRespTag::EventReports as _))?;
2025
2026            // Validate concrete event paths against node metadata
2027            // and emit EventStatusIB for non-wildcard paths that don't match
2028            for event_req in event_reqs.iter() {
2029                let path = event_req?;
2030
2031                if !path.is_wildcard() {
2032                    if let Err(status) =
2033                        metadata.access(|node| node.validate_event_path(&path, &accessor))
2034                    {
2035                        if matches!(status, IMStatusCode::UnsupportedEvent) {
2036                            // Event does not exist on this endpoint
2037                            // TODO: Look at TestEventsById.yaml
2038                            // Seems we should not error out in that case?
2039                            continue;
2040                        }
2041
2042                        *empty = false;
2043
2044                        let resp = EventResp::Status(EventStatus::new(path, status, None));
2045
2046                        let mut result = resp.to_tlv(&TLVTag::Anonymous, &mut *wb);
2047
2048                        if let Err(e) = &result {
2049                            if e.code() == ErrorCode::NoSpace {
2050                                debug!("<<< No TX space, chunking >>>");
2051                                if !self
2052                                    .send(ReportDataChunkState::ChunkingEvents, false, &mut *wb)
2053                                    .await?
2054                                {
2055                                    return Ok(false);
2056                                }
2057
2058                                result = resp.to_tlv(&TLVTag::Anonymous, &mut *wb);
2059                            }
2060                        }
2061
2062                        result?;
2063                    }
2064                }
2065            }
2066
2067            let event_filters = self.req.event_filters()?;
2068
2069            loop {
2070                let finished = self.events.fetch(|events| {
2071                    metadata.access(|node| {
2072                        for event in events {
2073                            let result = self.event_reader.process_read(
2074                                event,
2075                                &event_reqs,
2076                                &event_filters,
2077                                node,
2078                                &accessor,
2079                                &mut *wb,
2080                            );
2081
2082                            if let Err(e) = &result {
2083                                if e.code() == ErrorCode::NoSpace {
2084                                    return Ok::<_, Error>(false);
2085                                }
2086                            }
2087
2088                            if result? {
2089                                *empty = false;
2090                            }
2091                        }
2092
2093                        Ok(true)
2094                    })
2095                })?;
2096
2097                if finished {
2098                    break;
2099                }
2100
2101                debug!("<<< No TX space, chunking >>>");
2102                if !self
2103                    .send(ReportDataChunkState::ChunkingEvents, false, wb)
2104                    .await?
2105                {
2106                    return Ok(false);
2107                }
2108            }
2109
2110            wb.end_container()?;
2111        }
2112
2113        Ok(true)
2114    }
2115
2116    /// Send the items of an array attribute one by one, until the end of the array is reached.
2117    ///
2118    /// The data is potentially sent in multiple chunks if it cannot fit into a single Matter message.
2119    ///
2120    /// Arguments:
2121    /// - `attr` - the array attribute to send the items of
2122    /// - `wb` - the buffer to use while sending the items
2123    async fn send_array_items(
2124        &mut self,
2125        attr: &AttrDetails,
2126        wb: &mut WriteBuf<'_>,
2127    ) -> Result<bool, Error> {
2128        let mut attr = attr.clone();
2129
2130        // First generate an empty array
2131        let mut list_index = None;
2132        attr.list_chunked = true;
2133        attr.list_index = Some(Nullable::new(list_index));
2134
2135        loop {
2136            let pos = wb.get_tail();
2137
2138            let result = self.invoker.read(&attr, &mut *wb).await;
2139
2140            if result.is_err() {
2141                // If we got an error, we rewind to the position before the read
2142                // and handle it accordingly
2143                wb.rewind_to(pos);
2144            }
2145
2146            match result {
2147                Ok(()) => {
2148                    // The empty array payload was sent
2149                    // Now iterate over the array and send each item one by one as separate payload
2150
2151                    let new_list_index = if let Some(list_index) = list_index {
2152                        list_index + 1
2153                    } else {
2154                        0
2155                    };
2156
2157                    list_index = Some(new_list_index);
2158                    attr.list_index = Some(Nullable::some(new_list_index));
2159                }
2160                Err(err) if err.code() == ErrorCode::NoSpace => {
2161                    debug!("<<< No TX space, chunking >>>");
2162                    if !self
2163                        .send(ReportDataChunkState::ChunkingAttributes, false, wb)
2164                        .await?
2165                    {
2166                        return Ok(false);
2167                    }
2168                }
2169                Err(err) if err.code() == ErrorCode::ConstraintError => break, // Got to the end of the array
2170                Err(err) => Err(err)?,
2171            }
2172        }
2173
2174        Ok(true)
2175    }
2176
2177    /// Send the reply to the peer, potentially opening another reply.
2178    ///
2179    /// Arguments:
2180    /// - `state`: tracks chunking state - are we just sending a chunk packet or are we done and wrapping up?
2181    /// - `suppress_last_resp`: whether to suppress the response from the peer, this is ignored if state is != Done
2182    /// - `wb`: the buffer containing the reply. Once the reply is sent, the buffer is re-initialized for a new reply if `more_chunks` is `true`
2183    async fn send(
2184        &mut self,
2185        state: ReportDataChunkState,
2186        suppress_last_resp: bool,
2187        wb: &mut WriteBuf<'_>,
2188    ) -> Result<bool, Error> {
2189        self.end_reply(state, suppress_last_resp, wb)?;
2190
2191        self.invoker
2192            .exchange()
2193            .send(OpCode::ReportData, wb.as_slice())
2194            .await?;
2195
2196        let cont = match state {
2197            ReportDataChunkState::ChunkingAttributes => {
2198                let cont = self.recv_status_success().await?;
2199                self.start_reply(wb)?;
2200                wb.start_array(&TLVTag::Context(ReportDataRespTag::AttributeReports as u8))?;
2201                cont
2202            }
2203            ReportDataChunkState::ChunkingEvents => {
2204                let cont = self.recv_status_success().await?;
2205                self.start_reply(wb)?;
2206                wb.start_array(&TLVTag::Context(ReportDataRespTag::EventReports as u8))?;
2207                cont
2208            }
2209            ReportDataChunkState::Done => {
2210                if !suppress_last_resp {
2211                    self.recv_status_success().await?
2212                } else {
2213                    false
2214                }
2215            }
2216        };
2217
2218        Ok(cont)
2219    }
2220
2221    /// Receive a status response from the peer
2222    ///
2223    /// If the response is not a status response, the method will fail with an `Invalid` error.
2224    ///
2225    /// Return `Ok(true)` if the response is a success response, `Ok(false)` if the response is not a success response.
2226    async fn recv_status_success(&mut self) -> Result<bool, Error> {
2227        let rx = self.invoker.exchange().recv().await?;
2228        let opcode = rx.meta().proto_opcode;
2229
2230        if opcode != OpCode::StatusResponse as u8 {
2231            warn!(
2232                "Got opcode {:02x}, while expecting status code {:02x}",
2233                opcode,
2234                OpCode::StatusResponse as u8
2235            );
2236
2237            return Err(ErrorCode::Invalid.into());
2238        }
2239
2240        let resp = StatusResp::from_tlv(&get_root_node_struct(rx.payload())?)?;
2241
2242        if resp.status == IMStatusCode::Success {
2243            Ok(true)
2244        } else {
2245            warn!(
2246                "Got status response {:?}, aborting interaction",
2247                resp.status
2248            );
2249
2250            drop(rx);
2251
2252            self.invoker.exchange().acknowledge().await?;
2253
2254            Ok(false)
2255        }
2256    }
2257
2258    /// Start a reply by initializing the `WriteBuf` and writing the initial TLVs.
2259    fn start_reply(&self, wb: &mut WriteBuf<'_>) -> Result<(), Error> {
2260        wb.reset();
2261        wb.shrink(Self::LONG_READS_TLV_RESERVE_SIZE)?;
2262
2263        wb.start_struct(&TLVTag::Anonymous)?;
2264
2265        if let Some(subscription_id) = self.subscription_id {
2266            assert!(matches!(
2267                self.req,
2268                ReportDataReq::Subscribe(_) | ReportDataReq::SubscribeReport(_)
2269            ));
2270            wb.u32(
2271                &TLVTag::Context(ReportDataRespTag::SubscriptionId as u8),
2272                subscription_id,
2273            )?;
2274        } else {
2275            assert!(matches!(self.req, ReportDataReq::Read(_)));
2276        }
2277
2278        Ok(())
2279    }
2280
2281    /// End a reply by writing the closing TLVs and potentially indicating that there are more chunks to send.
2282    fn end_reply(
2283        &self,
2284        state: ReportDataChunkState,
2285        suppress_resp: bool,
2286        wb: &mut WriteBuf<'_>,
2287    ) -> Result<(), Error> {
2288        wb.expand(Self::LONG_READS_TLV_RESERVE_SIZE)?;
2289
2290        match state {
2291            ReportDataChunkState::ChunkingAttributes | ReportDataChunkState::ChunkingEvents => {
2292                wb.end_container()?;
2293                wb.bool(
2294                    &TLVTag::Context(ReportDataRespTag::MoreChunkedMsgs as u8),
2295                    true,
2296                )?;
2297            }
2298            ReportDataChunkState::Done => {
2299                if suppress_resp {
2300                    wb.bool(
2301                        &TLVTag::Context(ReportDataRespTag::SupressResponse as u8),
2302                        true,
2303                    )?;
2304                }
2305            }
2306        };
2307
2308        // InteractionModelRevision is mandatory in all IM messages from
2309        // Matter 1.0 onward (TLV tag 0xFF). matter.js validates this
2310        // strictly and refuses to commission devices that omit it; the
2311        // reference chip-tool happens to tolerate the absence.
2312        wb.u8(
2313            &TLVTag::Context(crate::im::encoding::IM_REVISION_TAG),
2314            IM_REVISION,
2315        )?;
2316
2317        wb.end_container()?;
2318
2319        Ok(())
2320    }
2321}
2322
2323/// Used to avoid duplicating the chunking logic for events and attributes; they both
2324/// share the same write path when the current packet fills up, and use this to determine
2325/// which field they should be setting up an array in for more output in the next packet
2326#[derive(Clone, Copy)]
2327enum ReportDataChunkState {
2328    ChunkingAttributes,
2329    ChunkingEvents,
2330    Done,
2331}
2332
2333/// This type responds to a `WriteReq` by invoking the
2334/// corresponding handlers for each write attribute in the request.
2335///
2336/// The responser assumes that all response data can fit in a single Matter message,
2337/// which is a fair assumption and as per the Matter spec, in that the response of a
2338/// write request is always shorter than the write request itself, so given that the
2339/// write request fits in a single Matter message, the write reponse should as well.
2340///
2341/// With that said, the write request might itself be just one out of many chunks that
2342/// the other peers is sending, but processing all of those chunks is not done here,
2343/// but is rather - a responsibility of the caller who should call in a loop `WriteResponder::respond`
2344/// for all the chunks of the write request, until the `WriteReq::more_chunks()` returns `false`.
2345struct WriteResponder<'a, 'b, 'c, C> {
2346    req: &'a WriteReq<'a>,
2347    invoker: HandlerInvoker<'b, 'c, C>,
2348}
2349
2350impl<'a, 'b, 'c, C> WriteResponder<'a, 'b, 'c, C>
2351where
2352    C: HandlerContext,
2353{
2354    /// Create a new `WriteResponder`.
2355    const fn new(req: &'a WriteReq<'a>, invoker: HandlerInvoker<'b, 'c, C>) -> Self {
2356        Self { req, invoker }
2357    }
2358
2359    /// Respond to the write request by processing each write attribute in the request
2360    /// and sending a response back.
2361    async fn respond<M>(
2362        &mut self,
2363        wb: &mut WriteBuf<'_>,
2364        metadata: M,
2365        suppress_resp: bool,
2366    ) -> Result<(), Error>
2367    where
2368        M: Metadata,
2369    {
2370        let accessor = self.invoker.exchange().accessor(&metadata)?;
2371
2372        wb.reset();
2373
2374        wb.start_struct(&TLVTag::Anonymous)?;
2375        wb.start_array(&TLVTag::Context(WriteRespTag::WriteResponses as u8))?;
2376
2377        for item in expand_write(metadata, self.req, &accessor)? {
2378            self.invoker.process_write(&item?, &mut *wb).await?;
2379        }
2380
2381        if suppress_resp {
2382            return Ok(());
2383        }
2384
2385        wb.end_container()?;
2386        // Mandatory `interactionModelRevision` (tag 0xFF); see note in
2387        // the ReportData emitter above.
2388        wb.u8(
2389            &TLVTag::Context(crate::im::encoding::IM_REVISION_TAG),
2390            IM_REVISION,
2391        )?;
2392        wb.end_container()?;
2393
2394        self.invoker
2395            .exchange()
2396            .send(OpCode::WriteResponse, wb.as_slice())
2397            .await
2398    }
2399}
2400
2401/// This type responds to an `InvRequest` by invoking the
2402/// corresponding handlers for each command in the invoke request.
2403///
2404/// NOTE: In future, this responder should support chunking in that
2405/// if the reply to all the commands in the invoke request is too large to fit
2406/// into a single Matter message, it should send the response in multiple chunks.
2407///
2408/// The simplest strategy for chunking would be to simply - and unconditionally - send each individual
2409/// command response in a separate Matter message, i.e. if the invoke request contains 3 commands,
2410/// the responder will send 3 Matter messages, each containing a single command response.
2411struct InvokeResponder<'a, 'b, 'c, C> {
2412    req: &'a InvReq<'a>,
2413    invoker: HandlerInvoker<'b, 'c, C>,
2414}
2415
2416impl<'a, 'b, 'c, C> InvokeResponder<'a, 'b, 'c, C>
2417where
2418    C: HandlerContext,
2419{
2420    /// Create a new `InvokeResponder`.
2421    const fn new(req: &'a InvReq<'a>, invoker: HandlerInvoker<'b, 'c, C>) -> Self {
2422        Self { req, invoker }
2423    }
2424
2425    /// Respond to the invoke request by processing each command in the request
2426    /// and sending one or more reponses back.
2427    async fn respond<M>(
2428        &mut self,
2429        wb: &mut WriteBuf<'_>,
2430        metadata: M,
2431        suppress_resp: bool,
2432    ) -> Result<(), Error>
2433    where
2434        M: Metadata,
2435    {
2436        wb.reset();
2437
2438        wb.start_struct(&TLVTag::Anonymous)?;
2439
2440        // Suppress Response -> TODO: Need to revisit this for cases where we send a command back
2441        wb.bool(
2442            &TLVTag::Context(InvRespTag::SupressResponse as u8),
2443            suppress_resp,
2444        )?;
2445
2446        let has_requests = self.req.inv_requests()?.is_some();
2447
2448        if has_requests {
2449            wb.start_array(&TLVTag::Context(InvRespTag::InvokeResponses as u8))?;
2450        }
2451
2452        let accessor = self.invoker.exchange().accessor(&metadata)?;
2453
2454        // When the Groupcast testing mode is armed for listener testing by
2455        // this group session's fabric, record an observation per processed
2456        // path (turned into `GroupcastTesting` events by the Groupcast
2457        // handler): a success per invoked concrete path, or - when nothing
2458        // was invocable at all (typically: access denied on every group
2459        // endpoint) - a single failed-auth observation.
2460        // (For invokes, `suppress_resp` is set exactly for group-addressed
2461        // requests - see the `respond` callers.)
2462        #[cfg(feature = "groups")]
2463        let group_testing = if suppress_resp {
2464            let exchange = self.invoker.exchange();
2465
2466            exchange
2467                .matter()
2468                .groupcast_testing()
2469                .armed(crate::dm::clusters::groupcast::GroupcastTestingEnum::EnableListenerTesting)
2470                .and_then(|mode| {
2471                    exchange
2472                        .with_state(|state| {
2473                            let sess = exchange.id().session(&mut state.sessions);
2474
2475                            let crate::transport::session::SessionMode::Group {
2476                                fab_idx,
2477                                group_id,
2478                            } = sess.get_session_mode()
2479                            else {
2480                                return Ok(None);
2481                            };
2482
2483                            if *fab_idx != mode.fab_idx {
2484                                return Ok(None);
2485                            }
2486
2487                            let src_ip = crate::dm::clusters::groupcast::TestingObservation::addr_ip(&sess.get_peer_addr());
2488                            let group_id = *group_id;
2489                            let fab_idx = *fab_idx;
2490                            let dst_ip = crate::dm::clusters::groupcast::TestingObservation::group_dst_ip(
2491                                &state.fabrics,
2492                                fab_idx,
2493                                group_id,
2494                            );
2495
2496                            Ok(Some((group_id, src_ip, dst_ip)))
2497                        })
2498                        .unwrap_or(None)
2499                })
2500        } else {
2501            None
2502        };
2503
2504        #[cfg(feature = "groups")]
2505        let mut any_invoked = false;
2506
2507        for item in expand_invoke(metadata, self.req, &accessor)? {
2508            let item = item?;
2509            #[cfg_attr(not(feature = "groups"), allow(unused_variables))]
2510            let invoked = self.invoker.process_invoke(&item, &mut *wb).await?;
2511
2512            #[cfg(feature = "groups")]
2513            if invoked {
2514                any_invoked = true;
2515
2516                if let (Some((group_id, src_ip, dst_ip)), Ok((cmd, _))) = (&group_testing, &item) {
2517                    self.invoker
2518                        .exchange()
2519                        .matter()
2520                        .groupcast_testing()
2521                        .observe(crate::dm::clusters::groupcast::TestingObservation {
2522                            src_ip: *src_ip,
2523                            dst_ip: Some(*dst_ip),
2524                            group_id: Some(*group_id),
2525                            endpoint_id: Some(cmd.endpoint_id),
2526                            cluster_id: Some(cmd.cluster_id),
2527                            element_id: Some(cmd.cmd_id),
2528                            access_allowed: Some(true),
2529                            result:
2530                                crate::dm::clusters::groupcast::GroupcastTestResultEnum::Success,
2531                        });
2532                }
2533            }
2534        }
2535
2536        #[cfg(feature = "groups")]
2537        if let Some((group_id, src_ip, dst_ip)) = group_testing {
2538            if !any_invoked {
2539                // Nothing was invokable for this group message - report it
2540                // as access-denied (the dominant cause: no ACL grant covers
2541                // the group's endpoints)
2542                self.invoker
2543                    .exchange()
2544                    .matter()
2545                    .groupcast_testing()
2546                    .observe(crate::dm::clusters::groupcast::TestingObservation {
2547                        src_ip,
2548                        dst_ip: Some(dst_ip),
2549                        group_id: Some(group_id),
2550                        endpoint_id: None,
2551                        cluster_id: None,
2552                        element_id: None,
2553                        access_allowed: Some(false),
2554                        result: crate::dm::clusters::groupcast::GroupcastTestResultEnum::FailedAuth,
2555                    });
2556            }
2557        }
2558
2559        if suppress_resp {
2560            return Ok(());
2561        }
2562
2563        if has_requests {
2564            wb.end_container()?;
2565        }
2566
2567        // Mandatory `interactionModelRevision` (tag 0xFF) at the end of
2568        // every IM message — see the matching note in the ReportData
2569        // emitter above.
2570        wb.u8(
2571            &TLVTag::Context(crate::im::encoding::IM_REVISION_TAG),
2572            IM_REVISION,
2573        )?;
2574        wb.end_container()?;
2575
2576        self.invoker
2577            .exchange()
2578            .send(OpCode::InvokeResponse, wb.as_slice())
2579            .await?;
2580
2581        Ok(())
2582    }
2583}