Skip to main content

rs_matter/im/
invoker.rs

1/*
2 *
3 *    Copyright (c) 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 engine's per-item handler invoker.
19//!
20//! [`HandlerInvoker`] drives a single attribute read/write or command invoke
21//! against the cluster handlers: it builds the handler-facing context and reply
22//! (the concrete `dm` `*Instance` realizations), calls the handler, and encodes
23//! the wire-format outcome (`AttrResp`/`CmdResp`/status), handling `NoSpace`
24//! rewind and subscription change-notification. Driven by the responders in
25//! [`crate::im`].
26
27use crate::dm::{
28    AsyncHandler, AttrDetails, CmdDetails, HandlerContext, InvokeContextInstance,
29    InvokeReplyInstance, ReadContextInstance, ReadReplyInstance, WriteContextInstance,
30};
31use crate::error::{Error, ErrorCode};
32use crate::im::encoding::{AttrResp, AttrStatus, CmdResp, CmdStatus, IMStatusCode};
33use crate::tlv::{TLVElement, TLVWrite, TagType, ToTLV};
34use crate::transport::exchange::Exchange;
35use crate::utils::storage::WriteBuf;
36
37pub struct HandlerInvoker<'a, 'b, C> {
38    exchange: &'b mut Exchange<'a>,
39    context: C,
40}
41
42impl<'a, 'b, C> HandlerInvoker<'a, 'b, C>
43where
44    C: HandlerContext,
45{
46    pub const fn new(exchange: &'b mut Exchange<'a>, context: C) -> Self {
47        Self { exchange, context }
48    }
49
50    pub fn exchange(&mut self) -> &mut Exchange<'a> {
51        self.exchange
52    }
53
54    pub async fn process_read(
55        &mut self,
56        item: &Result<AttrDetails, AttrStatus>,
57        tw: &mut WriteBuf<'_>,
58    ) -> Result<(), Error> {
59        let tail = tw.get_tail();
60
61        let result = self.do_process_read(item, &mut *tw).await;
62
63        if result.is_err() {
64            // If there was an error, rewind to the tail so we don't write any data.
65            tw.rewind_to(tail);
66        }
67
68        result
69    }
70
71    async fn do_process_read(
72        &mut self,
73        item: &Result<AttrDetails, AttrStatus>,
74        tw: &mut WriteBuf<'_>,
75    ) -> Result<(), Error> {
76        let result = match item {
77            Ok(attr) => {
78                let pos = tw.get_tail();
79
80                let result = self.read(attr, &mut *tw).await;
81
82                match result {
83                    Ok(()) => Ok(None),
84                    Err(e) if e.code() != ErrorCode::NoSpace => {
85                        error!("Error reading attribute: {}", e);
86
87                        tw.rewind_to(pos);
88
89                        Ok(attr.status(e.into()))
90                    }
91                    Err(e) => Err(e),
92                }
93            }
94            Err(status) => {
95                error!("Error processing attribute read: {:?}", status);
96                Ok(Some(status.clone()))
97            }
98        };
99
100        match result {
101            Ok(Some(status)) => AttrResp::Status(status).to_tlv(&TagType::Anonymous, tw),
102            Ok(None) => Ok(()),
103            Err(err) => Err(err),
104        }
105    }
106
107    pub async fn read(&mut self, attr: &AttrDetails, tw: &mut WriteBuf<'_>) -> Result<(), Error> {
108        self.context
109            .handler()
110            .read(
111                ReadContextInstance::new(self.exchange, &self.context, attr),
112                ReadReplyInstance::new(attr, tw),
113            )
114            .await
115    }
116
117    pub async fn process_write(
118        &mut self,
119        item: &Result<(AttrDetails, TLVElement<'_>), AttrStatus>,
120        tw: &mut WriteBuf<'_>,
121    ) -> Result<(), Error> {
122        let tail = tw.get_tail();
123
124        let result = self.do_process_write(item, &mut *tw).await;
125
126        if result.is_err() {
127            // If there was an error, rewind to the tail so we don't write any data.
128            tw.rewind_to(tail);
129        }
130
131        result
132    }
133
134    async fn do_process_write(
135        &mut self,
136        item: &Result<(AttrDetails, TLVElement<'_>), AttrStatus>,
137        tw: &mut WriteBuf<'_>,
138    ) -> Result<(), Error> {
139        let result = match item {
140            Ok((attr, data)) => {
141                let pos = tw.get_tail();
142
143                let result = self.write(attr, data).await;
144
145                match result {
146                    Ok(()) => {
147                        // A write that was accepted by the cluster handler
148                        // counts as an attribute change for subscription
149                        // reporting purposes. Notify generically here so that
150                        // cluster handlers do not each need to call
151                        // `notify_attr_changed` from every attribute setter.
152                        self.context.notify_attr_changed(
153                            attr.endpoint_id,
154                            attr.cluster_id,
155                            attr.attr_id,
156                        );
157                        Ok(attr.status(IMStatusCode::Success))
158                    }
159                    Err(err) if err.code() != ErrorCode::NoSpace => {
160                        error!("Error writing attribute: {}", err);
161
162                        tw.rewind_to(pos);
163
164                        Ok(attr.status(err.into()))
165                    }
166                    Err(err) => Err(err),
167                }
168            }
169            Err(status) => {
170                error!("Error processing attribute write: {:?}", status);
171                Ok(Some(status.clone()))
172            }
173        };
174
175        match result {
176            Ok(Some(status)) => status.to_tlv(&TagType::Anonymous, tw),
177            Ok(None) => Ok(()),
178            Err(err) => Err(err),
179        }
180    }
181
182    pub async fn write(&mut self, attr: &AttrDetails, data: &TLVElement<'_>) -> Result<(), Error> {
183        self.context
184            .handler()
185            .write(WriteContextInstance::new(
186                self.exchange,
187                &self.context,
188                attr,
189                data,
190            ))
191            .await
192    }
193
194    /// Process one expanded invoke item.
195    ///
196    /// Returns whether the item was successfully invoked on its handler
197    /// (used by the Groupcast testing mode to report group-invoke outcomes).
198    pub async fn process_invoke(
199        &mut self,
200        item: &Result<(CmdDetails, TLVElement<'_>), CmdStatus>,
201        tw: &mut WriteBuf<'_>,
202    ) -> Result<bool, Error> {
203        let tail = tw.get_tail();
204
205        let result = self.do_process_invoke(item, &mut *tw).await;
206
207        if result.is_err() {
208            // If there was an error, rewind to the tail so we don't write any data.
209            tw.rewind_to(tail);
210        }
211
212        result
213    }
214
215    async fn do_process_invoke(
216        &mut self,
217        item: &Result<(CmdDetails, TLVElement<'_>), CmdStatus>,
218        tw: &mut WriteBuf<'_>,
219    ) -> Result<bool, Error> {
220        let (result, invoked) = match item {
221            Ok((cmd, data)) => {
222                let pos = tw.get_tail();
223
224                let result = self.invoke(cmd, data, &mut *tw).await;
225
226                match result {
227                    Ok(()) => {
228                        if pos == tw.get_tail() {
229                            (Ok(cmd.status(IMStatusCode::Success)), true)
230                        } else {
231                            (Ok(None), true)
232                        }
233                    }
234                    Err(err) if err.code() != ErrorCode::NoSpace => {
235                        error!("Error invoking command: {}", err);
236
237                        tw.rewind_to(pos);
238
239                        (Ok(cmd.status(err.into())), false)
240                    }
241                    Err(err) => (Err(err), false),
242                }
243            }
244            Err(status) => {
245                error!("Error processing command: {:?}", status);
246                (Ok(Some(status.clone())), false)
247            }
248        };
249
250        match result {
251            Ok(Some(status)) => {
252                CmdResp::Status(status).to_tlv(&TagType::Anonymous, tw)?;
253                Ok(invoked)
254            }
255            Ok(None) => Ok(invoked),
256            Err(err) => Err(err),
257        }
258    }
259
260    pub async fn invoke(
261        &mut self,
262        cmd: &CmdDetails,
263        data: &TLVElement<'_>,
264        tw: &mut WriteBuf<'_>,
265    ) -> Result<(), Error> {
266        self.context
267            .handler()
268            .invoke(
269                InvokeContextInstance::new(self.exchange, &self.context, cmd, data),
270                InvokeReplyInstance::new(cmd, tw),
271            )
272            .await
273    }
274}