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::num::NonZeroU8;
28use core::pin::pin;
29
30use embassy_futures::select::{select3, select4};
31use embassy_time::{Instant, Timer};
32
33use crate::acl::Accessor;
34use crate::crypto::Crypto;
35use crate::dm::clusters::net_comm::{
36    NetCtl, NetworkType, Networks, NetworksAccess, SharedNetworks,
37};
38use crate::dm::clusters::wifi_diag::WirelessDiag;
39use crate::dm::networks::eth::EthNetwork;
40use crate::dm::networks::wireless::{NoopWirelessNetCtl, WirelessMgr, MAX_CREDS_SIZE};
41use crate::dm::networks::NetChangeNotif;
42use crate::dm::{
43    AsyncHandler, AttrChangeNotifier, AttrDetails, Attribute, DataModel, EventEmitter,
44    HandlerContext, MatchContextInstance, Metadata,
45};
46use crate::error::{Error, ErrorCode};
47use crate::im::events::{EventReader, EventTLVWrite, Events, DEFAULT_MAX_EVENTS_BUF_SIZE};
48use crate::im::invoker::HandlerInvoker;
49use crate::im::subscriptions::{
50    ReportContext, Subscriptions, SubscriptionsBuffers, DEFAULT_MAX_SUBSCRIPTIONS,
51};
52use crate::persist::{KvBlobStoreAccess, NETWORKS_KEY};
53use crate::respond::ExchangeHandler;
54use crate::tlv::{get_root_node_struct, FromTLV, Nullable, TLVElement, TLVTag, TLVWrite, ToTLV};
55use crate::transport::exchange::{Exchange, ExchangeId, MAX_EXCHANGE_TX_BUF_SIZE};
56use crate::utils::init::{init, Init};
57use crate::utils::select::Coalesce;
58use crate::utils::storage::pooled::Buffers;
59use crate::utils::storage::WriteBuf;
60use crate::Matter;
61
62pub use encoding::*;
63pub use expand::{expand_invoke, expand_read, expand_write};
64
65pub mod busy;
66pub mod client;
67pub mod encoding;
68pub mod events;
69pub mod expand;
70pub mod invoker;
71pub mod subscriptions;
72
73/// An `ExchangeHandler` implementation capable of handling responder exchanges for the Interaction Model protocol.
74/// The mutable, owned-together state a [`InteractionModel`] operates on: the
75/// subscriptions table, the events queue and the network store.
76///
77/// Allocating these as a single value (rather than three separate locals wired
78/// up by hand at every call site) is the whole point: construct one
79/// `InteractionModelState`, then hand a reference to it to [`InteractionModel::new`]. Each of
80/// the three pieces keeps its own lock internally (the proven multi-lock model);
81/// folding them behind a single mutex is a possible later refinement.
82///
83/// `N` is the (raw) [`Networks`] implementation — e.g.
84/// [`EthNetwork`](crate::dm::networks::eth::EthNetwork) or
85/// [`WirelessNetworks`](crate::dm::networks::wireless::WirelessNetworks); it is
86/// wrapped internally in a [`SharedNetworks`] so the data model and (later) the
87/// wireless manager can share it. `NS`/`NE` bound the subscription table and the
88/// event-buffer size and default to [`DEFAULT_MAX_SUBSCRIPTIONS`] /
89/// [`DEFAULT_MAX_EVENTS_BUF_SIZE`].
90pub struct InteractionModelState<
91    N,
92    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
93    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
94> {
95    subscriptions: Subscriptions<NS>,
96    events: Events<NE>,
97    networks: SharedNetworks<N>,
98}
99
100impl<N, const NS: usize, const NE: usize> InteractionModelState<N, NS, NE> {
101    /// Create a new state instance backed by the given (raw) [`Networks`] store.
102    pub const fn new(networks: N) -> Self {
103        Self {
104            subscriptions: Subscriptions::new(),
105            events: Events::new(),
106            networks: SharedNetworks::new(networks),
107        }
108    }
109
110    /// Return an in-place initializer for the state (for large `NE`, to
111    /// avoid a big temporary on the stack).
112    pub fn init(networks: impl Init<N>) -> impl Init<Self> {
113        init!(Self {
114            subscriptions <- Subscriptions::init(),
115            events <- Events::init(),
116            networks <- SharedNetworks::init(networks),
117        })
118    }
119
120    /// Reset this state's persisted contents to factory defaults - the
121    /// events-queue epoch and the network store - removing both from `kv` using
122    /// the scratch buffer provided by `kv`. Call once during a factory reset,
123    /// with exclusive (`&mut`) access (i.e. before the state is shared with a
124    /// [`InteractionModel`]).
125    pub async fn reset_persist<K>(&mut self, kv: K) -> Result<(), Error>
126    where
127        K: KvBlobStoreAccess,
128        N: Networks,
129    {
130        // We hold `&mut self`, so borrow the pieces directly - no locking.
131        let Self {
132            events, networks, ..
133        } = self;
134
135        let events = events.inner_mut();
136        let networks = networks.get_mut().get_mut();
137
138        kv.access(|store, buf| {
139            // The event-number epoch.
140            events.reset_persist(&mut *store, buf)?;
141
142            // The network store.
143            networks.reset()?;
144            store.remove(NETWORKS_KEY, buf)?;
145
146            Ok(())
147        })
148    }
149
150    /// Re-hydrate this state's persisted contents - the events-queue epoch (so
151    /// event numbers are not reused across reboots) and the network store - using
152    /// the scratch buffer provided by `kv`. Call once at startup, before the
153    /// state is shared with a [`InteractionModel`].
154    pub async fn load_persist<K>(&mut self, kv: K) -> Result<(), Error>
155    where
156        K: KvBlobStoreAccess,
157        N: Networks,
158    {
159        // We hold `&mut self`, so borrow the pieces directly - no locking
160        // (the inner mutexes are only needed for shared, runtime access).
161        let Self {
162            events, networks, ..
163        } = self;
164
165        let events = events.inner_mut();
166        let networks = networks.get_mut().get_mut();
167
168        // The KV ops are sync, so do them all inside a single `access` closure.
169        kv.access(|store, buf| {
170            // The event-number epoch.
171            events.load_persist(&mut *store, buf)?;
172
173            // The network store.
174            networks.reset()?;
175            if let Some(data) = store.load(NETWORKS_KEY, buf)? {
176                networks.load(data)?;
177            }
178
179            Ok(())
180        })
181    }
182
183    /// The subscriptions table.
184    pub const fn subscriptions(&self) -> &Subscriptions<NS> {
185        &self.subscriptions
186    }
187
188    /// The events queue.
189    pub const fn events(&self) -> &Events<NE> {
190        &self.events
191    }
192
193    /// The network store (wrapped for shared, change-notifying access).
194    pub const fn networks(&self) -> &SharedNetworks<N> {
195        &self.networks
196    }
197}
198
199/// The implementation needs a `DataModel` instance to interact with the underlying clusters of the data model.
200///
201/// `NC` is the network controller type driving the (optional) wireless connection
202/// manager from [`InteractionModel::run`]. It defaults to [`NoopWirelessNetCtl`], which is
203/// the right choice for Ethernet (and what the convenience [`InteractionModel::new`]
204/// constructor wires up); wireless devices pass a real controller via
205/// [`InteractionModel::new_with_net_ctl`].
206pub struct InteractionModel<
207    'a,
208    C,
209    B,
210    T,
211    K,
212    N,
213    NC = NoopWirelessNetCtl,
214    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
215    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
216> where
217    B: Buffers<IMBuffer>,
218{
219    matter: &'a Matter<'a>,
220    crypto: C,
221    buffers: &'a B,
222    kv: K,
223    net_ctl: NC,
224    subscriptions_buffers: SubscriptionsBuffers<'a, B, NS>,
225    state: &'a InteractionModelState<N, NS, NE>,
226    handler: T,
227}
228
229/// A [`InteractionModelState`] for an Ethernet device: its network store is a fixed
230/// [`EthNetwork`], so call sites need only the (defaulted) subscription/event
231/// sizes. Pairs with [`EthInteractionModel`].
232pub type EthInteractionModelState<
233    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
234    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
235> = InteractionModelState<EthNetwork<'static>, NS, NE>;
236
237/// A [`InteractionModel`] for an Ethernet device (network store fixed to [`EthNetwork`]),
238/// so the `N` generic disappears from call sites. Pairs with [`EthInteractionModelState`].
239pub type EthInteractionModel<
240    'a,
241    C,
242    B,
243    T,
244    K,
245    NC = NoopWirelessNetCtl,
246    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
247    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
248> = InteractionModel<'a, C, B, T, K, EthNetwork<'static>, NC, NS, NE>;
249
250/// A [`InteractionModelState`] for a wireless device, parameterized by the concrete
251/// wireless network store `N` (e.g. `WifiNetworks<3>` or a Thread store). Pairs
252/// with [`WirelessInteractionModel`].
253pub type WirelessInteractionModelState<
254    N,
255    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
256    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
257> = InteractionModelState<N, NS, NE>;
258
259/// A [`InteractionModel`] for a wireless device (network store `N`, network controller
260/// `NC`). Pairs with [`WirelessInteractionModelState`].
261pub type WirelessInteractionModel<
262    'a,
263    C,
264    B,
265    T,
266    K,
267    N,
268    NC,
269    const NS: usize = DEFAULT_MAX_SUBSCRIPTIONS,
270    const NE: usize = DEFAULT_MAX_EVENTS_BUF_SIZE,
271> = InteractionModel<'a, C, B, T, K, N, NC, NS, NE>;
272
273impl<'a, C, B, T, K, N, const NS: usize, const NE: usize>
274    InteractionModel<'a, C, B, T, K, N, NoopWirelessNetCtl, NS, NE>
275where
276    C: Crypto,
277    B: Buffers<IMBuffer>,
278    T: DataModel,
279    K: KvBlobStoreAccess,
280    N: Networks,
281{
282    /// Create the data model for a device that does not need an operational
283    /// wireless connection manager (typically an Ethernet device).
284    ///
285    /// This is a convenience wrapper around [`InteractionModel::new_with_net_ctl`] that
286    /// fixes the network controller to an inert [`NoopWirelessNetCtl`], so
287    /// [`InteractionModel::run`]'s connection-management branch stays dormant.
288    ///
289    /// # Arguments
290    /// - `matter` - a reference to the `Matter` instance
291    /// - `buffers` - a reference to an implementation of `Buffers<IMBuffer>` which is used for allocating RX and TX buffers on the fly, when necessary
292    /// - `handler` - an instance of type `T` which implements the `DataModel` trait. This instance is used for interacting with the underlying
293    ///   clusters of the data model. Note that the expectations is for the user to provide a handler that handles the Matter system clusters
294    ///   as well (Endpoint 0), possibly by decorating her own clusters with the `rs_matter::dm::root_endpoint::with_` methods
295    /// - `kv` - an instance of type `K` which implements the `KvBlobStoreAccess` trait
296    ///   (obtain one via [`Matter::kv`]). This instance is used for interacting with the key-value blob store.
297    /// - `state` - a reference to the [`InteractionModelState`] holding the subscriptions table, the
298    ///   events queue and the network store (the latter parameterized by the `Networks`
299    ///   implementation `N`).
300    #[inline(always)]
301    pub fn new(
302        matter: &'a Matter<'a>,
303        crypto: C,
304        buffers: &'a B,
305        handler: T,
306        kv: K,
307        state: &'a InteractionModelState<N, NS, NE>,
308    ) -> Self {
309        Self::new_with_net_ctl(
310            matter,
311            crypto,
312            buffers,
313            handler,
314            kv,
315            NoopWirelessNetCtl::new(NetworkType::Ethernet),
316            state,
317        )
318    }
319}
320
321impl<'a, C, B, T, K, N, NC, const NS: usize, const NE: usize>
322    InteractionModel<'a, C, B, T, K, N, NC, NS, NE>
323where
324    C: Crypto,
325    B: Buffers<IMBuffer>,
326    T: DataModel,
327    K: KvBlobStoreAccess,
328    N: Networks,
329{
330    /// Create the data model with an explicit network controller `net_ctl`.
331    ///
332    /// Use this for wireless devices: `net_ctl` drives the operational connection
333    /// manager run from [`InteractionModel::run`] (and is typically the same controller
334    /// instance also wired into the `NetworkCommissioning` cluster handler). For
335    /// Ethernet devices prefer the [`InteractionModel::new`] convenience constructor.
336    ///
337    /// # Arguments
338    /// - `matter` - a reference to the `Matter` instance
339    /// - `buffers` - a reference to an implementation of `Buffers<IMBuffer>` which is used for allocating RX and TX buffers on the fly, when necessary
340    /// - `handler` - an instance of type `T` which implements the `DataModel` trait. This instance is used for interacting with the underlying
341    ///   clusters of the data model. Note that the expectations is for the user to provide a handler that handles the Matter system clusters
342    ///   as well (Endpoint 0), possibly by decorating her own clusters with the `rs_matter::dm::root_endpoint::with_` methods
343    /// - `kv` - an instance of type `K` which implements the `KvBlobStoreAccess` trait
344    ///   (obtain one via [`Matter::kv`]). This instance is used for interacting with the key-value blob store.
345    /// - `net_ctl` - the network controller (`NetCtl` + `WirelessDiag` + `NetChangeNotif`) used by
346    ///   the operational wireless connection manager driven from [`InteractionModel::run`].
347    /// - `state` - a reference to the [`InteractionModelState`] holding the subscriptions table, the
348    ///   events queue and the network store (the latter parameterized by the `Networks`
349    ///   implementation `N`).
350    #[inline(always)]
351    pub fn new_with_net_ctl(
352        matter: &'a Matter<'a>,
353        crypto: C,
354        buffers: &'a B,
355        handler: T,
356        kv: K,
357        net_ctl: NC,
358        state: &'a InteractionModelState<N, NS, NE>,
359    ) -> Self {
360        state.subscriptions.clear();
361
362        Self {
363            matter,
364            crypto,
365            buffers,
366            kv,
367            net_ctl,
368            subscriptions_buffers: SubscriptionsBuffers::new(),
369            state,
370            handler,
371        }
372    }
373
374    /// Get a reference to the `Matter` instance this data model is associated with.
375    pub const fn matter(&self) -> &'a Matter<'a> {
376        self.matter
377    }
378
379    pub const fn crypto(&self) -> &C {
380        &self.crypto
381    }
382
383    /// Open the basic commissioning window.
384    ///
385    /// Equivalent to [`Matter::open_basic_comm_window`] but additionally
386    /// bumps the data version of the `AdministratorCommissioning`
387    /// cluster on the root endpoint and routes the change to
388    /// subscribers — both happen automatically because this `InteractionModel`
389    /// is itself the [`AttrChangeNotifier`] passed down.
390    ///
391    /// Prefer this entry point over the `Matter` one for any code path
392    /// that has a `InteractionModel` available; `Matter::open_basic_comm_window`
393    /// is the building block we delegate to and does not bump dataver
394    /// (see its docs).
395    pub fn open_basic_comm_window(&self, timeout_secs: u16) -> Result<(), Error> {
396        self.matter
397            .open_basic_comm_window(timeout_secs, &self.crypto, self)
398    }
399
400    /// Close the active commissioning window.
401    ///
402    /// Equivalent to [`Matter::close_comm_window`] but additionally
403    /// bumps the `AdministratorCommissioning` dataver and routes
404    /// subscribers via this `InteractionModel`'s [`AttrChangeNotifier`]. See
405    /// `open_basic_comm_window` for the rationale.
406    pub fn close_comm_window(&self) -> Result<bool, Error> {
407        self.matter.close_comm_window(self)
408    }
409
410    /// Bump `BasicInformation::ConfigurationVersion` by one, persist
411    /// the new value, and notify subscribers (which also bumps the
412    /// `BasicInformation` cluster's dataver via this `InteractionModel`'s
413    /// [`AttrChangeNotifier`]).
414    ///
415    /// Per Matter Core Spec, callers MUST invoke this
416    /// whenever the node's exposed fixed-quality surface changes —
417    /// typically after a firmware update that adds or removes
418    /// functionality, after an internal reconfiguration that changes
419    /// any `F`-quality attribute (Descriptor::ServerList,
420    /// PartsList, …), or (for bridges) after a bridged node is added
421    /// or removed. It is not invoked automatically by `rs-matter`
422    /// because the library has no way to know about an application's
423    /// reconfiguration events.
424    ///
425    /// Returns the new `ConfigurationVersion` value.
426    pub fn bump_configuration_version(&self) -> Result<u32, Error> {
427        // Delegate to `Matter::bump_configuration_version` for the
428        // in-memory bump + persist; pass `self` as the
429        // `AttrChangeNotifier` so the cluster's `Dataver` is bumped
430        // too (the `Matter`-level call by itself only routes
431        // subscribers and persists).
432        self.matter.bump_configuration_version(&self.kv, self)
433    }
434
435    /// Run the Data Model instance.
436    ///
437    /// This drives the IM timeout checks, the data-model handler's own background
438    /// job, the subscriptions reporting loop, and - for wireless devices - the
439    /// operational connection manager (inert for Ethernet, where `net_ctl` is a
440    /// [`NoopWirelessNetCtl`]).
441    pub async fn run(&self) -> Result<(), Error>
442    where
443        NC: NetCtl + WirelessDiag + NetChangeNotif,
444    {
445        let mut timeouts = pin!(self.run_timeout_checks());
446        let mut handler = pin!(self.handler.run(self));
447        let mut subs = pin!(self.process_subscriptions(self.matter));
448        let mut net = pin!(self.run_net_mgr());
449
450        select4(&mut timeouts, &mut handler, &mut subs, &mut net)
451            .coalesce()
452            .await
453    }
454
455    /// Drive the operational wireless connection manager.
456    ///
457    /// For Ethernet devices (`net_ctl.net_type() == NetworkType::Ethernet`) there
458    /// is nothing to manage, so this future simply pends forever. For wireless
459    /// devices it runs a [`WirelessMgr`] over the network store, cycling through
460    /// the registered networks and (re)connecting as needed once commissioned.
461    async fn run_net_mgr(&self) -> Result<(), Error>
462    where
463        NC: NetCtl + WirelessDiag + NetChangeNotif,
464    {
465        if self.net_ctl.net_type() == NetworkType::Ethernet {
466            // Nothing to manage for a wired device - just pend forever.
467            return core::future::pending().await;
468        }
469
470        let mut buf = [0u8; MAX_CREDS_SIZE];
471        let mut mgr = WirelessMgr::new(self.state.networks(), &self.net_ctl, &mut buf);
472
473        mgr.run().await
474    }
475
476    /// Perform a single, one-shot connect to the wireless network with the given
477    /// ID, immediately and regardless of the commissioning status.
478    ///
479    /// This drives the same [`WirelessMgr`] used by [`InteractionModel::run`]
480    /// (over this model's network controller and the network store owned by its
481    /// [`InteractionModelState`]), but calls [`WirelessMgr::connect_once`] rather
482    /// than the operational loop. It exists so a stack performing **non-concurrent**
483    /// (BLE-only) commissioning can replay the deferred `ConnectNetwork` once the
484    /// operational radio is up but before commissioning completes - without having
485    /// to own a `WirelessMgr` (or the networks) itself.
486    pub async fn connect_once(&self, network_id: &[u8]) -> Result<(), Error>
487    where
488        NC: NetCtl + WirelessDiag + NetChangeNotif,
489    {
490        let mut buf = [0u8; MAX_CREDS_SIZE];
491        let mut mgr = WirelessMgr::new(self.state.networks(), &self.net_ctl, &mut buf);
492
493        mgr.connect_once(network_id).await
494    }
495
496    async fn run_timeout_checks(&self) -> Result<(), Error> {
497        const CHECK_INTERVAL_SECS: u64 = 1;
498
499        loop {
500            Timer::after_secs(CHECK_INTERVAL_SECS).await;
501
502            self.check_timeouts(None)?;
503        }
504    }
505
506    fn check_timeouts(&self, exch_id: Option<ExchangeId>) -> Result<(), Error> {
507        let mut notify_mdns = || self.matter.transport().notify_mdns_changed();
508        let mut notify_change =
509            |endpt_id, clust_id| self.notify_cluster_changed(endpt_id, clust_id);
510
511        self.matter.with_state(|state| {
512            let expire_sess_id = exch_id.and_then(|exch_id| {
513                state
514                    .sessions
515                    .get(exch_id.session_id())
516                    .map(|sess| sess.id())
517            });
518
519            // Disarm the failsafe on timeout
520            state.failsafe.check_failsafe_timeout(
521                &mut state.fabrics,
522                &mut state.sessions,
523                &self.state.networks,
524                &self.kv,
525                expire_sess_id,
526                &mut notify_mdns,
527                &mut notify_change,
528            )?;
529
530            // Close the commissioning window on timeout
531            state
532                .pase
533                .check_comm_window_timeout(&mut notify_mdns, &mut notify_change)?;
534
535            Ok(())
536        })
537    }
538
539    /// Answer a responding exchange using the `DataModel` instance wrapped by this exchange handler.
540    pub async fn handle(&self, exchange: &mut Exchange<'_>) -> Result<(), Error> {
541        let fetch_meta = |exchange: &mut Exchange| {
542            let meta = exchange.rx()?.meta();
543            if meta.proto_id != PROTO_ID_INTERACTION_MODEL {
544                Err(ErrorCode::InvalidProto)?;
545            }
546
547            Result::<_, Error>::Ok(meta)
548        };
549
550        if exchange.rx().is_err() {
551            exchange.recv_fetch().await?;
552        }
553
554        let is_groupcast = exchange.is_groupcast()?;
555
556        let mut meta = fetch_meta(exchange)?;
557
558        let timeout_instant = if !is_groupcast && meta.opcode::<OpCode>()? == OpCode::TimedRequest {
559            let timeout = self.timed(exchange).await?;
560
561            exchange.recv_fetch().await?;
562            meta = fetch_meta(exchange)?;
563
564            Some(timeout)
565        } else {
566            None
567        };
568
569        self.check_timeouts(Some(exchange.id()))?;
570
571        // TODO: Handle the cases where we receive a timeout request
572        // before read and subscribe. This is probably not allowed.
573
574        match meta.opcode::<OpCode>()? {
575            OpCode::ReadRequest if is_groupcast => {
576                error!("Received a groupcast message for opcode: ReadRequest")
577            }
578            OpCode::ReadRequest if !is_groupcast => self.read(exchange).await?,
579            OpCode::WriteRequest => self.write(exchange, timeout_instant, is_groupcast).await?,
580            OpCode::InvokeRequest => self.invoke(exchange, timeout_instant, is_groupcast).await?,
581            OpCode::SubscribeRequest if is_groupcast => {
582                error!("Received a groupcast message for opcode: SubscribeRequest")
583            }
584            OpCode::SubscribeRequest if !is_groupcast => self.subscribe(exchange).await?,
585            OpCode::TimedRequest if !is_groupcast => {
586                Self::send_status(exchange, IMStatusCode::InvalidAction).await?
587            }
588            _ if is_groupcast => {
589                // Silently drop unsupported opcodes for group messages
590            }
591            opcode => {
592                error!("Invalid opcode: {:?}", opcode);
593                Err(ErrorCode::InvalidOpcode)?
594            }
595        }
596
597        if !is_groupcast {
598            exchange.acknowledge().await?;
599        }
600
601        Ok(())
602    }
603
604    /// Respond to a `ReadReq` request.
605    async fn read(&self, exchange: &mut Exchange<'_>) -> Result<(), Error> {
606        let Some((mut tx, rx)) = self.buffers(exchange).await? else {
607            return Ok(());
608        };
609
610        let read_req = ReadReq::new(TLVElement::new(&rx));
611        debug!("IM: Read request: {:?}", read_req);
612
613        if let Err(err) = Self::validate_read(&read_req) {
614            error!("Invalid read request: {:?}", err);
615            return Self::send_status(exchange, err.code().into()).await;
616        }
617
618        let req = ReportDataReq::Read(&read_req);
619
620        let mut wb = WriteBuf::new(&mut tx);
621
622        // Honor the `fabricFiltered` flag on the originating Read request.
623        // When set, fabric-sensitive events emitted on other fabrics are
624        // dropped before they reach the wire (Matter Core spec).
625        let fabric_filtered = req.fabric_filtered().unwrap_or(true);
626
627        let mut resp = ReportDataResponder::new(
628            &req,
629            None,
630            HandlerInvoker::new(exchange, self),
631            EventReader::new(0, u64::MAX, fabric_filtered),
632            &self.state.events,
633        );
634
635        resp.respond(&mut wb, true, true, &self.handler, |_, _, _| true)
636            .await?;
637
638        Ok(())
639    }
640
641    /// Validate a `ReadReq` request prior to processing.
642    fn validate_read(req: &ReadReq<'_>) -> Result<(), Error> {
643        if let Some(attr_requests) = req.attr_requests()? {
644            for attr_req in attr_requests {
645                Self::validate_attr_wildcard_path(&attr_req?)?;
646            }
647        }
648
649        Ok(())
650    }
651
652    /// Per-spec validation of an `AttrPath` that may contain wildcards.
653    ///
654    /// Per Matter spec, when a path uses a wildcard cluster
655    /// but specifies a concrete attribute id, that attribute id must be a
656    /// global (system) attribute. Any other combination must be rejected with
657    /// `INVALID_ACTION`.
658    fn validate_attr_wildcard_path(path: &AttrPath) -> Result<(), Error> {
659        if path.cluster.is_none() {
660            if let Some(attr_id) = path.attr {
661                if !Attribute::is_system_attr(attr_id) {
662                    return Err(ErrorCode::InvalidAction.into());
663                }
664            }
665        }
666
667        Ok(())
668    }
669
670    /// Respond to a `WriteReq` request.
671    ///
672    /// Arguments:
673    /// - `exchange` - the exchange to respond to
674    /// - `timeout_instant` - an optional timeout instant, if the request is a timed request
675    async fn write(
676        &self,
677        exchange: &mut Exchange<'_>,
678        timeout_instant: Option<Instant>,
679        is_groupcast: bool,
680    ) -> Result<(), Error> {
681        while exchange.rx().is_ok() {
682            // Loop while there are more write request chunks to process
683
684            let Some((mut tx, rx)) = self.buffers(exchange).await? else {
685                break;
686            };
687
688            let req = WriteReq::new(TLVElement::new(&rx));
689            debug!("IM: Write request: {:?}", req);
690
691            let timed = req.timed_request()?;
692
693            if self.timed_out(exchange, timeout_instant, timed).await? {
694                break;
695            }
696
697            let mut wb = WriteBuf::new(&mut tx);
698
699            let mut resp = WriteResponder::new(&req, HandlerInvoker::new(exchange, self));
700
701            resp.respond(&mut wb, &self.handler, is_groupcast).await?;
702
703            if req.more_chunks()? {
704                // This write request is just one of the chunks, so we need to wait and process
705                // the next chunk as well
706                exchange.recv_fetch().await?;
707            }
708        }
709
710        Ok(())
711    }
712
713    /// Respond to an `InvokeReq` request.
714    ///
715    /// Arguments:
716    /// - `exchange` - the exchange to respond to
717    /// - `timeout_instant` - an optional timeout instant, if the request is a timed request
718    async fn invoke(
719        &self,
720        exchange: &mut Exchange<'_>,
721        timeout_instant: Option<Instant>,
722        is_groupcast: bool,
723    ) -> Result<(), Error> {
724        let Some((mut tx, rx)) = self.buffers(exchange).await? else {
725            return Ok(());
726        };
727
728        let req = InvReq::new(TLVElement::new(&rx));
729        debug!("IM: Invoke request: {:?}", req);
730
731        let timed = req.timed_request()?;
732
733        if self.timed_out(exchange, timeout_instant, timed).await? {
734            return Ok(());
735        }
736
737        let max_paths = exchange.matter().dev_det().max_paths_per_invoke as usize;
738
739        if let Some(reqs) = req.inv_requests()? {
740            let mut count = 0;
741            for r in &reqs {
742                let _ = r?;
743                count += 1;
744            }
745
746            if count > max_paths {
747                return Self::send_status(exchange, IMStatusCode::InvalidAction).await;
748            }
749
750            // Per Matter Core spec: when an `InvokeRequestMessage`
751            // carries multiple `CommandDataIB` entries, each MUST include a unique
752            // `CommandRef` and the request paths SHALL be unique. `count` is bounded
753            // by `max_paths_per_invoke` (typically a single-digit number), so the
754            // O(n²) pairwise check below is cheaper than allocating buffers.
755            if count > 1 {
756                for (i, req_i) in reqs.iter().enumerate() {
757                    let req_i = req_i?;
758                    if req_i.command_ref.is_none() {
759                        return Self::send_status(exchange, IMStatusCode::InvalidAction).await;
760                    }
761                    for req_j in reqs.iter().skip(i + 1) {
762                        let req_j = req_j?;
763                        if req_i.path == req_j.path || req_i.command_ref == req_j.command_ref {
764                            return Self::send_status(exchange, IMStatusCode::InvalidAction).await;
765                        }
766                    }
767                }
768            }
769        }
770
771        let mut wb = WriteBuf::new(&mut tx);
772
773        let mut resp = InvokeResponder::new(&req, HandlerInvoker::new(exchange, self));
774
775        resp.respond(&mut wb, &self.handler, is_groupcast).await
776    }
777
778    /// Respond to a `SubscribeReq` request by priming the subscription (i.e. doing an initial data report)
779    /// and if the priming is successful, sending a `SubscribeResp` response to the peer and registering
780    /// the subscription details in the `Subscriptions` instance.
781    async fn subscribe(&self, exchange: &mut Exchange<'_>) -> Result<(), Error> {
782        let Some((mut tx, rx)) = self.buffers(exchange).await? else {
783            return Ok(());
784        };
785
786        let req = SubscribeReq::new(TLVElement::new(&rx));
787        debug!("IM: Subscribe request: {:?}", req);
788
789        let accessor = exchange.accessor()?;
790
791        if let Err(err) = self.validate_subscribe(&req, &accessor) {
792            error!("Invalid subscribe request: {:?}", err);
793            return Self::send_status(exchange, err.code().into()).await;
794        }
795
796        let (fab_idx, peer_node_id) = exchange.with_state(|state| {
797            let sess = exchange.id().session(&mut state.sessions);
798
799            let fab_idx = NonZeroU8::new(sess.get_local_fabric_idx()).ok_or(ErrorCode::Invalid)?;
800            let peer_node_id = sess.get_peer_node_id().ok_or(ErrorCode::Invalid)?;
801
802            Ok((fab_idx, peer_node_id))
803        })?;
804
805        if !req.keep_subs()? {
806            self.state
807                .subscriptions
808                .remove(&self.subscriptions_buffers, |sub| {
809                    (sub.ids().fab_idx == fab_idx && sub.ids().peer_node_id == peer_node_id)
810                        .then_some("new subscription request")
811                });
812        }
813
814        let max_int_secs = core::cmp::max(req.max_int_ceil()?, 40); // Say we need at least 4 secs for potential latencies
815        let min_int_secs = req.min_int_floor()?;
816
817        let now = Instant::now();
818
819        let Some(mut rctx) = self.state.subscriptions.add(
820            now,
821            fab_idx,
822            peer_node_id,
823            exchange.id().session_id(),
824            min_int_secs,
825            max_int_secs,
826            self.state.events.watermark(),
827            rx,
828            &self.subscriptions_buffers,
829        ) else {
830            return Self::send_status(exchange, IMStatusCode::ResourceExhausted).await;
831        };
832
833        let primed = self.report_data(&mut rctx, &mut tx, exchange, true).await?;
834
835        if primed {
836            exchange
837                .send_with(|_, wb| {
838                    SubscribeResp::write(wb, rctx.subscription().ids().id, max_int_secs)?;
839                    Ok(Some(OpCode::SubscribeResponse.into()))
840                })
841                .await?;
842
843            rctx.set_keep();
844
845            info!("Subscription {:?} primed", rctx.subscription().ids());
846
847            // Commit the subscription into the table now (its `report_complete`
848            // runs on `Drop`) and then wake the reporter so it can account for
849            // the new subscription's deadline.
850            drop(rctx);
851            self.state.subscriptions.notification.notify();
852        }
853
854        Ok(())
855    }
856
857    /// Validates the subscription request
858    fn validate_subscribe(
859        &self,
860        req: &SubscribeReq<'_>,
861        accessor: &Accessor<'_>,
862    ) -> Result<(), Error> {
863        // As per spec, we need to validate that the subscription request
864        // contains existing endpoints, clusters and attributes, and if not
865        // we should (a bit surprisingly) return InvalidAction
866
867        self.handler.access(|node| {
868            let mut has_attrs = false;
869            let mut has_events = false;
870
871            if let Some(attr_requests) = req.attr_requests()? {
872                has_attrs = true;
873
874                for attr_req in attr_requests {
875                    let path = attr_req?;
876
877                    if path.is_wildcard() {
878                        Self::validate_attr_wildcard_path(&path)?;
879
880                        if !node.has_accessible_attr(&path, accessor) {
881                            return Err(ErrorCode::InvalidAction.into());
882                        }
883                    } else {
884                        node.validate_attr_path(&path, false, false, accessor)
885                            .map_err(|_| ErrorCode::InvalidAction)?;
886                    }
887                }
888            }
889
890            if let Some(event_reqs) = req.event_requests()? {
891                has_events = true;
892
893                for event_req in event_reqs {
894                    let path = event_req?;
895
896                    if !path.is_wildcard() {
897                        node.validate_event_path(&path, accessor)
898                            .map_err(|_| ErrorCode::InvalidAction)?;
899                    }
900                }
901            }
902
903            if !has_attrs && !has_events {
904                // Empty subscribe requests are not allowed either
905                return Err(ErrorCode::InvalidAction.into());
906            }
907
908            Ok(())
909        })
910    }
911
912    /// Process all valid subscriptions in an endless loop, checking for changes
913    /// and reporting them to the peers.
914    async fn process_subscriptions(&self, matter: &Matter<'_>) -> Result<(), Error> {
915        loop {
916            // Sleep until the soonest subscription deadline: the end of a
917            // `min_int` quiet period for a subscription holding back a change,
918            // or the chosen liveness wake point. A change, event, removal, a torn
919            // down session, or a newly accepted subscription (the accept path
920            // notifies) all wake the loop early. With no subscription there is
921            // no deadline, so just wait to be notified.
922            let mut notification = pin!(self.state.subscriptions.notification.wait());
923            let mut session_removed = pin!(matter.transport().wait_session_removed());
924
925            // With no subscription (or none primed) the deadline is `Instant::MAX`,
926            // so the timer effectively never fires and the loop just waits to be
927            // notified.
928            let deadline = self
929                .state
930                .subscriptions
931                .next_report_at(self.state.events.watermark(), &self.subscriptions_buffers);
932            let mut timeout = pin!(Timer::at(deadline));
933
934            select3(&mut notification, &mut timeout, &mut session_removed).await;
935
936            let now = Instant::now();
937
938            // First remove all expired or no-longer valid subscriptions
939
940            loop {
941                let removed_any =
942                    self.state
943                        .subscriptions
944                        .remove(&self.subscriptions_buffers, |sub| {
945                            if sub.is_expired(now) {
946                                return Some("expired");
947                            }
948
949                            matter.with_state(|state| {
950                                if state.fabrics.get(sub.ids().fab_idx).is_none() {
951                                    return Some("fabric removed");
952                                }
953
954                                // The session the subscription was accepted on was
955                                // torn down (eviction, explicit close, peer-side
956                                // CASE re-handshake, ...). Per Matter spec
957                                // subscriptions are scoped to the session they
958                                // were established on, and the publisher can no
959                                // longer route reports to the subscriber. Drop
960                                // immediately rather than waiting for `max_int`
961                                // to expire and time-out the send.
962                                if state.sessions.get(sub.session_id()).is_none() {
963                                    return Some("session removed");
964                                }
965
966                                None
967                            })
968                        });
969
970                if !removed_any {
971                    break;
972                }
973            }
974
975            // Now report while there are subscriptions which are due for reporting
976
977            let event_numbers_watermark = self.state.events.watermark();
978
979            loop {
980                let Some(mut rctx) = self.state.subscriptions.report(
981                    now,
982                    event_numbers_watermark,
983                    &self.subscriptions_buffers,
984                ) else {
985                    break;
986                };
987
988                let result = self.process_subscription(matter, &mut rctx).await;
989
990                match result {
991                    Ok(true) => rctx.set_keep(),
992                    Ok(false) => (),
993                    Err(e) => error!(
994                        "Error processing subscription {:?}: {:?}",
995                        rctx.subscription().ids(),
996                        e
997                    ),
998                }
999            }
1000
1001            // Periodically trim changed-attr entries that have been reported by every
1002            // subscription, so the table does not accumulate stale promoted wildcards.
1003            self.state.subscriptions.purge_reported_changes();
1004        }
1005    }
1006
1007    /// Process one valid subscription, reporting the data to the peer.
1008    ///
1009    /// Arguments:
1010    /// - `matter` - a reference to the `Matter` instance
1011    /// - `fabric_idx` - the fabric index of the peer
1012    /// - `peer_node_id` - the node ID of the peer
1013    /// - `session_id` - the session ID of the peer, if any
1014    /// - `sub` - the received and saved data for the subscription, when the subscription was primed
1015    /// - `min_event_number` - the subscription's current event watermark; updated
1016    ///   in place as events are emitted so the caller can persist it
1017    /// - `ctx` - the report context for this subscription
1018    #[allow(clippy::too_many_arguments)]
1019    async fn process_subscription(
1020        &self,
1021        matter: &Matter<'_>,
1022        rctx: &mut ReportContext<'_, '_, B, NS>,
1023    ) -> Result<bool, Error> {
1024        let mut exchange =
1025            Exchange::initiate_for_session(matter, rctx.subscription().session_id())?;
1026
1027        if let Some(mut tx) = self.buffers.get().await {
1028            // Always safe as `IMBuffer` is defined to be `MAX_EXCHANGE_RX_BUF_SIZE`, which is bigger than `MAX_EXCHANGE_TX_BUF_SIZE`
1029            unwrap!(tx.resize_default(MAX_EXCHANGE_TX_BUF_SIZE));
1030
1031            let primed = self
1032                .report_data(rctx, &mut tx, &mut exchange, false)
1033                .await?;
1034
1035            exchange.acknowledge().await?;
1036
1037            Ok(primed)
1038        } else {
1039            error!(
1040                "No TX buffer available for processing subscription {:?}",
1041                rctx.subscription().ids(),
1042            );
1043
1044            Ok(false)
1045        }
1046    }
1047
1048    /// Process a `TimedReq` request, which is used to set a timeout for the following Write/Invoke request.
1049    async fn timed(&self, exchange: &mut Exchange<'_>) -> Result<Instant, Error> {
1050        let req = TimedReq::from_tlv(&get_root_node_struct(exchange.rx()?.payload())?)?;
1051        debug!("IM: Timed request: {:?}", req);
1052
1053        let timeout_instant = req.timeout_instant();
1054
1055        Self::send_status(exchange, IMStatusCode::Success).await?;
1056
1057        Ok(timeout_instant)
1058    }
1059
1060    /// A utility to check whether a timed request has timed out, and if so, send a timeout status response
1061    async fn timed_out(
1062        &self,
1063        exchange: &mut Exchange<'_>,
1064        timeout_instant: Option<Instant>,
1065        timed_req: bool,
1066    ) -> Result<bool, Error> {
1067        let status = {
1068            if timed_req != timeout_instant.is_some() {
1069                Some(IMStatusCode::TimedRequestMisMatch)
1070            } else if timeout_instant
1071                .map(|timeout_instant| Instant::now() > timeout_instant)
1072                .unwrap_or(false)
1073            {
1074                Some(IMStatusCode::Timeout)
1075            } else {
1076                None
1077            }
1078        };
1079
1080        if let Some(status) = status {
1081            Self::send_status(exchange, status).await?;
1082
1083            Ok(true)
1084        } else {
1085            Ok(false)
1086        }
1087    }
1088
1089    /// A utility to respond with a `ReportData` response to a subscription request, which is used to report data to the peer.
1090    ///
1091    /// Arguments:
1092    /// - `id` - the subscription ID
1093    /// - `fabric_idx` - the fabric index of the peer
1094    /// - `peer_node_id` - the node ID of the peer
1095    /// - `min_event_number` - the minimum event number to report
1096    /// - `rx` - the received data for the subscription, when the subscription was primed
1097    /// - `tx` - the TX buffer to write the response to
1098    /// - `exchange` - the exchange to respond to
1099    /// - `with_dataver` - whether to include the data version in the response
1100    #[allow(clippy::too_many_arguments)]
1101    async fn report_data(
1102        &self,
1103        rctx: &mut ReportContext<'_, '_, B, NS>,
1104        tx: &mut [u8],
1105        exchange: &mut Exchange<'_>,
1106        with_dataver: bool,
1107    ) -> Result<bool, Error>
1108    where
1109        T: DataModel,
1110    {
1111        let mut wb = WriteBuf::new(tx);
1112
1113        let sub_req = SubscribeReq::new(TLVElement::new(rctx.rx()));
1114        let req = if with_dataver {
1115            ReportDataReq::Subscribe(&sub_req)
1116        } else {
1117            ReportDataReq::SubscribeReport(&sub_req)
1118        };
1119
1120        // Honor the `fabricFiltered` flag on the originating Subscribe request.
1121        // When set, fabric-sensitive events emitted on other fabrics are
1122        // dropped before they reach the wire (Matter Core spec).
1123        let fabric_filtered = req.fabric_filtered().unwrap_or(true);
1124
1125        let mut resp = ReportDataResponder::new(
1126            &req,
1127            Some(rctx.subscription().ids().id),
1128            HandlerInvoker::new(exchange, self),
1129            EventReader::new(
1130                rctx.max_seen_event_number(),
1131                rctx.next_max_seen_event_number(),
1132                fabric_filtered,
1133            ),
1134            &self.state.events,
1135        );
1136
1137        let sub_valid = resp
1138            .respond(
1139                &mut wb,
1140                false,
1141                rctx.should_send_if_empty(),
1142                &self.handler,
1143                |e, c, a| rctx.should_report_attr(e, c, a),
1144            )
1145            .await?;
1146
1147        if !sub_valid {
1148            warn!(
1149                "Subscription {:?} removed during reporting",
1150                rctx.subscription().ids()
1151            );
1152        }
1153
1154        Ok(sub_valid)
1155    }
1156
1157    /// A utility to fetch a pair of TX/RX buffers for processing an Interaction Model request.
1158    ///
1159    /// If there are no free buffers available, this method will send a `Busy` status response to the peer.
1160    ///
1161    /// Upon returning:
1162    /// - The RX buffer will contain the payload of the received Interaction Model request
1163    /// - The TX buffer will be resized to `MAX_EXCHANGE_TX_BUF_SIZE` and will be ready to be written to
1164    ///
1165    /// Returns:
1166    /// - `Ok(Some((tx, rx)))` - if both TX and RX buffers are available
1167    /// - `Ok(None)` - if no buffers are available, and a `Busy` status response has been sent
1168    /// - `Err(Error)` - if an error occurred while fetching the buffers or sending the status response
1169    async fn buffers(
1170        &self,
1171        exchange: &mut Exchange<'_>,
1172    ) -> Result<Option<(B::Buffer<'a>, B::Buffer<'a>)>, Error> {
1173        if let Some(tx) = self.tx_buffer(exchange).await? {
1174            if let Some(rx) = self.rx_buffer(exchange).await? {
1175                return Ok(Some((tx, rx)));
1176            }
1177        }
1178
1179        Ok(None)
1180    }
1181
1182    /// A utility to fetch a RX buffer for processing an Interaction Model request.
1183    ///
1184    /// If there are no free buffers available, this method will send a `Busy` status response to the peer.
1185    ///
1186    /// Upon returning, the RX buffer will contain the payload of the received Interaction Model request.
1187    ///
1188    /// Returns:
1189    /// - `Ok(Some(rx))` - if a RX buffer is available
1190    /// - `Ok(None)` - if no RX buffer is available, and a `Busy` status response has been sent
1191    /// - `Err(Error)` - if an error occurred while fetching the buffer or sending the status response
1192    async fn rx_buffer(&self, exchange: &mut Exchange<'_>) -> Result<Option<B::Buffer<'a>>, Error> {
1193        if let Some(mut buffer) = self.buffer(exchange).await? {
1194            let rx = exchange.rx()?;
1195
1196            buffer.clear();
1197
1198            // Safe to unwrap, as `IMBuffer` is defined to be `MAX_EXCHANGE_RX_BUF_SIZE`, i.e. it cannot be overflown
1199            // by the payload of the received exchange.
1200            unwrap!(buffer.extend_from_slice(rx.payload()));
1201
1202            exchange.rx_done()?;
1203
1204            Ok(Some(buffer))
1205        } else {
1206            Ok(None)
1207        }
1208    }
1209
1210    /// A utility to fetch a TX buffer for processing an Interaction Model request.
1211    ///
1212    /// If there are no free buffers available, this method will send a `Busy` status response to the peer.
1213    ///
1214    /// Upon returning, the TX buffer will be resized to `MAX_EXCHANGE_TX_BUF_SIZE` and will be ready to be written to.
1215    ///
1216    /// Returns:
1217    /// - `Ok(Some(tx))` - if a TX buffer is available
1218    /// - `Ok(None)` - if no TX buffer is available, and a `Busy` status response has been sent
1219    /// - `Err(Error)` - if an error occurred while fetching the buffer or sending the status response
1220    async fn tx_buffer(&self, exchange: &mut Exchange<'_>) -> Result<Option<B::Buffer<'a>>, Error> {
1221        if let Some(mut buffer) = self.buffer(exchange).await? {
1222            // Always safe as `IMBuffer` is defined to be `MAX_EXCHANGE_RX_BUF_SIZE`, which is bigger than `MAX_EXCHANGE_TX_BUF_SIZE`
1223            unwrap!(buffer.resize_default(MAX_EXCHANGE_TX_BUF_SIZE));
1224
1225            Ok(Some(buffer))
1226        } else {
1227            Ok(None)
1228        }
1229    }
1230
1231    /// A utility to fetch a buffer for processing an Interaction Model request.
1232    ///
1233    /// If there are no free buffers available, this method will send a `Busy` status response to the peer.
1234    ///
1235    /// Upon returning, the buffer will be UNINITIALIZED. I.e. it is up to the user to resize it appropriately
1236    /// 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.
1237    ///
1238    /// Returns:
1239    /// - `Ok(Some(buffer))` - if a buffer is available
1240    /// - `Ok(None)` - if no buffer is available, and a `Busy` status response has been sent
1241    /// - `Err(Error)` - if an error occurred while fetching the buffer or sending the status response
1242    async fn buffer(&self, exchange: &mut Exchange<'_>) -> Result<Option<B::Buffer<'a>>, Error> {
1243        if let Some(buffer) = self.buffers.get().await {
1244            Ok(Some(buffer))
1245        } else {
1246            Self::send_status(exchange, IMStatusCode::Busy).await?;
1247
1248            Ok(None)
1249        }
1250    }
1251
1252    /// A utility to send a status response to the peer.
1253    async fn send_status(exchange: &mut Exchange<'_>, status: IMStatusCode) -> Result<(), Error> {
1254        exchange
1255            .send_with(|_, wb| {
1256                StatusResp::write(wb, status)?;
1257
1258                Ok(Some(OpCode::StatusResponse.into()))
1259            })
1260            .await
1261    }
1262}
1263
1264impl<C, B, T, K, N, NC, const NS: usize, const NE: usize> ExchangeHandler
1265    for InteractionModel<'_, C, B, T, K, N, NC, NS, NE>
1266where
1267    C: Crypto,
1268    B: Buffers<IMBuffer>,
1269    T: DataModel,
1270    K: KvBlobStoreAccess,
1271    N: Networks,
1272{
1273    async fn handle(&self, mut exchange: Exchange<'_>) -> Result<(), Error> {
1274        InteractionModel::handle(self, &mut exchange).await
1275    }
1276}
1277
1278impl<C, B, T, K, N, NC, const NS: usize, const NE: usize> HandlerContext
1279    for InteractionModel<'_, C, B, T, K, N, NC, NS, NE>
1280where
1281    C: Crypto,
1282    B: Buffers<IMBuffer>,
1283    T: DataModel,
1284    K: KvBlobStoreAccess,
1285    N: Networks,
1286{
1287    fn matter(&self) -> &Matter<'_> {
1288        self.matter
1289    }
1290
1291    fn crypto(&self) -> impl Crypto + '_ {
1292        &self.crypto
1293    }
1294
1295    fn kv(&self) -> impl KvBlobStoreAccess + '_ {
1296        &self.kv
1297    }
1298
1299    fn networks(&self) -> impl NetworksAccess + '_ {
1300        &self.state.networks
1301    }
1302
1303    fn metadata(&self) -> impl Metadata + '_ {
1304        &self.handler
1305    }
1306
1307    fn handler(&self) -> impl AsyncHandler + '_ {
1308        &self.handler
1309    }
1310
1311    fn buffers(&self) -> impl Buffers<IMBuffer> + '_ {
1312        self.buffers
1313    }
1314}
1315
1316impl<C, B, T, K, N, NC, const NS: usize, const NE: usize> AttrChangeNotifier
1317    for InteractionModel<'_, C, B, T, K, N, NC, NS, NE>
1318where
1319    C: Crypto,
1320    B: Buffers<IMBuffer>,
1321    T: DataModel,
1322    K: KvBlobStoreAccess,
1323    N: Networks,
1324{
1325    fn notify_attr_changed(&self, endpoint_id: EndptId, cluster_id: ClusterId, attr_id: AttrId) {
1326        self.handler.bump_dataver(MatchContextInstance::new(
1327            Some(endpoint_id),
1328            Some(cluster_id),
1329        ));
1330        self.state
1331            .subscriptions
1332            .notify_attr_changed(endpoint_id, cluster_id, attr_id);
1333    }
1334
1335    fn notify_cluster_changed(&self, endpoint_id: EndptId, cluster_id: ClusterId) {
1336        self.handler.bump_dataver(MatchContextInstance::new(
1337            Some(endpoint_id),
1338            Some(cluster_id),
1339        ));
1340        self.state
1341            .subscriptions
1342            .notify_cluster_changed(endpoint_id, cluster_id);
1343    }
1344
1345    fn notify_endpoint_changed(&self, endpoint_id: EndptId) {
1346        self.handler
1347            .bump_dataver(MatchContextInstance::new(Some(endpoint_id), None));
1348        self.state
1349            .subscriptions
1350            .notify_endpoint_changed(endpoint_id)
1351    }
1352
1353    fn notify_all_changed(&self) {
1354        self.handler
1355            .bump_dataver(MatchContextInstance::new(None, None));
1356        self.state.subscriptions.notify_all_changed()
1357    }
1358}
1359
1360impl<C, B, T, K, N, NC, const NS: usize, const NE: usize> EventEmitter
1361    for InteractionModel<'_, C, B, T, K, N, NC, NS, NE>
1362where
1363    C: Crypto,
1364    B: Buffers<IMBuffer>,
1365    T: DataModel,
1366    K: KvBlobStoreAccess,
1367    N: Networks,
1368{
1369    fn emit_event<F>(
1370        &self,
1371        endpoint_id: EndptId,
1372        cluster_id: ClusterId,
1373        event_id: EventId,
1374        priority: EventPriority,
1375        f: F,
1376    ) -> Result<u64, Error>
1377    where
1378        F: FnOnce(EventTLVWrite<'_>) -> Result<(), Error>,
1379    {
1380        let event_number =
1381            self.state
1382                .events
1383                .push(endpoint_id, cluster_id, event_id, priority, &self.kv, f)?;
1384
1385        self.state
1386            .subscriptions
1387            .notify_event_emitted(endpoint_id, cluster_id, event_id);
1388
1389        Ok(event_number)
1390    }
1391}
1392
1393pub enum RespondOutcome {
1394    Accepted,
1395    Rejected,
1396    Empty,
1397}
1398
1399/// This type responds with a `ReportData` response to all of:
1400/// - A `ReadReq`
1401/// - A `SubscribeReq`
1402/// - A `SubscribeReportReq` (i.e. once a valid recorded subscription is detected as in a need to be reported on)
1403///
1404/// The responder handles chunking as needed. I.e. if reported data is too large to fit into a single
1405/// Matter message, it will send the data in multiple chunks (i.e. with multiple Matter messages), waiting for
1406/// a `Success` response from the peer after each chunk, and then continuing to send the next chunk until all data is sent.
1407struct ReportDataResponder<'a, 'b, 'c, const NE: usize, C> {
1408    req: &'a ReportDataReq<'a>,
1409    subscription_id: Option<u32>,
1410    invoker: HandlerInvoker<'b, 'c, C>,
1411    event_reader: EventReader,
1412    events: &'a Events<NE>,
1413}
1414
1415impl<'a, 'b, 'c, const NE: usize, C> ReportDataResponder<'a, 'b, 'c, NE, C>
1416where
1417    C: HandlerContext,
1418{
1419    // This is the amount of space we reserve for the structure/array closing TLVs
1420    // to be attached towards the end of long reads
1421    const LONG_READS_TLV_RESERVE_SIZE: usize = 24;
1422
1423    /// Create a new `ReportDataResponder`.
1424    const fn new(
1425        req: &'a ReportDataReq<'a>,
1426        subscription_id: Option<u32>,
1427        invoker: HandlerInvoker<'b, 'c, C>,
1428        event_reader: EventReader,
1429        events: &'a Events<NE>,
1430    ) -> Self {
1431        Self {
1432            req,
1433            subscription_id,
1434            invoker,
1435            event_reader,
1436            events,
1437        }
1438    }
1439
1440    /// Respond to the request with a `ReportData` response, possibly with more than one
1441    /// chunk if the data is too large to fit into a single Matter message.
1442    ///
1443    /// Arguments:
1444    /// - `wb` - the buffer to use while sending the response
1445    /// - `suppress_last_resp` - whether to suppress the response from the peer. When multiple Matter messages are
1446    ///   being sent due to chunking, this is valid for the last chunk only, as the others - by necessity need to have a
1447    ///   status response by the other peer
1448    async fn respond<M, F>(
1449        &mut self,
1450        wb: &mut WriteBuf<'_>,
1451        suppress_last_resp: bool,
1452        send_if_empty: bool,
1453        metadata: M,
1454        mut filter: F,
1455    ) -> Result<bool, Error>
1456    where
1457        M: Metadata,
1458        F: FnMut(EndptId, ClusterId, u32) -> bool,
1459    {
1460        let mut empty = true;
1461
1462        self.start_reply(wb)?;
1463
1464        if !self
1465            .report_attributes(wb, &mut empty, &metadata, &mut filter)
1466            .await?
1467        {
1468            return Ok(false);
1469        }
1470
1471        if !self.report_events(wb, &mut empty, &metadata).await? {
1472            return Ok(false);
1473        }
1474
1475        if send_if_empty || !empty {
1476            self.send(ReportDataChunkState::Done, suppress_last_resp, wb)
1477                .await
1478        } else {
1479            debug!("No data to report, skipping sending ReportData response");
1480
1481            Ok(true)
1482        }
1483    }
1484
1485    async fn report_attributes<M, F>(
1486        &mut self,
1487        wb: &mut WriteBuf<'_>,
1488        empty: &mut bool,
1489        metadata: M,
1490        mut filter: F,
1491    ) -> Result<bool, Error>
1492    where
1493        M: Metadata,
1494        F: FnMut(EndptId, ClusterId, u32) -> bool,
1495    {
1496        let accessor = self.invoker.exchange().accessor()?;
1497
1498        if self.req.attr_requests()?.is_some() {
1499            wb.start_array(&TLVTag::Context(ReportDataRespTag::AttributeReports as u8))?;
1500
1501            for item in expand_read(&metadata, self.req, &accessor, &mut filter)? {
1502                let item = item?;
1503
1504                *empty = false;
1505
1506                loop {
1507                    let result = self.invoker.process_read(&item, &mut *wb).await;
1508
1509                    match result {
1510                        Ok(()) => break,
1511                        Err(err) if err.code() == ErrorCode::NoSpace => {
1512                            let array_attr = item.as_ref().ok().filter(|attr| {
1513                                attr.list_index.is_none()
1514                                    // The whole attribute is requested
1515                                    // Check if it is an array, and if so, send it as individual items instead
1516                                    && attr.array
1517                            });
1518
1519                            if let Some(array_attr) = array_attr {
1520                                if self.send_array_items(array_attr, wb).await? {
1521                                    break;
1522                                } else {
1523                                    return Ok(false);
1524                                }
1525                            } else {
1526                                debug!("<<< No TX space, chunking >>>");
1527                                if !self
1528                                    .send(ReportDataChunkState::ChunkingAttributes, false, wb)
1529                                    .await?
1530                                {
1531                                    return Ok(false);
1532                                }
1533                            }
1534                        }
1535                        Err(err) => Err(err)?,
1536                    }
1537                }
1538            }
1539
1540            wb.end_container()?;
1541        }
1542
1543        Ok(true)
1544    }
1545
1546    async fn report_events<M>(
1547        &mut self,
1548        wb: &mut WriteBuf<'_>,
1549        empty: &mut bool,
1550        metadata: M,
1551    ) -> Result<bool, Error>
1552    where
1553        M: Metadata,
1554    {
1555        let accessor = self.invoker.exchange().accessor()?;
1556
1557        if let Some(event_reqs) = self.req.event_requests()? {
1558            wb.start_array(&TLVTag::Context(ReportDataRespTag::EventReports as _))?;
1559
1560            // Validate concrete event paths against node metadata
1561            // and emit EventStatusIB for non-wildcard paths that don't match
1562            for event_req in event_reqs.iter() {
1563                let path = event_req?;
1564
1565                if !path.is_wildcard() {
1566                    if let Err(status) =
1567                        metadata.access(|node| node.validate_event_path(&path, &accessor))
1568                    {
1569                        if matches!(status, IMStatusCode::UnsupportedEvent) {
1570                            // Event does not exist on this endpoint
1571                            // TODO: Look at TestEventsById.yaml
1572                            // Seems we should not error out in that case?
1573                            continue;
1574                        }
1575
1576                        *empty = false;
1577
1578                        let resp = EventResp::Status(EventStatus::new(path, status, None));
1579
1580                        let mut result = resp.to_tlv(&TLVTag::Anonymous, &mut *wb);
1581
1582                        if let Err(e) = &result {
1583                            if e.code() == ErrorCode::NoSpace {
1584                                debug!("<<< No TX space, chunking >>>");
1585                                if !self
1586                                    .send(ReportDataChunkState::ChunkingEvents, false, &mut *wb)
1587                                    .await?
1588                                {
1589                                    return Ok(false);
1590                                }
1591
1592                                result = resp.to_tlv(&TLVTag::Anonymous, &mut *wb);
1593                            }
1594                        }
1595
1596                        result?;
1597                    }
1598                }
1599            }
1600
1601            let event_filters = self.req.event_filters()?;
1602
1603            loop {
1604                let finished = self.events.fetch(|events| {
1605                    metadata.access(|node| {
1606                        for event in events {
1607                            let result = self.event_reader.process_read(
1608                                event,
1609                                &event_reqs,
1610                                &event_filters,
1611                                node,
1612                                &accessor,
1613                                &mut *wb,
1614                            );
1615
1616                            if let Err(e) = &result {
1617                                if e.code() == ErrorCode::NoSpace {
1618                                    return Ok::<_, Error>(false);
1619                                }
1620                            }
1621
1622                            if result? {
1623                                *empty = false;
1624                            }
1625                        }
1626
1627                        Ok(true)
1628                    })
1629                })?;
1630
1631                if finished {
1632                    break;
1633                }
1634
1635                debug!("<<< No TX space, chunking >>>");
1636                if !self
1637                    .send(ReportDataChunkState::ChunkingEvents, false, wb)
1638                    .await?
1639                {
1640                    return Ok(false);
1641                }
1642            }
1643
1644            wb.end_container()?;
1645        }
1646
1647        Ok(true)
1648    }
1649
1650    /// Send the items of an array attribute one by one, until the end of the array is reached.
1651    ///
1652    /// The data is potentially sent in multiple chunks if it cannot fit into a single Matter message.
1653    ///
1654    /// Arguments:
1655    /// - `attr` - the array attribute to send the items of
1656    /// - `wb` - the buffer to use while sending the items
1657    async fn send_array_items(
1658        &mut self,
1659        attr: &AttrDetails,
1660        wb: &mut WriteBuf<'_>,
1661    ) -> Result<bool, Error> {
1662        let mut attr = attr.clone();
1663
1664        // First generate an empty array
1665        let mut list_index = None;
1666        attr.list_chunked = true;
1667        attr.list_index = Some(Nullable::new(list_index));
1668
1669        loop {
1670            let pos = wb.get_tail();
1671
1672            let result = self.invoker.read(&attr, &mut *wb).await;
1673
1674            if result.is_err() {
1675                // If we got an error, we rewind to the position before the read
1676                // and handle it accordingly
1677                wb.rewind_to(pos);
1678            }
1679
1680            match result {
1681                Ok(()) => {
1682                    // The empty array payload was sent
1683                    // Now iterate over the array and send each item one by one as separate payload
1684
1685                    let new_list_index = if let Some(list_index) = list_index {
1686                        list_index + 1
1687                    } else {
1688                        0
1689                    };
1690
1691                    list_index = Some(new_list_index);
1692                    attr.list_index = Some(Nullable::some(new_list_index));
1693                }
1694                Err(err) if err.code() == ErrorCode::NoSpace => {
1695                    debug!("<<< No TX space, chunking >>>");
1696                    if !self
1697                        .send(ReportDataChunkState::ChunkingAttributes, false, wb)
1698                        .await?
1699                    {
1700                        return Ok(false);
1701                    }
1702                }
1703                Err(err) if err.code() == ErrorCode::ConstraintError => break, // Got to the end of the array
1704                Err(err) => Err(err)?,
1705            }
1706        }
1707
1708        Ok(true)
1709    }
1710
1711    /// Send the reply to the peer, potentially opening another reply.
1712    ///
1713    /// Arguments:
1714    /// - `state`: tracks chunking state - are we just sending a chunk packet or are we done and wrapping up?
1715    /// - `suppress_last_resp`: whether to suppress the response from the peer, this is ignored if state is != Done
1716    /// - `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`
1717    async fn send(
1718        &mut self,
1719        state: ReportDataChunkState,
1720        suppress_last_resp: bool,
1721        wb: &mut WriteBuf<'_>,
1722    ) -> Result<bool, Error> {
1723        self.end_reply(state, suppress_last_resp, wb)?;
1724
1725        self.invoker
1726            .exchange()
1727            .send(OpCode::ReportData, wb.as_slice())
1728            .await?;
1729
1730        let cont = match state {
1731            ReportDataChunkState::ChunkingAttributes => {
1732                let cont = self.recv_status_success().await?;
1733                self.start_reply(wb)?;
1734                wb.start_array(&TLVTag::Context(ReportDataRespTag::AttributeReports as u8))?;
1735                cont
1736            }
1737            ReportDataChunkState::ChunkingEvents => {
1738                let cont = self.recv_status_success().await?;
1739                self.start_reply(wb)?;
1740                wb.start_array(&TLVTag::Context(ReportDataRespTag::EventReports as u8))?;
1741                cont
1742            }
1743            ReportDataChunkState::Done => {
1744                if !suppress_last_resp {
1745                    self.recv_status_success().await?
1746                } else {
1747                    false
1748                }
1749            }
1750        };
1751
1752        Ok(cont)
1753    }
1754
1755    /// Receive a status response from the peer
1756    ///
1757    /// If the response is not a status response, the method will fail with an `Invalid` error.
1758    ///
1759    /// Return `Ok(true)` if the response is a success response, `Ok(false)` if the response is not a success response.
1760    async fn recv_status_success(&mut self) -> Result<bool, Error> {
1761        let rx = self.invoker.exchange().recv().await?;
1762        let opcode = rx.meta().proto_opcode;
1763
1764        if opcode != OpCode::StatusResponse as u8 {
1765            warn!(
1766                "Got opcode {:02x}, while expecting status code {:02x}",
1767                opcode,
1768                OpCode::StatusResponse as u8
1769            );
1770
1771            return Err(ErrorCode::Invalid.into());
1772        }
1773
1774        let resp = StatusResp::from_tlv(&get_root_node_struct(rx.payload())?)?;
1775
1776        if resp.status == IMStatusCode::Success {
1777            Ok(true)
1778        } else {
1779            warn!(
1780                "Got status response {:?}, aborting interaction",
1781                resp.status
1782            );
1783
1784            drop(rx);
1785
1786            self.invoker.exchange().acknowledge().await?;
1787
1788            Ok(false)
1789        }
1790    }
1791
1792    /// Start a reply by initializing the `WriteBuf` and writing the initial TLVs.
1793    fn start_reply(&self, wb: &mut WriteBuf<'_>) -> Result<(), Error> {
1794        wb.reset();
1795        wb.shrink(Self::LONG_READS_TLV_RESERVE_SIZE)?;
1796
1797        wb.start_struct(&TLVTag::Anonymous)?;
1798
1799        if let Some(subscription_id) = self.subscription_id {
1800            assert!(matches!(
1801                self.req,
1802                ReportDataReq::Subscribe(_) | ReportDataReq::SubscribeReport(_)
1803            ));
1804            wb.u32(
1805                &TLVTag::Context(ReportDataRespTag::SubscriptionId as u8),
1806                subscription_id,
1807            )?;
1808        } else {
1809            assert!(matches!(self.req, ReportDataReq::Read(_)));
1810        }
1811
1812        Ok(())
1813    }
1814
1815    /// End a reply by writing the closing TLVs and potentially indicating that there are more chunks to send.
1816    fn end_reply(
1817        &self,
1818        state: ReportDataChunkState,
1819        suppress_resp: bool,
1820        wb: &mut WriteBuf<'_>,
1821    ) -> Result<(), Error> {
1822        wb.expand(Self::LONG_READS_TLV_RESERVE_SIZE)?;
1823
1824        match state {
1825            ReportDataChunkState::ChunkingAttributes | ReportDataChunkState::ChunkingEvents => {
1826                wb.end_container()?;
1827                wb.bool(
1828                    &TLVTag::Context(ReportDataRespTag::MoreChunkedMsgs as u8),
1829                    true,
1830                )?;
1831            }
1832            ReportDataChunkState::Done => {
1833                if suppress_resp {
1834                    wb.bool(
1835                        &TLVTag::Context(ReportDataRespTag::SupressResponse as u8),
1836                        true,
1837                    )?;
1838                }
1839            }
1840        };
1841
1842        // InteractionModelRevision is mandatory in all IM messages from
1843        // Matter 1.0 onward (TLV tag 0xFF). matter.js validates this
1844        // strictly and refuses to commission devices that omit it; the
1845        // reference chip-tool happens to tolerate the absence.
1846        wb.u8(
1847            &TLVTag::Context(crate::im::encoding::IM_REVISION_TAG),
1848            IM_REVISION,
1849        )?;
1850
1851        wb.end_container()?;
1852
1853        Ok(())
1854    }
1855}
1856
1857/// Used to avoid duplicating the chunking logic for events and attributes; they both
1858/// share the same write path when the current packet fills up, and use this to determine
1859/// which field they should be setting up an array in for more output in the next packet
1860#[derive(Clone, Copy)]
1861enum ReportDataChunkState {
1862    ChunkingAttributes,
1863    ChunkingEvents,
1864    Done,
1865}
1866
1867/// This type responds to a `WriteReq` by invoking the
1868/// corresponding handlers for each write attribute in the request.
1869///
1870/// The responser assumes that all response data can fit in a single Matter message,
1871/// which is a fair assumption and as per the Matter spec, in that the response of a
1872/// write request is always shorter than the write request itself, so given that the
1873/// write request fits in a single Matter message, the write reponse should as well.
1874///
1875/// With that said, the write request might itself be just one out of many chunks that
1876/// the other peers is sending, but processing all of those chunks is not done here,
1877/// but is rather - a responsibility of the caller who should call in a loop `WriteResponder::respond`
1878/// for all the chunks of the write request, until the `WriteReq::more_chunks()` returns `false`.
1879struct WriteResponder<'a, 'b, 'c, C> {
1880    req: &'a WriteReq<'a>,
1881    invoker: HandlerInvoker<'b, 'c, C>,
1882}
1883
1884impl<'a, 'b, 'c, C> WriteResponder<'a, 'b, 'c, C>
1885where
1886    C: HandlerContext,
1887{
1888    /// Create a new `WriteResponder`.
1889    const fn new(req: &'a WriteReq<'a>, invoker: HandlerInvoker<'b, 'c, C>) -> Self {
1890        Self { req, invoker }
1891    }
1892
1893    /// Respond to the write request by processing each write attribute in the request
1894    /// and sending a response back.
1895    async fn respond<M>(
1896        &mut self,
1897        wb: &mut WriteBuf<'_>,
1898        metadata: M,
1899        suppress_resp: bool,
1900    ) -> Result<(), Error>
1901    where
1902        M: Metadata,
1903    {
1904        let accessor = self.invoker.exchange().accessor()?;
1905
1906        wb.reset();
1907
1908        wb.start_struct(&TLVTag::Anonymous)?;
1909        wb.start_array(&TLVTag::Context(WriteRespTag::WriteResponses as u8))?;
1910
1911        for item in expand_write(metadata, self.req, &accessor)? {
1912            self.invoker.process_write(&item?, &mut *wb).await?;
1913        }
1914
1915        if suppress_resp {
1916            return Ok(());
1917        }
1918
1919        wb.end_container()?;
1920        // Mandatory `interactionModelRevision` (tag 0xFF); see note in
1921        // the ReportData emitter above.
1922        wb.u8(
1923            &TLVTag::Context(crate::im::encoding::IM_REVISION_TAG),
1924            IM_REVISION,
1925        )?;
1926        wb.end_container()?;
1927
1928        self.invoker
1929            .exchange()
1930            .send(OpCode::WriteResponse, wb.as_slice())
1931            .await
1932    }
1933}
1934
1935/// This type responds to an `InvRequest` by invoking the
1936/// corresponding handlers for each command in the invoke request.
1937///
1938/// NOTE: In future, this responder should support chunking in that
1939/// if the reply to all the commands in the invoke request is too large to fit
1940/// into a single Matter message, it should send the response in multiple chunks.
1941///
1942/// The simplest strategy for chunking would be to simply - and unconditionally - send each individual
1943/// command response in a separate Matter message, i.e. if the invoke request contains 3 commands,
1944/// the responder will send 3 Matter messages, each containing a single command response.
1945struct InvokeResponder<'a, 'b, 'c, C> {
1946    req: &'a InvReq<'a>,
1947    invoker: HandlerInvoker<'b, 'c, C>,
1948}
1949
1950impl<'a, 'b, 'c, C> InvokeResponder<'a, 'b, 'c, C>
1951where
1952    C: HandlerContext,
1953{
1954    /// Create a new `InvokeResponder`.
1955    const fn new(req: &'a InvReq<'a>, invoker: HandlerInvoker<'b, 'c, C>) -> Self {
1956        Self { req, invoker }
1957    }
1958
1959    /// Respond to the invoke request by processing each command in the request
1960    /// and sending one or more reponses back.
1961    async fn respond<M>(
1962        &mut self,
1963        wb: &mut WriteBuf<'_>,
1964        metadata: M,
1965        suppress_resp: bool,
1966    ) -> Result<(), Error>
1967    where
1968        M: Metadata,
1969    {
1970        wb.reset();
1971
1972        wb.start_struct(&TLVTag::Anonymous)?;
1973
1974        // Suppress Response -> TODO: Need to revisit this for cases where we send a command back
1975        wb.bool(
1976            &TLVTag::Context(InvRespTag::SupressResponse as u8),
1977            suppress_resp,
1978        )?;
1979
1980        let has_requests = self.req.inv_requests()?.is_some();
1981
1982        if has_requests {
1983            wb.start_array(&TLVTag::Context(InvRespTag::InvokeResponses as u8))?;
1984        }
1985
1986        let accessor = self.invoker.exchange().accessor()?;
1987
1988        for item in expand_invoke(metadata, self.req, &accessor)? {
1989            self.invoker.process_invoke(&item?, &mut *wb).await?;
1990        }
1991
1992        if suppress_resp {
1993            return Ok(());
1994        }
1995
1996        if has_requests {
1997            wb.end_container()?;
1998        }
1999
2000        // Mandatory `interactionModelRevision` (tag 0xFF) at the end of
2001        // every IM message — see the matching note in the ReportData
2002        // emitter above.
2003        wb.u8(
2004            &TLVTag::Context(crate::im::encoding::IM_REVISION_TAG),
2005            IM_REVISION,
2006        )?;
2007        wb.end_container()?;
2008
2009        self.invoker
2010            .exchange()
2011            .send(OpCode::InvokeResponse, wb.as_slice())
2012            .await?;
2013
2014        Ok(())
2015    }
2016}