#![forbid(unsafe_code)]
use crate::error::ImError;
use crate::event::{EventFilter, EventPath};
use crate::path::ReadPath;
use crate::{expect_message_struct, skip_container, IM_REVISION};
use matter_codec::{Element, Tag, TlvReader, TlvWriter};
#[derive(Clone, Debug, PartialEq)]
pub struct SubscribeRequest {
pub keep_subscriptions: bool,
pub min_interval_floor: u16,
pub max_interval_ceiling: u16,
pub paths: Vec<ReadPath>,
pub event_paths: Vec<EventPath>,
pub event_filters: Vec<EventFilter>,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct SubscribeResponse {
pub subscription_id: u32,
pub max_interval: u16,
}
#[must_use]
#[allow(clippy::expect_used, clippy::missing_panics_doc)] pub fn build_subscribe_request(req: &SubscribeRequest) -> Vec<u8> {
let mut buf = Vec::with_capacity(48 + req.paths.len() * 24 + req.event_paths.len() * 24);
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous)
.expect("infallible: vec writer");
w.put_bool(Tag::Context(0), req.keep_subscriptions)
.expect("infallible: vec writer");
w.put_uint(Tag::Context(1), u64::from(req.min_interval_floor))
.expect("infallible: vec writer");
w.put_uint(Tag::Context(2), u64::from(req.max_interval_ceiling))
.expect("infallible: vec writer");
w.start_array(Tag::Context(3))
.expect("infallible: vec writer");
for p in &req.paths {
w.start_list(Tag::Anonymous)
.expect("infallible: vec writer");
if let Some(ep) = p.endpoint {
w.put_uint(Tag::Context(2), u64::from(ep))
.expect("infallible: vec writer");
}
if let Some(cl) = p.cluster {
w.put_uint(Tag::Context(3), u64::from(cl))
.expect("infallible: vec writer");
}
if let Some(at) = p.attribute {
w.put_uint(Tag::Context(4), u64::from(at))
.expect("infallible: vec writer");
}
w.end_container().expect("infallible: vec writer");
}
w.end_container().expect("infallible: vec writer");
if !req.event_paths.is_empty() {
w.start_array(Tag::Context(4))
.expect("infallible: vec writer");
for p in &req.event_paths {
p.write(&mut w).expect("infallible: vec writer");
}
w.end_container().expect("infallible: vec writer");
}
if !req.event_filters.is_empty() {
w.start_array(Tag::Context(5))
.expect("infallible: vec writer");
for f in &req.event_filters {
f.write(&mut w).expect("infallible: vec writer");
}
w.end_container().expect("infallible: vec writer");
}
w.put_bool(Tag::Context(7), false)
.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
}
pub fn parse_subscribe_response(bytes: &[u8]) -> Result<SubscribeResponse, ImError> {
let mut r = TlvReader::new(bytes);
expect_message_struct(&mut r)?;
let mut subscription_id: Option<u32> = None;
let mut max_interval: Option<u16> = None;
loop {
match r.next()? {
None | Some(Element::ContainerEnd) => break,
Some(Element::Scalar {
tag: Tag::Context(0),
value: matter_codec::Value::Uint(n),
}) => {
subscription_id = Some(u32::try_from(n).map_err(|_| {
ImError::UnexpectedValue("SubscribeResponse.subscriptionId exceeds u32")
})?);
}
Some(Element::Scalar {
tag: Tag::Context(2),
value: matter_codec::Value::Uint(n),
}) => {
max_interval = Some(u16::try_from(n).map_err(|_| {
ImError::UnexpectedValue("SubscribeResponse.maxInterval exceeds u16")
})?);
}
Some(Element::ContainerStart { .. }) => skip_container(&mut r)?,
Some(_) => {}
}
}
Ok(SubscribeResponse {
subscription_id: subscription_id
.ok_or(ImError::MissingField("SubscribeResponse.subscriptionId"))?,
max_interval: max_interval.ok_or(ImError::MissingField("SubscribeResponse.maxInterval"))?,
})
}
#[must_use]
#[allow(clippy::expect_used, clippy::missing_panics_doc)] pub fn build_status_response(status: u8) -> Vec<u8> {
let mut buf = Vec::with_capacity(16);
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous)
.expect("infallible: vec writer");
w.put_uint(Tag::Context(0), u64::from(status))
.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)]
use super::*;
use matter_codec::ContainerKind;
#[test]
fn status_response_success_has_expected_structure() {
let bytes = build_status_response(0);
let mut r = TlvReader::new(&bytes);
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(0),
value: matter_codec::Value::Uint(0)
})
));
assert!(matches!(
r.next().unwrap(),
Some(Element::Scalar {
tag: Tag::Context(0xFF),
value: matter_codec::Value::Uint(11)
})
));
}
#[test]
fn subscribe_request_has_expected_structure() {
let req = SubscribeRequest {
keep_subscriptions: false,
min_interval_floor: 1,
max_interval_ceiling: 30,
paths: vec![ReadPath::concrete(1, 0x06, 0x0000)],
event_paths: vec![],
event_filters: vec![],
};
let bytes = build_subscribe_request(&req);
let mut r = TlvReader::new(&bytes);
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(0),
value: matter_codec::Value::Bool(false)
})
));
assert!(matches!(
r.next().unwrap(),
Some(Element::Scalar {
tag: Tag::Context(1),
value: matter_codec::Value::Uint(1)
})
));
assert!(matches!(
r.next().unwrap(),
Some(Element::Scalar {
tag: Tag::Context(2),
value: matter_codec::Value::Uint(30)
})
));
assert!(matches!(
r.next().unwrap(),
Some(Element::ContainerStart {
tag: Tag::Context(3),
kind: ContainerKind::Array
})
));
}
#[test]
fn parse_subscribe_response_roundtrip() {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous).unwrap();
w.put_uint(Tag::Context(0), 0x1234_5678_u64).unwrap(); w.put_uint(Tag::Context(2), 30_u64).unwrap(); w.put_uint(Tag::Context(0xFF), 11_u64).unwrap(); w.end_container().unwrap();
let result = parse_subscribe_response(&buf).unwrap();
assert_eq!(result.subscription_id, 0x1234_5678);
assert_eq!(result.max_interval, 30);
}
}