#![forbid(unsafe_code)]
use crate::error::ImError;
use matter_codec::{Element, Tag, TlvReader, Value};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct CommandPath {
pub endpoint: u16,
pub cluster: u32,
pub command: u32,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct AttributePath {
pub endpoint: u16,
pub cluster: u32,
pub attribute: u32,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct ReadPath {
pub endpoint: Option<u16>,
pub cluster: Option<u32>,
pub attribute: Option<u32>,
}
impl ReadPath {
#[must_use]
pub fn new(endpoint: Option<u16>, cluster: Option<u32>, attribute: Option<u32>) -> Self {
Self {
endpoint,
cluster,
attribute,
}
}
#[must_use]
pub fn concrete(endpoint: u16, cluster: u32, attribute: u32) -> Self {
Self {
endpoint: Some(endpoint),
cluster: Some(cluster),
attribute: Some(attribute),
}
}
#[must_use]
pub fn cluster(endpoint: u16, cluster: u32) -> Self {
Self {
endpoint: Some(endpoint),
cluster: Some(cluster),
attribute: None,
}
}
#[must_use]
pub fn all() -> Self {
Self {
endpoint: None,
cluster: None,
attribute: None,
}
}
}
impl From<AttributePath> for ReadPath {
fn from(p: AttributePath) -> Self {
Self {
endpoint: Some(p.endpoint),
cluster: Some(p.cluster),
attribute: Some(p.attribute),
}
}
}
pub(crate) fn attribute_path_from_reader(
r: &mut TlvReader<'_>,
) -> Result<(AttributePath, bool), ImError> {
let mut endpoint = None;
let mut cluster = None;
let mut attribute = None;
let mut append = false;
loop {
match r.next()? {
None => {
return Err(ImError::Codec(matter_codec::Error::UnclosedContainer));
}
Some(Element::ContainerEnd) => break,
Some(Element::Scalar {
tag: Tag::Context(2),
value: Value::Uint(n),
}) => {
endpoint =
Some(u16::try_from(n).map_err(|_| {
ImError::UnexpectedValue("AttributePath.endpoint exceeds u16")
})?);
}
Some(Element::Scalar {
tag: Tag::Context(3),
value: Value::Uint(n),
}) => {
cluster =
Some(u32::try_from(n).map_err(|_| {
ImError::UnexpectedValue("AttributePath.cluster exceeds u32")
})?);
}
Some(Element::Scalar {
tag: Tag::Context(4),
value: Value::Uint(n),
}) => {
attribute = Some(u32::try_from(n).map_err(|_| {
ImError::UnexpectedValue("AttributePath.attribute exceeds u32")
})?);
}
Some(Element::Scalar {
tag: Tag::Context(5),
value: Value::Null,
}) => append = true,
Some(Element::ContainerStart { .. }) => crate::skip_container(r)?,
Some(_) => {}
}
}
Ok((
AttributePath {
endpoint: endpoint.ok_or(ImError::MissingField("AttributePath.endpoint"))?,
cluster: cluster.ok_or(ImError::MissingField("AttributePath.cluster"))?,
attribute: attribute.ok_or(ImError::MissingField("AttributePath.attribute"))?,
},
append,
))
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)] use super::*;
use matter_codec::TlvWriter;
fn parse(build: impl FnOnce(&mut TlvWriter<'_>)) -> Result<(AttributePath, bool), ImError> {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_list(Tag::Anonymous).unwrap();
build(&mut w);
w.end_container().unwrap();
let mut r = TlvReader::new(&buf);
assert!(matches!(
r.next().unwrap(),
Some(Element::ContainerStart { .. })
));
attribute_path_from_reader(&mut r)
}
#[test]
fn streaming_path_parse_matches_member_semantics() {
let (p, append) = parse(|w| {
w.put_uint(Tag::Context(2), 1).unwrap();
w.put_uint(Tag::Context(3), 0x0006).unwrap();
w.put_uint(Tag::Context(4), 0xFFFC).unwrap();
w.put_null(Tag::Context(5)).unwrap();
})
.unwrap();
assert_eq!((p.endpoint, p.cluster, p.attribute), (1, 0x0006, 0xFFFC));
assert!(append);
let (p, _) = parse(|w| {
w.put_uint(Tag::Context(2), 1).unwrap();
w.put_uint(Tag::Context(2), 2).unwrap();
w.put_uint(Tag::Context(3), 6).unwrap();
w.put_uint(Tag::Context(4), 0).unwrap();
})
.unwrap();
assert_eq!(p.endpoint, 2);
let (p, append) = parse(|w| {
w.put_uint(Tag::Context(2), 1).unwrap();
w.start_structure(Tag::Context(9)).unwrap();
w.put_uint(Tag::Context(0), 7).unwrap();
w.end_container().unwrap();
w.put_uint(Tag::Context(3), 6).unwrap();
w.put_uint(Tag::Context(4), 0).unwrap();
})
.unwrap();
assert_eq!(p.cluster, 6);
assert!(!append);
}
#[test]
fn streaming_path_parse_range_and_missing_errors() {
assert!(matches!(
parse(|w| {
w.put_uint(Tag::Context(2), 0x0001_0000).unwrap();
w.put_uint(Tag::Context(3), 6).unwrap();
w.put_uint(Tag::Context(4), 0).unwrap();
}),
Err(ImError::UnexpectedValue(_))
));
assert!(matches!(
parse(|w| {
w.put_uint(Tag::Context(2), 0).unwrap();
w.put_uint(Tag::Context(3), 6).unwrap();
}),
Err(ImError::MissingField("AttributePath.attribute"))
));
}
#[test]
fn truncated_path_body_errors_unclosed_container() {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_list(Tag::Anonymous).unwrap();
w.put_uint(Tag::Context(2), 1).unwrap();
w.put_uint(Tag::Context(3), 6).unwrap();
w.put_uint(Tag::Context(4), 0).unwrap();
w.end_container().unwrap();
buf.pop(); let mut r = TlvReader::new(&buf);
assert!(matches!(
r.next().unwrap(),
Some(Element::ContainerStart { .. })
));
assert!(matches!(
attribute_path_from_reader(&mut r),
Err(ImError::Codec(matter_codec::Error::UnclosedContainer))
));
}
#[test]
fn non_null_list_index_leaves_append_false() {
let (path, append) = parse(|w| {
w.put_uint(Tag::Context(2), 1).unwrap();
w.put_uint(Tag::Context(3), 6).unwrap();
w.put_uint(Tag::Context(4), 0).unwrap();
w.put_uint(Tag::Context(5), 3).unwrap(); })
.unwrap();
assert_eq!((path.endpoint, path.cluster, path.attribute), (1, 6, 0));
assert!(!append, "only ListIndex=null signals append");
}
#[test]
fn wrong_typed_member_is_ignored() {
let (path, append) = parse(|w| {
w.put_uint(Tag::Context(2), 1).unwrap();
w.put_uint(Tag::Context(3), 6).unwrap();
w.put_uint(Tag::Context(4), 0).unwrap();
w.put_utf8(Tag::Context(2), "nope").unwrap(); })
.unwrap();
assert_eq!((path.endpoint, path.cluster, path.attribute), (1, 6, 0));
assert!(!append);
}
}