1#![forbid(unsafe_code)]
5
6use crate::error::ImError;
7use matter_codec::{Element, Tag, TlvReader, Value};
8
9#[derive(Copy, Clone, Debug, PartialEq, Eq)]
14pub struct CommandPath {
15 pub endpoint: u16,
17 pub cluster: u32,
19 pub command: u32,
21}
22
23#[derive(Copy, Clone, Debug, PartialEq, Eq)]
30pub struct AttributePath {
31 pub endpoint: u16,
33 pub cluster: u32,
35 pub attribute: u32,
37}
38
39#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
50#[non_exhaustive]
51pub struct ReadPath {
52 pub endpoint: Option<u16>,
54 pub cluster: Option<u32>,
56 pub attribute: Option<u32>,
58}
59
60impl ReadPath {
61 #[must_use]
65 pub fn new(endpoint: Option<u16>, cluster: Option<u32>, attribute: Option<u32>) -> Self {
66 Self {
67 endpoint,
68 cluster,
69 attribute,
70 }
71 }
72
73 #[must_use]
75 pub fn concrete(endpoint: u16, cluster: u32, attribute: u32) -> Self {
76 Self {
77 endpoint: Some(endpoint),
78 cluster: Some(cluster),
79 attribute: Some(attribute),
80 }
81 }
82
83 #[must_use]
85 pub fn cluster(endpoint: u16, cluster: u32) -> Self {
86 Self {
87 endpoint: Some(endpoint),
88 cluster: Some(cluster),
89 attribute: None,
90 }
91 }
92
93 #[must_use]
95 pub fn all() -> Self {
96 Self {
97 endpoint: None,
98 cluster: None,
99 attribute: None,
100 }
101 }
102}
103
104impl From<AttributePath> for ReadPath {
105 fn from(p: AttributePath) -> Self {
106 Self {
107 endpoint: Some(p.endpoint),
108 cluster: Some(p.cluster),
109 attribute: Some(p.attribute),
110 }
111 }
112}
113
114pub(crate) fn attribute_path_from_reader(
120 r: &mut TlvReader<'_>,
121) -> Result<(AttributePath, bool), ImError> {
122 let mut endpoint = None;
123 let mut cluster = None;
124 let mut attribute = None;
125 let mut append = false;
126 loop {
127 match r.next()? {
128 None => {
129 return Err(ImError::Codec(matter_codec::Error::UnclosedContainer));
130 }
131 Some(Element::ContainerEnd) => break,
132 Some(Element::Scalar {
133 tag: Tag::Context(2),
134 value: Value::Uint(n),
135 }) => {
136 endpoint =
137 Some(u16::try_from(n).map_err(|_| {
138 ImError::UnexpectedValue("AttributePath.endpoint exceeds u16")
139 })?);
140 }
141 Some(Element::Scalar {
142 tag: Tag::Context(3),
143 value: Value::Uint(n),
144 }) => {
145 cluster =
146 Some(u32::try_from(n).map_err(|_| {
147 ImError::UnexpectedValue("AttributePath.cluster exceeds u32")
148 })?);
149 }
150 Some(Element::Scalar {
151 tag: Tag::Context(4),
152 value: Value::Uint(n),
153 }) => {
154 attribute = Some(u32::try_from(n).map_err(|_| {
155 ImError::UnexpectedValue("AttributePath.attribute exceeds u32")
156 })?);
157 }
158 Some(Element::Scalar {
159 tag: Tag::Context(5),
160 value: Value::Null,
161 }) => append = true,
162 Some(Element::ContainerStart { .. }) => crate::skip_container(r)?,
163 Some(_) => {}
164 }
165 }
166 Ok((
167 AttributePath {
168 endpoint: endpoint.ok_or(ImError::MissingField("AttributePath.endpoint"))?,
169 cluster: cluster.ok_or(ImError::MissingField("AttributePath.cluster"))?,
170 attribute: attribute.ok_or(ImError::MissingField("AttributePath.attribute"))?,
171 },
172 append,
173 ))
174}
175
176#[cfg(test)]
177mod tests {
178 #![allow(clippy::unwrap_used)] use super::*;
180 use matter_codec::TlvWriter;
183
184 fn parse(build: impl FnOnce(&mut TlvWriter<'_>)) -> Result<(AttributePath, bool), ImError> {
186 let mut buf = Vec::new();
187 let mut w = TlvWriter::new(&mut buf);
188 w.start_list(Tag::Anonymous).unwrap();
189 build(&mut w);
190 w.end_container().unwrap();
191 let mut r = TlvReader::new(&buf);
192 assert!(matches!(
193 r.next().unwrap(),
194 Some(Element::ContainerStart { .. })
195 ));
196 attribute_path_from_reader(&mut r)
197 }
198
199 #[test]
200 fn streaming_path_parse_matches_member_semantics() {
201 let (p, append) = parse(|w| {
203 w.put_uint(Tag::Context(2), 1).unwrap();
204 w.put_uint(Tag::Context(3), 0x0006).unwrap();
205 w.put_uint(Tag::Context(4), 0xFFFC).unwrap();
206 w.put_null(Tag::Context(5)).unwrap();
207 })
208 .unwrap();
209 assert_eq!((p.endpoint, p.cluster, p.attribute), (1, 0x0006, 0xFFFC));
210 assert!(append);
211
212 let (p, _) = parse(|w| {
214 w.put_uint(Tag::Context(2), 1).unwrap();
215 w.put_uint(Tag::Context(2), 2).unwrap();
216 w.put_uint(Tag::Context(3), 6).unwrap();
217 w.put_uint(Tag::Context(4), 0).unwrap();
218 })
219 .unwrap();
220 assert_eq!(p.endpoint, 2);
221
222 let (p, append) = parse(|w| {
224 w.put_uint(Tag::Context(2), 1).unwrap();
225 w.start_structure(Tag::Context(9)).unwrap();
226 w.put_uint(Tag::Context(0), 7).unwrap();
227 w.end_container().unwrap();
228 w.put_uint(Tag::Context(3), 6).unwrap();
229 w.put_uint(Tag::Context(4), 0).unwrap();
230 })
231 .unwrap();
232 assert_eq!(p.cluster, 6);
233 assert!(!append);
234 }
235
236 #[test]
237 fn streaming_path_parse_range_and_missing_errors() {
238 assert!(matches!(
240 parse(|w| {
241 w.put_uint(Tag::Context(2), 0x0001_0000).unwrap();
242 w.put_uint(Tag::Context(3), 6).unwrap();
243 w.put_uint(Tag::Context(4), 0).unwrap();
244 }),
245 Err(ImError::UnexpectedValue(_))
246 ));
247 assert!(matches!(
249 parse(|w| {
250 w.put_uint(Tag::Context(2), 0).unwrap();
251 w.put_uint(Tag::Context(3), 6).unwrap();
252 }),
253 Err(ImError::MissingField("AttributePath.attribute"))
254 ));
255 }
256
257 #[test]
258 fn truncated_path_body_errors_unclosed_container() {
259 let mut buf = Vec::new();
261 let mut w = TlvWriter::new(&mut buf);
262 w.start_list(Tag::Anonymous).unwrap();
263 w.put_uint(Tag::Context(2), 1).unwrap();
264 w.put_uint(Tag::Context(3), 6).unwrap();
265 w.put_uint(Tag::Context(4), 0).unwrap();
266 w.end_container().unwrap();
267 buf.pop(); let mut r = TlvReader::new(&buf);
269 assert!(matches!(
270 r.next().unwrap(),
271 Some(Element::ContainerStart { .. })
272 ));
273 assert!(matches!(
274 attribute_path_from_reader(&mut r),
275 Err(ImError::Codec(matter_codec::Error::UnclosedContainer))
276 ));
277 }
278
279 #[test]
280 fn non_null_list_index_leaves_append_false() {
281 let (path, append) = parse(|w| {
282 w.put_uint(Tag::Context(2), 1).unwrap();
283 w.put_uint(Tag::Context(3), 6).unwrap();
284 w.put_uint(Tag::Context(4), 0).unwrap();
285 w.put_uint(Tag::Context(5), 3).unwrap(); })
287 .unwrap();
288 assert_eq!((path.endpoint, path.cluster, path.attribute), (1, 6, 0));
289 assert!(!append, "only ListIndex=null signals append");
290 }
291
292 #[test]
293 fn wrong_typed_member_is_ignored() {
294 let (path, append) = parse(|w| {
296 w.put_uint(Tag::Context(2), 1).unwrap();
297 w.put_uint(Tag::Context(3), 6).unwrap();
298 w.put_uint(Tag::Context(4), 0).unwrap();
299 w.put_utf8(Tag::Context(2), "nope").unwrap(); })
301 .unwrap();
302 assert_eq!((path.endpoint, path.cluster, path.attribute), (1, 6, 0));
303 assert!(!append);
304 }
305}