1#![forbid(unsafe_code)]
6
7use crate::invoke::{command_path_from_reader, retag_container_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>,
37 pub command_ref: Option<u16>,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct ParsedInvokeRequest {
44 pub suppress_response: bool,
46 pub timed: bool,
48 pub commands: Vec<InvokedCommand>,
50}
51
52pub fn parse_invoke_request(bytes: &[u8]) -> Result<ParsedInvokeRequest, crate::ImError> {
60 let mut r = TlvReader::new(bytes);
61 expect_message_struct(&mut r)?;
62
63 let mut suppress_response = false;
64 let mut timed = false;
65 let mut commands = Vec::new();
66
67 loop {
68 match r.next()? {
69 None | Some(Element::ContainerEnd) => break,
70 Some(Element::Scalar {
71 tag: Tag::Context(0),
72 value: Value::Bool(b),
73 }) => suppress_response = b,
74 Some(Element::Scalar {
75 tag: Tag::Context(1),
76 value: Value::Bool(b),
77 }) => timed = b,
78 Some(Element::ContainerStart {
79 tag: Tag::Context(2),
80 kind: ContainerKind::Array,
81 }) => {
82 read_invoke_requests(&mut r, &mut commands)?;
83 }
84 Some(Element::ContainerStart { .. }) => skip_container(&mut r)?,
85 Some(_) => {}
86 }
87 }
88
89 Ok(ParsedInvokeRequest {
90 suppress_response,
91 timed,
92 commands,
93 })
94}
95
96fn read_invoke_requests(
99 r: &mut TlvReader<'_>,
100 out: &mut Vec<InvokedCommand>,
101) -> Result<(), crate::ImError> {
102 loop {
103 match r.next()? {
104 None | Some(Element::ContainerEnd) => return Ok(()),
105 Some(Element::ContainerStart {
106 kind: ContainerKind::Structure,
107 ..
108 }) => out.push(read_command_data(r)?),
109 Some(Element::ContainerStart { .. }) => skip_container(r)?,
110 Some(_) => {}
111 }
112 }
113}
114
115fn read_command_data(r: &mut TlvReader<'_>) -> Result<InvokedCommand, crate::ImError> {
118 let mut path = None;
119 let mut fields_tlv = None;
120 let mut command_ref = None;
121
122 loop {
123 match r.next()? {
124 None | Some(Element::ContainerEnd) => break,
125 Some(Element::ContainerStart {
126 tag: Tag::Context(0),
127 kind: ContainerKind::List,
128 }) => {
129 path = Some(command_path_from_reader(r)?);
130 }
131 Some(Element::ContainerStart {
132 tag: Tag::Context(1),
133 kind: ContainerKind::Structure,
134 }) => {
135 fields_tlv = Some(retag_container_anonymous(r, ContainerKind::Structure)?);
136 }
137 Some(Element::Scalar {
138 tag: Tag::Context(2),
139 value: Value::Uint(n),
140 }) => {
141 command_ref = u16::try_from(n).ok();
142 }
143 Some(Element::ContainerStart { .. }) => skip_container(r)?,
144 Some(_) => {}
145 }
146 }
147
148 Ok(InvokedCommand {
149 path: path.ok_or(crate::ImError::MissingField("CommandDataIB.CommandPath"))?,
150 fields_tlv: fields_tlv
151 .ok_or(crate::ImError::MissingField("CommandDataIB.CommandFields"))?,
152 command_ref,
153 })
154}
155
156#[must_use]
168#[allow(clippy::expect_used)] pub fn build_invoke_response_command(path: CommandPath, response_fields_tlv: &[u8]) -> Vec<u8> {
170 let mut buf = Vec::with_capacity(48 + response_fields_tlv.len());
171 let mut w = TlvWriter::new(&mut buf);
172 w.start_structure(Tag::Anonymous)
173 .expect("infallible: vec writer");
174 w.put_bool(Tag::Context(0), false)
175 .expect("infallible: vec writer"); w.start_array(Tag::Context(1))
177 .expect("infallible: vec writer"); {
179 w.start_structure(Tag::Anonymous)
180 .expect("infallible: vec writer"); w.start_structure(Tag::Context(0))
182 .expect("infallible: vec writer"); write_command_path(&mut w, Tag::Context(0), path);
184 w.put_preencoded(Tag::Context(1), response_fields_tlv)
185 .expect("infallible: caller passes a valid anonymous-tagged struct");
186 w.end_container().expect("infallible: vec writer"); w.end_container().expect("infallible: vec writer"); }
189 w.end_container().expect("infallible: vec writer"); w.put_uint(Tag::Context(0xFF), u64::from(IM_REVISION))
191 .expect("infallible: vec writer");
192 w.end_container().expect("infallible: vec writer"); buf
194}
195
196#[must_use]
200#[allow(clippy::expect_used, clippy::missing_panics_doc)] pub fn build_invoke_response_status(path: CommandPath, status: ImStatus) -> Vec<u8> {
202 let mut buf = Vec::with_capacity(64);
203 let mut w = TlvWriter::new(&mut buf);
204 w.start_structure(Tag::Anonymous)
205 .expect("infallible: vec writer");
206 w.put_bool(Tag::Context(0), false)
207 .expect("infallible: vec writer"); w.start_array(Tag::Context(1))
209 .expect("infallible: vec writer"); {
211 w.start_structure(Tag::Anonymous)
212 .expect("infallible: vec writer"); w.start_structure(Tag::Context(1))
214 .expect("infallible: vec writer"); write_command_path(&mut w, Tag::Context(0), path);
216 w.start_structure(Tag::Context(1))
217 .expect("infallible: vec writer"); w.put_uint(Tag::Context(0), u64::from(status.to_u8()))
219 .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"); }
224 w.end_container().expect("infallible: vec writer"); w.put_uint(Tag::Context(0xFF), u64::from(IM_REVISION))
226 .expect("infallible: vec writer");
227 w.end_container().expect("infallible: vec writer"); buf
229}
230
231#[cfg(test)]
232mod tests {
233 #![allow(clippy::unwrap_used, clippy::expect_used)] use super::*;
235 use crate::invoke::{build_invoke_request, parse_invoke_response, InvokeResponse};
236
237 fn anon_struct_ctx0(value: u64) -> Vec<u8> {
238 let mut b = Vec::new();
239 let mut w = TlvWriter::new(&mut b);
240 w.start_structure(Tag::Anonymous).unwrap();
241 w.put_uint(Tag::Context(0), value).unwrap();
242 w.end_container().unwrap();
243 b
244 }
245
246 #[test]
247 fn parse_invoke_request_roundtrips_builder() {
248 let fields = anon_struct_ctx0(0xFFF1);
249 let path = CommandPath {
250 endpoint: 0,
251 cluster: 0x0029,
252 command: 0x00,
253 };
254 let msg = build_invoke_request(path, &fields);
255 let parsed = parse_invoke_request(&msg).expect("parse");
256 assert!(!parsed.suppress_response);
257 assert!(!parsed.timed);
258 assert_eq!(parsed.commands.len(), 1);
259 assert_eq!(parsed.commands[0].path, path);
260 assert_eq!(parsed.commands[0].fields_tlv, fields);
261 assert_eq!(parsed.commands[0].command_ref, None);
262 }
263
264 #[test]
265 fn build_invoke_response_command_roundtrips() {
266 let fields = anon_struct_ctx0(7);
267 let path = CommandPath {
268 endpoint: 0,
269 cluster: 0x0029,
270 command: 0x01,
271 };
272 let msg = build_invoke_response_command(path, &fields);
273 match parse_invoke_response(&msg).expect("parse") {
274 InvokeResponse::Command {
275 path: p,
276 fields_tlv,
277 } => {
278 assert_eq!(p, path);
279 assert_eq!(fields_tlv, fields);
280 }
281 InvokeResponse::Status(s) => panic!("expected Command, got Status({s:?})"),
282 }
283 }
284
285 #[test]
286 fn command_fields_preserve_device_integer_widths() {
287 let nonminimal_fields = [0x15u8, 0x25, 0x00, 0x2A, 0x00, 0x18];
291 let mut buf = Vec::new();
292 let mut w = TlvWriter::new(&mut buf);
293 w.start_structure(Tag::Anonymous).unwrap();
294 w.put_bool(Tag::Context(0), false).unwrap(); w.put_bool(Tag::Context(1), false).unwrap(); w.start_array(Tag::Context(2)).unwrap(); w.start_structure(Tag::Anonymous).unwrap(); w.start_list(Tag::Context(0)).unwrap(); w.put_uint(Tag::Context(0), 0).unwrap();
300 w.put_uint(Tag::Context(1), 0x0029).unwrap();
301 w.put_uint(Tag::Context(2), 0x00).unwrap();
302 w.end_container().unwrap();
303 w.put_preencoded(Tag::Context(1), &nonminimal_fields)
304 .unwrap();
305 w.end_container().unwrap(); w.end_container().unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
308 w.end_container().unwrap();
309
310 let parsed = parse_invoke_request(&buf).expect("parse");
311 assert_eq!(parsed.commands.len(), 1);
312 assert_eq!(
313 parsed.commands[0].fields_tlv, nonminimal_fields,
314 "device widths must be preserved verbatim"
315 );
316 }
317
318 #[test]
319 fn build_invoke_response_status_roundtrips() {
320 let path = CommandPath {
321 endpoint: 0,
322 cluster: 0x0029,
323 command: 0x04,
324 };
325 let msg = build_invoke_response_status(path, ImStatus::Success);
326 assert!(matches!(
327 parse_invoke_response(&msg),
328 Ok(InvokeResponse::Status(ImStatus::Success))
329 ));
330 }
331}