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}