Skip to main content

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}