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}