use matter_codec::{Tag, Value};
#[allow(dead_code)]
pub(crate) const GROUP_KEY_MANAGEMENT_CLUSTER: u32 = 0x003F;
#[allow(dead_code)]
pub(crate) const GROUPS_CLUSTER: u32 = 0x0004;
#[allow(dead_code)]
pub(crate) const CMD_KEY_SET_WRITE: u32 = 0x00;
#[allow(dead_code)]
pub(crate) const CMD_KEY_SET_REMOVE: u32 = 0x03;
#[allow(dead_code)]
pub(crate) const ATTR_GROUP_KEY_MAP: u32 = 0x0000;
#[allow(dead_code)]
pub(crate) const CMD_ADD_GROUP: u32 = 0x00;
#[allow(dead_code)]
pub(crate) const CMD_REMOVE_GROUP: u32 = 0x03;
#[allow(dead_code)]
pub(crate) const SECURITY_POLICY_TRUST_FIRST: u64 = 0;
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct GroupKeySet {
pub key_set_id: u16,
pub epoch_key: Vec<u8>,
pub epoch_start_time: u64,
}
impl GroupKeySet {
#[must_use]
pub fn new(key_set_id: u16, epoch_key: Vec<u8>, epoch_start_time: u64) -> Self {
Self {
key_set_id,
epoch_key,
epoch_start_time,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct GroupKeyMapEntry {
pub group_id: u16,
pub group_key_set_id: u16,
}
impl GroupKeyMapEntry {
#[must_use]
pub fn new(group_id: u16, group_key_set_id: u16) -> Self {
Self {
group_id,
group_key_set_id,
}
}
}
#[allow(dead_code)]
pub(crate) fn key_set_write_fields(set: &GroupKeySet) -> Value {
let key_set = Value::Structure(vec![
(Tag::Context(0), Value::Uint(u64::from(set.key_set_id))),
(Tag::Context(1), Value::Uint(SECURITY_POLICY_TRUST_FIRST)),
(Tag::Context(2), Value::Bytes(set.epoch_key.clone())),
(Tag::Context(3), Value::Uint(set.epoch_start_time)),
(Tag::Context(4), Value::Null),
(Tag::Context(5), Value::Null),
(Tag::Context(6), Value::Null),
(Tag::Context(7), Value::Null),
]);
Value::Structure(vec![(Tag::Context(0), key_set)])
}
#[allow(dead_code)]
pub(crate) fn group_key_map_entry_value(e: GroupKeyMapEntry) -> Value {
Value::Structure(vec![
(Tag::Context(1), Value::Uint(u64::from(e.group_id))),
(Tag::Context(2), Value::Uint(u64::from(e.group_key_set_id))),
])
}
#[allow(dead_code)]
pub(crate) fn add_group_fields(group_id: u16, name: &str) -> Value {
Value::Structure(vec![
(Tag::Context(0), Value::Uint(u64::from(group_id))),
(Tag::Context(1), Value::Utf8(name.to_string())),
])
}
#[allow(dead_code)]
pub(crate) fn remove_group_fields(group_id: u16) -> Value {
Value::Structure(vec![(Tag::Context(0), Value::Uint(u64::from(group_id)))])
}
#[allow(dead_code)]
pub(crate) fn parse_group_status(fields: &Value) -> u8 {
let members = match fields {
Value::Structure(m) | Value::List(m) => m.as_slice(),
_ => return u8::MAX,
};
members
.iter()
.find(|(t, _)| *t == Tag::Context(0))
.and_then(|(_, v)| {
if let Value::Uint(n) = v {
u8::try_from(*n).ok()
} else {
None
}
})
.unwrap_or(u8::MAX)
}
#[cfg(test)]
mod tests {
use matter_codec::{TlvReader, TlvWriter};
use super::*;
fn enc(v: &Value) -> Vec<u8> {
let mut b = Vec::new();
#[allow(clippy::unwrap_used)] TlvWriter::new(&mut b)
.write_value(Tag::Anonymous, v)
.unwrap();
b
}
#[test]
fn key_set_write_matches_generated_encoder() {
use matter_clusters::gen::group_key_management::{
encode_key_set_write, GroupKeySecurityPolicyEnum, GroupKeySetStruct,
};
use matter_clusters::types::Nullable;
let epoch = vec![0xABu8; 16];
let ours = enc(&key_set_write_fields(&GroupKeySet::new(
42,
epoch.clone(),
0,
)));
let theirs = encode_key_set_write(GroupKeySetStruct {
group_key_set_id: 42,
group_key_security_policy: GroupKeySecurityPolicyEnum::TrustFirst,
epoch_key0: Nullable::Value(epoch),
epoch_start_time0: Nullable::Value(0),
epoch_key1: Nullable::Null,
epoch_start_time1: Nullable::Null,
epoch_key2: Nullable::Null,
epoch_start_time2: Nullable::Null,
group_key_multicast_policy: None,
fabric_index: None,
});
assert_eq!(
ours, theirs,
"KeySetWrite fields must byte-match the generated encoder"
);
}
#[test]
fn group_key_map_entry_matches_generated_encoder() {
let ours = enc(&group_key_map_entry_value(GroupKeyMapEntry::new(7, 42)));
#[allow(clippy::unwrap_used)] let (_tag, val) = TlvReader::new(&ours).read_value().unwrap();
let Value::Structure(members) = val else {
panic!("expected structure")
};
let group_id = members.iter().find(|(t, _)| *t == Tag::Context(1));
let key_set_id = members.iter().find(|(t, _)| *t == Tag::Context(2));
assert_eq!(group_id, Some(&(Tag::Context(1), Value::Uint(7))));
assert_eq!(key_set_id, Some(&(Tag::Context(2), Value::Uint(42))));
let fabric_index = members.iter().find(|(t, _)| *t == Tag::Context(254));
assert!(fabric_index.is_none(), "write must not emit fabric_index");
}
#[test]
fn add_group_and_status() {
let f = add_group_fields(7, "kitchen");
let Value::Structure(m) = f else { panic!() };
assert_eq!(m[0], (Tag::Context(0), Value::Uint(7)));
assert_eq!(m[1], (Tag::Context(1), Value::Utf8("kitchen".into())));
let resp = Value::Structure(vec![
(Tag::Context(0), Value::Uint(0)),
(Tag::Context(1), Value::Uint(7)),
]);
assert_eq!(parse_group_status(&resp), 0);
assert_eq!(
parse_group_status(&Value::Structure(vec![(Tag::Context(0), Value::Uint(137))])),
137
);
}
}