1#![forbid(unsafe_code)]
6
7use crate::invoke::{command_path_from_value, reencode_anonymous, write_command_path};
8use crate::path::CommandPath;
9use crate::status::ImStatus;
10use crate::{expect_message_struct, skip_container, IM_REVISION};
11use matter_codec::{ContainerKind, Element, Tag, TlvReader, TlvWriter, Value};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct InvokedCommand {
16 pub path: CommandPath,
18 pub fields_tlv: Vec<u8>,
21 pub command_ref: Option<u16>,
23}
24
25#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct ParsedInvokeRequest {
28 pub suppress_response: bool,
30 pub timed: bool,
32 pub commands: Vec<InvokedCommand>,
34}
35
36pub fn parse_invoke_request(bytes: &[u8]) -> Result<ParsedInvokeRequest, crate::ImError> {
44 let mut r = TlvReader::new(bytes);
45 expect_message_struct(&mut r)?;
46
47 let mut suppress_response = false;
48 let mut timed = false;
49 let mut commands = Vec::new();
50
51 loop {
52 match r.next()? {
53 None | Some(Element::ContainerEnd) => break,
54 Some(Element::Scalar {
55 tag: Tag::Context(0),
56 value: Value::Bool(b),
57 }) => suppress_response = b,
58 Some(Element::Scalar {
59 tag: Tag::Context(1),
60 value: Value::Bool(b),
61 }) => timed = b,
62 Some(Element::ContainerStart {
63 tag: Tag::Context(2),
64 kind: ContainerKind::Array,
65 }) => {
66 read_invoke_requests(&mut r, &mut commands)?;
67 }
68 Some(Element::ContainerStart { .. }) => skip_container(&mut r)?,
69 Some(_) => {}
70 }
71 }
72
73 Ok(ParsedInvokeRequest {
74 suppress_response,
75 timed,
76 commands,
77 })
78}
79
80fn read_invoke_requests(
83 r: &mut TlvReader<'_>,
84 out: &mut Vec<InvokedCommand>,
85) -> Result<(), crate::ImError> {
86 loop {
87 match r.next()? {
88 None | Some(Element::ContainerEnd) => return Ok(()),
89 Some(Element::ContainerStart {
90 kind: ContainerKind::Structure,
91 ..
92 }) => out.push(read_command_data(r)?),
93 Some(Element::ContainerStart { .. }) => skip_container(r)?,
94 Some(_) => {}
95 }
96 }
97}
98
99fn read_command_data(r: &mut TlvReader<'_>) -> Result<InvokedCommand, crate::ImError> {
102 let mut path = None;
103 let mut fields_tlv = None;
104 let mut command_ref = None;
105
106 loop {
107 match r.next()? {
108 None | Some(Element::ContainerEnd) => break,
109 Some(Element::ContainerStart {
110 tag: Tag::Context(0),
111 kind: ContainerKind::List,
112 }) => {
113 let members = read_list_members(r)?;
114 path = Some(command_path_from_value(&members)?);
115 }
116 Some(Element::ContainerStart {
117 tag: Tag::Context(1),
118 kind: ContainerKind::Structure,
119 }) => {
120 let value = read_struct_value(r)?;
122 fields_tlv = Some(reencode_anonymous(&value));
123 }
124 Some(Element::Scalar {
125 tag: Tag::Context(2),
126 value: Value::Uint(n),
127 }) => {
128 command_ref = u16::try_from(n).ok();
129 }
130 Some(Element::ContainerStart { .. }) => skip_container(r)?,
131 Some(_) => {}
132 }
133 }
134
135 Ok(InvokedCommand {
136 path: path.ok_or(crate::ImError::MissingField("CommandDataIB.CommandPath"))?,
137 fields_tlv: fields_tlv
138 .ok_or(crate::ImError::MissingField("CommandDataIB.CommandFields"))?,
139 command_ref,
140 })
141}
142
143fn read_list_members(r: &mut TlvReader<'_>) -> Result<Vec<(Tag, Value)>, crate::ImError> {
145 match crate::read_container_value(r, ContainerKind::List)? {
146 Value::List(members) => Ok(members),
147 _ => Err(crate::ImError::UnexpectedValue("expected a list")),
148 }
149}
150
151fn read_struct_value(r: &mut TlvReader<'_>) -> Result<Value, crate::ImError> {
154 crate::read_container_value(r, ContainerKind::Structure)
155}
156
157#[must_use]
169#[allow(clippy::expect_used)] pub fn build_invoke_response_command(path: CommandPath, response_fields_tlv: &[u8]) -> Vec<u8> {
171 let mut buf = Vec::new();
172 let mut w = TlvWriter::new(&mut buf);
173 w.start_structure(Tag::Anonymous)
174 .expect("infallible: vec writer");
175 w.put_bool(Tag::Context(0), false)
176 .expect("infallible: vec writer"); w.start_array(Tag::Context(1))
178 .expect("infallible: vec writer"); {
180 w.start_structure(Tag::Anonymous)
181 .expect("infallible: vec writer"); w.start_structure(Tag::Context(0))
183 .expect("infallible: vec writer"); write_command_path(&mut w, Tag::Context(0), path);
185 w.put_preencoded(Tag::Context(1), response_fields_tlv)
186 .expect("infallible: caller passes a valid anonymous-tagged struct");
187 w.end_container().expect("infallible: vec writer"); w.end_container().expect("infallible: vec writer"); }
190 w.end_container().expect("infallible: vec writer"); w.put_uint(Tag::Context(0xFF), u64::from(IM_REVISION))
192 .expect("infallible: vec writer");
193 w.end_container().expect("infallible: vec writer"); buf
195}
196
197#[must_use]
201#[allow(clippy::expect_used, clippy::missing_panics_doc)] pub fn build_invoke_response_status(path: CommandPath, status: ImStatus) -> Vec<u8> {
203 let mut buf = Vec::new();
204 let mut w = TlvWriter::new(&mut buf);
205 w.start_structure(Tag::Anonymous)
206 .expect("infallible: vec writer");
207 w.put_bool(Tag::Context(0), false)
208 .expect("infallible: vec writer"); w.start_array(Tag::Context(1))
210 .expect("infallible: vec writer"); {
212 w.start_structure(Tag::Anonymous)
213 .expect("infallible: vec writer"); w.start_structure(Tag::Context(1))
215 .expect("infallible: vec writer"); write_command_path(&mut w, Tag::Context(0), path);
217 w.start_structure(Tag::Context(1))
218 .expect("infallible: vec writer"); w.put_uint(Tag::Context(0), u64::from(status.to_u8()))
220 .expect("infallible: vec writer"); w.end_container().expect("infallible: vec writer"); w.end_container().expect("infallible: vec writer"); w.end_container().expect("infallible: vec writer"); }
225 w.end_container().expect("infallible: vec writer"); w.put_uint(Tag::Context(0xFF), u64::from(IM_REVISION))
227 .expect("infallible: vec writer");
228 w.end_container().expect("infallible: vec writer"); buf
230}
231
232#[cfg(test)]
233mod tests {
234 #![allow(clippy::unwrap_used, clippy::expect_used)] use super::*;
236 use crate::invoke::{build_invoke_request, parse_invoke_response, InvokeResponse};
237
238 fn anon_struct_ctx0(value: u64) -> Vec<u8> {
239 let mut b = Vec::new();
240 let mut w = TlvWriter::new(&mut b);
241 w.start_structure(Tag::Anonymous).unwrap();
242 w.put_uint(Tag::Context(0), value).unwrap();
243 w.end_container().unwrap();
244 b
245 }
246
247 #[test]
248 fn parse_invoke_request_roundtrips_builder() {
249 let fields = anon_struct_ctx0(0xFFF1);
250 let path = CommandPath {
251 endpoint: 0,
252 cluster: 0x0029,
253 command: 0x00,
254 };
255 let msg = build_invoke_request(path, &fields);
256 let parsed = parse_invoke_request(&msg).expect("parse");
257 assert!(!parsed.suppress_response);
258 assert!(!parsed.timed);
259 assert_eq!(parsed.commands.len(), 1);
260 assert_eq!(parsed.commands[0].path, path);
261 assert_eq!(parsed.commands[0].fields_tlv, fields);
262 assert_eq!(parsed.commands[0].command_ref, None);
263 }
264
265 #[test]
266 fn build_invoke_response_command_roundtrips() {
267 let fields = anon_struct_ctx0(7);
268 let path = CommandPath {
269 endpoint: 0,
270 cluster: 0x0029,
271 command: 0x01,
272 };
273 let msg = build_invoke_response_command(path, &fields);
274 match parse_invoke_response(&msg).expect("parse") {
275 InvokeResponse::Command {
276 path: p,
277 fields_tlv,
278 } => {
279 assert_eq!(p, path);
280 assert_eq!(fields_tlv, fields);
281 }
282 InvokeResponse::Status(s) => panic!("expected Command, got Status({s:?})"),
283 }
284 }
285
286 #[test]
287 fn build_invoke_response_status_roundtrips() {
288 let path = CommandPath {
289 endpoint: 0,
290 cluster: 0x0029,
291 command: 0x04,
292 };
293 let msg = build_invoke_response_status(path, ImStatus::Success);
294 assert!(matches!(
295 parse_invoke_response(&msg),
296 Ok(InvokeResponse::Status(ImStatus::Success))
297 ));
298 }
299}