#![forbid(unsafe_code)]
use crate::invoke::{command_path_from_value, reencode_anonymous, write_command_path};
use crate::path::CommandPath;
use crate::status::ImStatus;
use crate::{expect_message_struct, skip_container, IM_REVISION};
use matter_codec::{ContainerKind, Element, Tag, TlvReader, TlvWriter, Value};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InvokedCommand {
pub path: CommandPath,
pub fields_tlv: Vec<u8>,
pub command_ref: Option<u16>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParsedInvokeRequest {
pub suppress_response: bool,
pub timed: bool,
pub commands: Vec<InvokedCommand>,
}
pub fn parse_invoke_request(bytes: &[u8]) -> Result<ParsedInvokeRequest, crate::ImError> {
let mut r = TlvReader::new(bytes);
expect_message_struct(&mut r)?;
let mut suppress_response = false;
let mut timed = false;
let mut commands = Vec::new();
loop {
match r.next()? {
None | Some(Element::ContainerEnd) => break,
Some(Element::Scalar {
tag: Tag::Context(0),
value: Value::Bool(b),
}) => suppress_response = b,
Some(Element::Scalar {
tag: Tag::Context(1),
value: Value::Bool(b),
}) => timed = b,
Some(Element::ContainerStart {
tag: Tag::Context(2),
kind: ContainerKind::Array,
}) => {
read_invoke_requests(&mut r, &mut commands)?;
}
Some(Element::ContainerStart { .. }) => skip_container(&mut r)?,
Some(_) => {}
}
}
Ok(ParsedInvokeRequest {
suppress_response,
timed,
commands,
})
}
fn read_invoke_requests(
r: &mut TlvReader<'_>,
out: &mut Vec<InvokedCommand>,
) -> Result<(), crate::ImError> {
loop {
match r.next()? {
None | Some(Element::ContainerEnd) => return Ok(()),
Some(Element::ContainerStart {
kind: ContainerKind::Structure,
..
}) => out.push(read_command_data(r)?),
Some(Element::ContainerStart { .. }) => skip_container(r)?,
Some(_) => {}
}
}
}
fn read_command_data(r: &mut TlvReader<'_>) -> Result<InvokedCommand, crate::ImError> {
let mut path = None;
let mut fields_tlv = None;
let mut command_ref = None;
loop {
match r.next()? {
None | Some(Element::ContainerEnd) => break,
Some(Element::ContainerStart {
tag: Tag::Context(0),
kind: ContainerKind::List,
}) => {
let members = read_list_members(r)?;
path = Some(command_path_from_value(&members)?);
}
Some(Element::ContainerStart {
tag: Tag::Context(1),
kind: ContainerKind::Structure,
}) => {
let value = read_struct_value(r)?;
fields_tlv = Some(reencode_anonymous(&value));
}
Some(Element::Scalar {
tag: Tag::Context(2),
value: Value::Uint(n),
}) => {
command_ref = u16::try_from(n).ok();
}
Some(Element::ContainerStart { .. }) => skip_container(r)?,
Some(_) => {}
}
}
Ok(InvokedCommand {
path: path.ok_or(crate::ImError::MissingField("CommandDataIB.CommandPath"))?,
fields_tlv: fields_tlv
.ok_or(crate::ImError::MissingField("CommandDataIB.CommandFields"))?,
command_ref,
})
}
fn read_list_members(r: &mut TlvReader<'_>) -> Result<Vec<(Tag, Value)>, crate::ImError> {
match crate::read_container_value(r, ContainerKind::List)? {
Value::List(members) => Ok(members),
_ => Err(crate::ImError::UnexpectedValue("expected a list")),
}
}
fn read_struct_value(r: &mut TlvReader<'_>) -> Result<Value, crate::ImError> {
crate::read_container_value(r, ContainerKind::Structure)
}
#[must_use]
#[allow(clippy::expect_used)] pub fn build_invoke_response_command(path: CommandPath, response_fields_tlv: &[u8]) -> Vec<u8> {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous)
.expect("infallible: vec writer");
w.put_bool(Tag::Context(0), false)
.expect("infallible: vec writer"); w.start_array(Tag::Context(1))
.expect("infallible: vec writer"); {
w.start_structure(Tag::Anonymous)
.expect("infallible: vec writer"); w.start_structure(Tag::Context(0))
.expect("infallible: vec writer"); write_command_path(&mut w, Tag::Context(0), path);
w.put_preencoded(Tag::Context(1), response_fields_tlv)
.expect("infallible: caller passes a valid anonymous-tagged struct");
w.end_container().expect("infallible: vec writer"); w.end_container().expect("infallible: vec writer"); }
w.end_container().expect("infallible: vec writer"); w.put_uint(Tag::Context(0xFF), u64::from(IM_REVISION))
.expect("infallible: vec writer");
w.end_container().expect("infallible: vec writer"); buf
}
#[must_use]
#[allow(clippy::expect_used, clippy::missing_panics_doc)] pub fn build_invoke_response_status(path: CommandPath, status: ImStatus) -> Vec<u8> {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous)
.expect("infallible: vec writer");
w.put_bool(Tag::Context(0), false)
.expect("infallible: vec writer"); w.start_array(Tag::Context(1))
.expect("infallible: vec writer"); {
w.start_structure(Tag::Anonymous)
.expect("infallible: vec writer"); w.start_structure(Tag::Context(1))
.expect("infallible: vec writer"); write_command_path(&mut w, Tag::Context(0), path);
w.start_structure(Tag::Context(1))
.expect("infallible: vec writer"); w.put_uint(Tag::Context(0), u64::from(status.to_u8()))
.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"); }
w.end_container().expect("infallible: vec writer"); w.put_uint(Tag::Context(0xFF), u64::from(IM_REVISION))
.expect("infallible: vec writer");
w.end_container().expect("infallible: vec writer"); buf
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)] use super::*;
use crate::invoke::{build_invoke_request, parse_invoke_response, InvokeResponse};
fn anon_struct_ctx0(value: u64) -> Vec<u8> {
let mut b = Vec::new();
let mut w = TlvWriter::new(&mut b);
w.start_structure(Tag::Anonymous).unwrap();
w.put_uint(Tag::Context(0), value).unwrap();
w.end_container().unwrap();
b
}
#[test]
fn parse_invoke_request_roundtrips_builder() {
let fields = anon_struct_ctx0(0xFFF1);
let path = CommandPath {
endpoint: 0,
cluster: 0x0029,
command: 0x00,
};
let msg = build_invoke_request(path, &fields);
let parsed = parse_invoke_request(&msg).expect("parse");
assert!(!parsed.suppress_response);
assert!(!parsed.timed);
assert_eq!(parsed.commands.len(), 1);
assert_eq!(parsed.commands[0].path, path);
assert_eq!(parsed.commands[0].fields_tlv, fields);
assert_eq!(parsed.commands[0].command_ref, None);
}
#[test]
fn build_invoke_response_command_roundtrips() {
let fields = anon_struct_ctx0(7);
let path = CommandPath {
endpoint: 0,
cluster: 0x0029,
command: 0x01,
};
let msg = build_invoke_response_command(path, &fields);
match parse_invoke_response(&msg).expect("parse") {
InvokeResponse::Command {
path: p,
fields_tlv,
} => {
assert_eq!(p, path);
assert_eq!(fields_tlv, fields);
}
InvokeResponse::Status(s) => panic!("expected Command, got Status({s:?})"),
}
}
#[test]
fn build_invoke_response_status_roundtrips() {
let path = CommandPath {
endpoint: 0,
cluster: 0x0029,
command: 0x04,
};
let msg = build_invoke_response_status(path, ImStatus::Success);
assert!(matches!(
parse_invoke_response(&msg),
Ok(InvokeResponse::Status(ImStatus::Success))
));
}
}