#![forbid(unsafe_code)]
use crate::error::ImError;
use crate::{read_container_value, skip_container};
use matter_codec::{ContainerKind, Element, Tag, TlvReader, TlvWriter, Value};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct EventPath {
pub node: Option<u64>,
pub endpoint: Option<u16>,
pub cluster: Option<u32>,
pub event: Option<u32>,
pub is_urgent: Option<bool>,
}
impl EventPath {
#[must_use]
pub fn concrete(endpoint: u16, cluster: u32, event: u32) -> Self {
Self {
node: None,
endpoint: Some(endpoint),
cluster: Some(cluster),
event: Some(event),
is_urgent: None,
}
}
#[must_use]
pub fn cluster(endpoint: u16, cluster: u32) -> Self {
Self {
node: None,
endpoint: Some(endpoint),
cluster: Some(cluster),
event: None,
is_urgent: None,
}
}
pub(crate) fn write(&self, w: &mut TlvWriter<'_>) -> Result<(), matter_codec::Error> {
w.start_list(Tag::Anonymous)?;
if let Some(n) = self.node {
w.put_uint(Tag::Context(0), n)?;
}
if let Some(e) = self.endpoint {
w.put_uint(Tag::Context(1), u64::from(e))?;
}
if let Some(c) = self.cluster {
w.put_uint(Tag::Context(2), u64::from(c))?;
}
if let Some(ev) = self.event {
w.put_uint(Tag::Context(3), u64::from(ev))?;
}
if let Some(u) = self.is_urgent {
w.put_bool(Tag::Context(4), u)?;
}
w.end_container()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct EventFilter {
pub node: Option<u64>,
pub event_min: u64,
}
impl EventFilter {
#[must_use]
pub fn from_event_min(event_min: u64) -> Self {
Self {
node: None,
event_min,
}
}
pub(crate) fn write(&self, w: &mut TlvWriter<'_>) -> Result<(), matter_codec::Error> {
w.start_structure(Tag::Anonymous)?;
if let Some(n) = self.node {
w.put_uint(Tag::Context(0), n)?;
}
w.put_uint(Tag::Context(1), self.event_min)?;
w.end_container()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EventPriority {
Debug,
Info,
Critical,
Unknown(u8),
}
impl EventPriority {
#[must_use]
fn from_u8(v: u8) -> Self {
match v {
0 => Self::Debug,
1 => Self::Info,
2 => Self::Critical,
other => Self::Unknown(other),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EventTimestamp {
Epoch(u64),
System(u64),
DeltaEpoch(u64),
DeltaSystem(u64),
None,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct EventReportItem {
pub path: EventPath,
pub event_number: u64,
pub priority: EventPriority,
pub timestamp: EventTimestamp,
pub value: Value,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum EventReport {
Data(EventReportItem),
Status {
path: EventPath,
status: u8,
},
}
fn event_path_from_reader(r: &mut TlvReader<'_>) -> Result<EventPath, ImError> {
let mut p = EventPath::default();
loop {
match r.next()? {
None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
Some(Element::ContainerEnd) => return Ok(p),
Some(Element::Scalar {
tag: Tag::Context(0),
value: Value::Uint(n),
}) => p.node = Some(n),
Some(Element::Scalar {
tag: Tag::Context(1),
value: Value::Uint(n),
}) => p.endpoint = u16::try_from(n).ok(),
Some(Element::Scalar {
tag: Tag::Context(2),
value: Value::Uint(n),
}) => p.cluster = u32::try_from(n).ok(),
Some(Element::Scalar {
tag: Tag::Context(3),
value: Value::Uint(n),
}) => p.event = u32::try_from(n).ok(),
Some(Element::Scalar {
tag: Tag::Context(4),
value: Value::Bool(b),
}) => p.is_urgent = Some(b),
Some(Element::ContainerStart { .. }) => skip_container(r)?,
Some(_) => {}
}
}
}
fn parse_event_report_ib(r: &mut TlvReader<'_>) -> Result<Option<EventReport>, ImError> {
let mut out: Option<EventReport> = None;
loop {
match r.next()? {
None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
Some(Element::ContainerEnd) => break,
Some(Element::ContainerStart {
tag: Tag::Context(1),
kind: ContainerKind::Structure,
}) => out = Some(EventReport::Data(parse_event_data(r)?)),
Some(Element::ContainerStart {
tag: Tag::Context(0),
kind: ContainerKind::Structure,
}) => out = Some(parse_event_status(r)?),
Some(Element::ContainerStart { .. }) => skip_container(r)?,
Some(_) => {}
}
}
Ok(out)
}
fn parse_event_data(r: &mut TlvReader<'_>) -> Result<EventReportItem, ImError> {
let mut path = EventPath::default();
let mut event_number = 0u64;
let mut priority = EventPriority::Unknown(0xFF);
let mut timestamp = EventTimestamp::None;
let mut value: Option<Value> = None;
loop {
match r.next()? {
None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
Some(Element::ContainerEnd) => break,
Some(Element::ContainerStart {
tag: Tag::Context(0),
kind: ContainerKind::List,
}) => {
path = event_path_from_reader(r)?;
}
Some(Element::Scalar {
tag: Tag::Context(1),
value: Value::Uint(n),
}) => event_number = n,
Some(Element::Scalar {
tag: Tag::Context(2),
value: Value::Uint(n),
}) => priority = EventPriority::from_u8(u8::try_from(n).unwrap_or(0xFF)),
Some(Element::Scalar {
tag: Tag::Context(3),
value: Value::Uint(n),
}) => timestamp = EventTimestamp::Epoch(n),
Some(Element::Scalar {
tag: Tag::Context(4),
value: Value::Uint(n),
}) => timestamp = EventTimestamp::System(n),
Some(Element::Scalar {
tag: Tag::Context(5),
value: Value::Uint(n),
}) => timestamp = EventTimestamp::DeltaEpoch(n),
Some(Element::Scalar {
tag: Tag::Context(6),
value: Value::Uint(n),
}) => timestamp = EventTimestamp::DeltaSystem(n),
Some(Element::Scalar {
tag: Tag::Context(7),
value: v,
}) => value = Some(v),
Some(Element::ContainerStart {
tag: Tag::Context(7),
kind,
}) => value = Some(read_container_value(r, kind)?),
Some(Element::ContainerStart { .. }) => skip_container(r)?,
Some(_) => {}
}
}
Ok(EventReportItem {
path,
event_number,
priority,
timestamp,
value: value.ok_or(ImError::MissingField("EventData.Data"))?,
})
}
fn parse_event_status(r: &mut TlvReader<'_>) -> Result<EventReport, ImError> {
let mut path = EventPath::default();
let mut status = 0u8;
loop {
match r.next()? {
None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
Some(Element::ContainerEnd) => break,
Some(Element::ContainerStart {
tag: Tag::Context(0),
kind: ContainerKind::List,
}) => {
path = event_path_from_reader(r)?;
}
Some(Element::ContainerStart {
tag: Tag::Context(1),
kind: ContainerKind::Structure,
}) => loop {
match r.next()? {
None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
Some(Element::ContainerEnd) => break,
Some(Element::Scalar {
tag: Tag::Context(0),
value: Value::Uint(n),
}) => status = u8::try_from(n).unwrap_or(0),
Some(Element::ContainerStart { .. }) => skip_container(r)?,
Some(_) => {}
}
},
Some(Element::ContainerStart { .. }) => skip_container(r)?,
Some(_) => {}
}
}
Ok(EventReport::Status { path, status })
}
pub(crate) fn parse_event_reports(
r: &mut TlvReader<'_>,
out: &mut Vec<EventReport>,
) -> Result<(), ImError> {
loop {
match r.next()? {
None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
Some(Element::ContainerEnd) => return Ok(()),
Some(Element::ContainerStart {
kind: ContainerKind::Structure,
..
}) => {
if let Some(rep) = parse_event_report_ib(r)? {
out.push(rep);
}
}
Some(Element::ContainerStart { .. }) => skip_container(r)?,
Some(_) => {}
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)] use super::*;
use matter_codec::{ContainerKind, Element, Tag, TlvReader, Value};
#[test]
fn event_path_encodes_as_list_with_tags_1_2_3() {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
EventPath::concrete(0, 0x28, 0x00).write(&mut w).unwrap();
let mut r = TlvReader::new(&buf);
assert!(matches!(
r.next().unwrap(),
Some(Element::ContainerStart {
tag: Tag::Anonymous,
kind: ContainerKind::List
})
));
assert!(matches!(
r.next().unwrap(),
Some(Element::Scalar {
tag: Tag::Context(1),
value: Value::Uint(0)
})
));
assert!(matches!(
r.next().unwrap(),
Some(Element::Scalar {
tag: Tag::Context(2),
value: Value::Uint(0x28)
})
));
assert!(matches!(
r.next().unwrap(),
Some(Element::Scalar {
tag: Tag::Context(3),
value: Value::Uint(0x00)
})
));
}
#[test]
fn event_filter_encodes_as_struct() {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
EventFilter::from_event_min(0).write(&mut w).unwrap();
let mut r = TlvReader::new(&buf);
assert!(matches!(
r.next().unwrap(),
Some(Element::ContainerStart {
tag: Tag::Anonymous,
kind: ContainerKind::Structure
})
));
assert!(matches!(
r.next().unwrap(),
Some(Element::Scalar {
tag: Tag::Context(1),
value: Value::Uint(0)
})
));
}
#[test]
fn parses_event_data_ib() {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous).unwrap(); w.start_structure(Tag::Context(1)).unwrap(); w.start_list(Tag::Context(0)).unwrap(); w.put_uint(Tag::Context(1), 0).unwrap();
w.put_uint(Tag::Context(2), 0x28).unwrap();
w.put_uint(Tag::Context(3), 0x00).unwrap();
w.end_container().unwrap();
w.put_uint(Tag::Context(1), 1).unwrap(); w.put_uint(Tag::Context(2), 2).unwrap(); w.put_uint(Tag::Context(3), 0).unwrap(); w.put_uint(Tag::Context(7), 7).unwrap(); w.end_container().unwrap();
w.end_container().unwrap();
let mut r = TlvReader::new(&buf);
assert!(matches!(
r.next().unwrap(),
Some(Element::ContainerStart { .. })
));
let rep = parse_event_report_ib(&mut r).unwrap().unwrap();
match rep {
EventReport::Data(it) => {
assert_eq!(it.path.endpoint, Some(0));
assert_eq!(it.path.cluster, Some(0x28));
assert_eq!(it.path.event, Some(0x00));
assert_eq!(it.event_number, 1);
assert_eq!(it.priority, EventPriority::Critical);
assert_eq!(it.timestamp, EventTimestamp::Epoch(0));
assert_eq!(it.value, Value::Uint(7));
}
EventReport::Status { .. } => panic!("expected Data, got Status"),
}
}
#[test]
fn parses_event_status_ib() {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous).unwrap(); w.start_structure(Tag::Context(0)).unwrap(); w.start_list(Tag::Context(0)).unwrap(); w.put_uint(Tag::Context(1), 1).unwrap();
w.put_uint(Tag::Context(2), 0x28).unwrap();
w.put_uint(Tag::Context(3), 0x02).unwrap();
w.end_container().unwrap();
w.start_structure(Tag::Context(1)).unwrap(); w.put_uint(Tag::Context(0), 0x86).unwrap(); w.end_container().unwrap();
w.end_container().unwrap();
w.end_container().unwrap();
let mut r = TlvReader::new(&buf);
assert!(matches!(
r.next().unwrap(),
Some(Element::ContainerStart { .. })
));
let rep = parse_event_report_ib(&mut r).unwrap().unwrap();
match rep {
EventReport::Status { path, status } => {
assert_eq!(path.endpoint, Some(1));
assert_eq!(path.event, Some(0x02));
assert_eq!(status, 0x86);
}
EventReport::Data(_) => panic!("expected Status, got Data"),
}
}
}