1use matter_codec::{Tag, Value};
7
8#[allow(dead_code)]
11pub(crate) const GROUP_KEY_MANAGEMENT_CLUSTER: u32 = 0x003F;
12#[allow(dead_code)]
14pub(crate) const GROUPS_CLUSTER: u32 = 0x0004;
15#[allow(dead_code)]
17pub(crate) const CMD_KEY_SET_WRITE: u32 = 0x00;
18#[allow(dead_code)]
20pub(crate) const CMD_KEY_SET_REMOVE: u32 = 0x03;
21#[allow(dead_code)]
23pub(crate) const ATTR_GROUP_KEY_MAP: u32 = 0x0000;
24#[allow(dead_code)]
26pub(crate) const CMD_ADD_GROUP: u32 = 0x00;
27#[allow(dead_code)]
29pub(crate) const CMD_REMOVE_GROUP: u32 = 0x03;
30#[allow(dead_code)]
32pub(crate) const SECURITY_POLICY_TRUST_FIRST: u64 = 0;
33
34#[derive(Clone, Debug, PartialEq, Eq)]
39#[non_exhaustive]
40pub struct GroupKeySet {
41 pub key_set_id: u16,
43 pub epoch_key: Vec<u8>,
45 pub epoch_start_time: u64,
47}
48
49impl GroupKeySet {
50 #[must_use]
52 pub fn new(key_set_id: u16, epoch_key: Vec<u8>, epoch_start_time: u64) -> Self {
53 Self {
54 key_set_id,
55 epoch_key,
56 epoch_start_time,
57 }
58 }
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63#[non_exhaustive]
64pub struct GroupKeyMapEntry {
65 pub group_id: u16,
67 pub group_key_set_id: u16,
69}
70
71impl GroupKeyMapEntry {
72 #[must_use]
74 pub fn new(group_id: u16, group_key_set_id: u16) -> Self {
75 Self {
76 group_id,
77 group_key_set_id,
78 }
79 }
80}
81
82#[allow(dead_code)]
85pub(crate) fn key_set_write_fields(set: &GroupKeySet) -> Value {
86 let key_set = Value::Structure(vec![
87 (Tag::Context(0), Value::Uint(u64::from(set.key_set_id))),
88 (Tag::Context(1), Value::Uint(SECURITY_POLICY_TRUST_FIRST)),
89 (Tag::Context(2), Value::Bytes(set.epoch_key.clone())),
90 (Tag::Context(3), Value::Uint(set.epoch_start_time)),
91 (Tag::Context(4), Value::Null),
92 (Tag::Context(5), Value::Null),
93 (Tag::Context(6), Value::Null),
94 (Tag::Context(7), Value::Null),
95 ]);
96 Value::Structure(vec![(Tag::Context(0), key_set)])
97}
98
99#[allow(dead_code)]
102pub(crate) fn group_key_map_entry_value(e: GroupKeyMapEntry) -> Value {
103 Value::Structure(vec![
104 (Tag::Context(1), Value::Uint(u64::from(e.group_id))),
105 (Tag::Context(2), Value::Uint(u64::from(e.group_key_set_id))),
106 ])
107}
108
109#[allow(dead_code)]
111pub(crate) fn add_group_fields(group_id: u16, name: &str) -> Value {
112 Value::Structure(vec![
113 (Tag::Context(0), Value::Uint(u64::from(group_id))),
114 (Tag::Context(1), Value::Utf8(name.to_string())),
115 ])
116}
117
118#[allow(dead_code)]
120pub(crate) fn remove_group_fields(group_id: u16) -> Value {
121 Value::Structure(vec![(Tag::Context(0), Value::Uint(u64::from(group_id)))])
122}
123
124#[allow(dead_code)]
128pub(crate) fn parse_group_status(fields: &Value) -> u8 {
129 let members = match fields {
130 Value::Structure(m) | Value::List(m) => m.as_slice(),
131 _ => return u8::MAX,
132 };
133 members
134 .iter()
135 .find(|(t, _)| *t == Tag::Context(0))
136 .and_then(|(_, v)| {
137 if let Value::Uint(n) = v {
138 u8::try_from(*n).ok()
139 } else {
140 None
141 }
142 })
143 .unwrap_or(u8::MAX)
144}
145
146#[cfg(test)]
147mod tests {
148 use matter_codec::{TlvReader, TlvWriter};
149
150 use super::*;
151
152 fn enc(v: &Value) -> Vec<u8> {
153 let mut b = Vec::new();
154 #[allow(clippy::unwrap_used)] TlvWriter::new(&mut b)
156 .write_value(Tag::Anonymous, v)
157 .unwrap();
158 b
159 }
160
161 #[test]
162 fn key_set_write_matches_generated_encoder() {
163 use matter_clusters::gen::group_key_management::{
164 encode_key_set_write, GroupKeySecurityPolicyEnum, GroupKeySetStruct,
165 };
166 use matter_clusters::types::Nullable;
167 let epoch = vec![0xABu8; 16];
168 let ours = enc(&key_set_write_fields(&GroupKeySet::new(
169 42,
170 epoch.clone(),
171 0,
172 )));
173 let theirs = encode_key_set_write(GroupKeySetStruct {
174 group_key_set_id: 42,
175 group_key_security_policy: GroupKeySecurityPolicyEnum::TrustFirst,
176 epoch_key0: Nullable::Value(epoch),
177 epoch_start_time0: Nullable::Value(0),
178 epoch_key1: Nullable::Null,
179 epoch_start_time1: Nullable::Null,
180 epoch_key2: Nullable::Null,
181 epoch_start_time2: Nullable::Null,
182 group_key_multicast_policy: None,
183 fabric_index: None,
184 });
185 assert_eq!(
186 ours, theirs,
187 "KeySetWrite fields must byte-match the generated encoder"
188 );
189 }
190
191 #[test]
192 fn group_key_map_entry_matches_generated_encoder() {
193 let ours = enc(&group_key_map_entry_value(GroupKeyMapEntry::new(7, 42)));
197 #[allow(clippy::unwrap_used)] let (_tag, val) = TlvReader::new(&ours).read_value().unwrap();
200 let Value::Structure(members) = val else {
201 panic!("expected structure")
202 };
203 let group_id = members.iter().find(|(t, _)| *t == Tag::Context(1));
204 let key_set_id = members.iter().find(|(t, _)| *t == Tag::Context(2));
205 assert_eq!(group_id, Some(&(Tag::Context(1), Value::Uint(7))));
206 assert_eq!(key_set_id, Some(&(Tag::Context(2), Value::Uint(42))));
207 let fabric_index = members.iter().find(|(t, _)| *t == Tag::Context(254));
209 assert!(fabric_index.is_none(), "write must not emit fabric_index");
210 }
211
212 #[test]
213 fn add_group_and_status() {
214 let f = add_group_fields(7, "kitchen");
215 let Value::Structure(m) = f else { panic!() };
216 assert_eq!(m[0], (Tag::Context(0), Value::Uint(7)));
217 assert_eq!(m[1], (Tag::Context(1), Value::Utf8("kitchen".into())));
218 let resp = Value::Structure(vec![
219 (Tag::Context(0), Value::Uint(0)),
220 (Tag::Context(1), Value::Uint(7)),
221 ]);
222 assert_eq!(parse_group_status(&resp), 0);
223 assert_eq!(
224 parse_group_status(&Value::Structure(vec![(Tag::Context(0), Value::Uint(137))])),
225 137
226 );
227 }
228}