rs_matter/im/encoding/attr/write.rs
1/*
2 *
3 * Copyright (c) 2025-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
18use core::fmt;
19
20use crate::error::{Error, ErrorCode};
21use crate::im::encoding::{AttrId, ClusterId, EndptId};
22use crate::im::{AttrData, AttrStatus};
23use crate::tlv::{FromTLV, TLVArray, TLVElement, ToTLV};
24
25/// A request to write attributes to a Matter device.
26///
27/// Corresponds to the `WriteRequestMessage` TLV structure in the Interaction Model.
28#[derive(Clone, PartialEq, Eq, Hash, FromTLV, ToTLV)]
29#[tlvargs(lifetime = "'a")]
30pub struct WriteReq<'a>(TLVElement<'a>);
31
32impl<'a> WriteReq<'a> {
33 /// Create a new `WriteReq` from a `TLVElement`.
34 pub const fn new(element: TLVElement<'a>) -> Self {
35 Self(element)
36 }
37
38 /// Return `Ok(true)` if this write request should suppress the response.
39 pub fn supress_response(&self) -> Result<bool, Error> {
40 self.0
41 .r#struct()?
42 .find_ctx(0)?
43 .non_empty()
44 .map(|t| t.bool())
45 .unwrap_or(Ok(false))
46 }
47
48 /// Return `Ok(true)` if this write request is a timed request.
49 pub fn timed_request(&self) -> Result<bool, Error> {
50 self.0
51 .r#struct()?
52 .find_ctx(1)?
53 .non_empty()
54 .map(|t| t.bool())
55 .unwrap_or(Ok(false))
56 }
57
58 /// Return the attribute data to write in this write request.
59 pub fn write_requests(&self) -> Result<TLVArray<'a, AttrData<'_>>, Error> {
60 TLVArray::new(self.0.r#struct()?.find_ctx(2)?)
61 }
62
63 /// Return `Ok(true)` if this write request has more chunks
64 /// (i.e. more write requests coming after this one, which are for the same exchange/transaction).
65 pub fn more_chunks(&self) -> Result<bool, Error> {
66 self.0
67 .r#struct()?
68 .find_ctx(3)?
69 .non_empty()
70 .map(|t| t.bool())
71 .unwrap_or(Ok(false))
72 }
73}
74
75impl fmt::Debug for WriteReq<'_> {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 f.debug_struct("WriteReqRef")
78 .field("supress_response", &self.supress_response())
79 .field("timed_request", &self.timed_request())
80 .field("write_requests", &self.write_requests())
81 .field("more_chunks", &self.more_chunks())
82 .finish()
83 }
84}
85
86#[cfg(feature = "defmt")]
87impl defmt::Format for WriteReq<'_> {
88 fn format(&self, f: defmt::Formatter<'_>) {
89 defmt::write!(f,
90 "WriteReqRef {{\n supress_response: {:?},\n timed_request: {:?},\n write_requests: {:?},\n more_chunks: {:?},\n}}",
91 self.supress_response(),
92 self.timed_request(),
93 self.write_requests(),
94 self.more_chunks(),
95 )
96 }
97}
98
99/// Tags corresponding to the fields in the `WriteReq` TLV structure.
100///
101/// Used when there is a need to perform low-level TLV serde on
102/// `WriteReq` structures.
103#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
104#[cfg_attr(feature = "defmt", derive(defmt::Format))]
105#[repr(u8)]
106pub enum WriteReqTag {
107 SuppressResponse = 0,
108 TimedRequest = 1,
109 WriteRequests = 2,
110 MoreChunked = 3,
111}
112
113/// A response to a write request.
114///
115/// Corresponds to the `WriteResponseMessage` TLV structure in the Interaction Model.
116#[derive(ToTLV, FromTLV, Debug)]
117#[cfg_attr(feature = "defmt", derive(defmt::Format))]
118#[tlvargs(lifetime = "'a")]
119pub struct WriteResp<'a> {
120 pub write_responses: TLVArray<'a, AttrStatus>,
121 /// `interactionModelRevision` (TLV context tag `0xFF`). Mandatory in
122 /// every IM message we send; modelled as `Option<u8>` so we tolerate
123 /// peers that omit it (the C++ SDK is tolerant in practice).
124 #[tagval(crate::im::encoding::IM_REVISION_TAG)]
125 pub interaction_model_revision: Option<u8>,
126}
127
128impl<'a> WriteResp<'a> {
129 /// Iterate the entries in `write_responses` whose path matches
130 /// the given `(cluster, attr)` pair, in `(endpoint, result)` form.
131 ///
132 /// - **`Ok(())`** — the per-attribute write succeeded
133 /// (`IMStatusCode::Success`).
134 /// - **`Err(_)`** — non-`Success` status; the `IMStatusCode`
135 /// becomes an [`Error`], covering access-check and
136 /// `Unsupported{Endpoint,Cluster,Attribute}` failures uniformly.
137 /// - Entries with non-matching cluster/attr are skipped.
138 /// - Entries with an absent endpoint in the path are skipped.
139 ///
140 /// Wildcard write requests (path missing endpoint, cluster, or
141 /// attr) produce one status entry per expanded path; the
142 /// iterator yields them in wire order.
143 pub fn statuses(
144 &self,
145 cluster: ClusterId,
146 attr: AttrId,
147 ) -> impl Iterator<Item = (EndptId, Result<(), Error>)> + '_ {
148 self.write_responses
149 .iter()
150 .filter_map(move |status| filter_attr_status(status.ok()?, cluster, attr))
151 }
152}
153
154/// Helper for [`WriteResp::statuses`] — extracts `(endpoint, Result<(),
155/// Error>)` from a single `AttrStatus` if it matches the requested
156/// `(cluster, attr)` filter.
157fn filter_attr_status(
158 s: AttrStatus,
159 cluster: ClusterId,
160 attr: AttrId,
161) -> Option<(EndptId, Result<(), Error>)> {
162 if s.path.cluster != Some(cluster) || s.path.attr != Some(attr) {
163 return None;
164 }
165 let endpoint = s.path.endpoint?;
166 let result = if s.status.status == crate::im::IMStatusCode::Success {
167 Ok(())
168 } else {
169 let err: Error = s
170 .status
171 .status
172 .to_error_code()
173 .unwrap_or(ErrorCode::Failure)
174 .into();
175 Err(err)
176 };
177 Some((endpoint, result))
178}
179
180/// Create a new `WriteResp` from a `TLVElement`.
181///
182/// Used when there is a need to perform low-level TLV serde on
183/// `WriteResp` structures.
184#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
185#[cfg_attr(feature = "defmt", derive(defmt::Format))]
186#[repr(u8)]
187pub enum WriteRespTag {
188 WriteResponses = 0,
189}